I.はじめに
1.コンパイラ最適化の概要
メモリアクセス速度はCPU処理速度よりもはるかに遅いため、マシンの全体的なパフォーマンスを向上させるために、ハードウェアにハードウェア高速キャッシュが導入され、メモリへのアクセスが高速化されます。また、最近のCPUでの命令の実行は必ずしも厳密な順序で実行されるとは限らず、相関のない命令を順不同で実行して、CPUの命令パイプラインを最大限に活用し、実行速度を上げることができます。上記はハードウェアレベルの最適化です。
ソフトウェアレベルでの最適化を見てください。1つはコードを書くときにプログラマーによって最適化され、もう1つはコンパイラーによって最適化されます。编译器优化常用的方法有:将内存变量缓存到寄存器;调整指令顺序充分利用CPU指令流水线,常见的是重新排序读写指令。
コンベンショナルメモリを最適化する場合、これらの最適化は透過的で効率的です。コンパイラの最適化またはハードウェアの並べ替えによって引き起こされる問題の解決策は、ハードウェア(または他のプロセッサ)の観点から特定の順序で実行する必要がある操作間にメモリバリアを設定することです。Linuxはマクロソリューションを提供します。コンパイラが問題です。
void Barrier(void)
この関数はコンパイラにメモリバリアを挿入するように通知しますが、ハードウェアには無効です。コンパイルされたコードは、現在のCPUレジスタにあるすべての変更された値をメモリに格納し、データが送信されたときにメモリから再度読み取ります。が必要です。
2、揮発性
揮発性は常に最適化に関連しています。コンパイラには、プログラム内の変数が割り当てられている場所、使用されている場所、失敗した場所を分析するデータフロー分析と呼ばれる手法があります。分析結果は、一定のマージ、一定の伝播に使用できます。およびその他の最適化、さらに排除することができます。いくつかのコード。ただし、これらの最適化はプログラムで必要とされない場合があり、現時点で使用できますvolatile关键字禁止做这些优化
。
2、揮発性の詳細な説明
1.揮発性の役割
volatileの本来の目的は“易变的”
、レジスタへのアクセスがメモリセルへのアクセスよりもはるかに高速であるため、コンパイラは通常、メモリへのアクセスを減らすように最適化しますが、ダーティデータを読み取る可能性があります。当要求使用volatile声明变量值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据
。正確には、このキーワードで宣言された変数に遭遇すると、コンパイラは変数にアクセスするコードを最適化せず、特別なアドレスへの安定したアクセスを提供します。volatileを使用しない場合、コンパイラはステートメントを最適化します。(簡潔に言うと、volatileキーワードはコンパイラーのコンパイル結果に影響します。volatileで宣言された変数は、変数がいつでも変更される可能性があることを示します。変数に関連する操作については、エラーを回避するためにコンパイルの最適化を実行しないでください)
2.2つの例を見てください
1> 告诉compiler不能做任何优化
たとえば、特定のアドレスに2つのコマンドを送信するには、次のようにします。
int *ip =...; //设备地址
*ip = 1; //第一个指令
*ip = 2; //第二个指令
上記のプログラムコンパイラは最適化できます。
int *ip = ...;
*ip = 2;
その結果、最初の命令が失われます。volatileが使用されている場合、コンパイラーは、プログラムの本来の意図を保証するための最適化を許可しません。
volatile int *ip = ...;
*ip = 1;
*ip = 2;
コンパイラーで最適化する場合でも、2つの代入ステートメントを1つにマージすることはありません。他の最適化のみを実行できます。
2> 用volatile定义的变量会在程序外被改变,每次都必须从内存中读取,而不能重复使用放在cache或寄存器中的备份。
例えば:
volatile char a;
a=0;
while(!a){
//do some things;
}
doother();
如果没有 volatile,doother()不会被执行
3.揮発性変数を使用するためのいくつかのシナリオ
1> 中断服务程序中修改的供其它程序检测的变量需要加volatile;
例えば:
static int i=0;
int main(void)
{
...
while (1){
if (i) dosomething();
}
}
/* Interrupt service routine. */
void ISR_2(void)
{
i=1;
}
ISR_2の意図は、割り込みが生成されるようにプログラムすることであり、関数呼び出しは主関数を実行しますが、コンパイラは主関数内で変更されていないと判断するため、レジスタの読み取り操作i
から1回だけ実行できi
、それぞれif
がすべてのみ決定されます。このレジスタの内容を使用すると、“i副本”
dosomethingが呼び出されることはありません。volatile
変更された変数を追加すると、コンパイラーは変数の読み取りと書き込みを行って、これが最適化されないようにします(ポジティブ実行)。この例i
では、そのように述べる必要があります。
2>多任务环境下各任务间共享的标志应该加volatile
3>存储器映射的硬件寄存器通常也要加voliate,因为每次对它的读写都可能有不同意义。
例えば:
デバイスが初期化され、このデバイスの特定のレジスタが0xff800000であるとします。
int *output = (unsigned int *)0xff800000;//定义一个IO端口;
int init(void)
{
int i;
for(i=0; i< 10; i++){
*output = i;
}
}
コンパイラーによる最適化後、コンパイラーは、前のループが半日意味がないと判断し、最終的には出力ポインターを9に割り当てるだけなので、最終結果には影響しません。コンパイラーは最終的にコンパイル済みを提供します。コードの結果は次のものと同等です。
int init(void)
{
*output = 9;
}
上記のコードと同じ順序で外部デバイスを初期化すると、明らかに最適化プロセスは目標を達成しません。逆に、このポートへの書き込み操作を繰り返さずに読み取り操作を繰り返す場合、結果は同じです。コンパイラが最適化された後、コードがこのアドレスを1回だけ読み取る可能性があります。ただし、コードの観点からは問題ありません。現時点volatile
では、変数が不安定であることをコンパイラーに通知するための使用に関して、今回の変数のエクスペリエンスを最適化するためではありません。
例えば:
volatile int *output=(volatile unsigned int *)0xff800000;//定义一个I/O端口
さらに、上記のいくつかの状況を同時に考慮する必要があることがよくあります数据的完整性
(相互に関連する記号は半分読み取られ、中断され、書き直されます)1中可以通过关中断来实现,2中禁止任务调度,3中则只能依靠硬件的良好设计
。
4.いくつかの質問
- 1)パラメータをconstまたはvolatileにすることはできますか?
はい、読み取り専用ステータスレジスタなど。予期せず変更される可能性があるため、揮発性です。プログラムがそれを変更しようとすべきではないので、それはconstです。
- 2)ポインタは揮発性ですか?
はい、割り込みサービスルーチンがバッファへのポインタを変更する場合。
5.揮発性の本質
1>コンパイラの最適化
このスレッドでは、変数を読み取るときに、アクセス速度を向上させるために、コンパイラが最適化時に変数をレジスタに読み込むことがあります。後で変数値を取得すると、値はレジスタから直接取得されます。変数の場合このスレッドで値が変更されると、一貫性を維持するために、変数の新しい値が同時にレジスタにコピーされます。
他のスレッドなどで変数の値が変化しても、それに応じてレジスタの値が変化しないため、アプリケーションプログラムが読み取った値が実際の変数の値と一致しなくなります。
このレジスタの値が他のスレッドなどによって変更された場合、元の変数の値は変更されないため、アプリケーションプログラムによって読み取られた値は実際の変数の値と一致しません。
2> volatileは、“直接存取原始内存地址”
より適切な「可変」として解釈する必要があります。この解釈は、単に少し誤解を招くものです。
6.次の機能の何が問題になっていますか
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
このプログラムの目的は、ポインターptrが指す値の2乗を返すことです。ただし、 ptrは揮発性パラメーターを指すため、コンパイラーは次のようなコードを生成します。
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
* ptrの値が予期せず変更される可能性があるため、aとbが異なる場合があります。その結果、このコードは期待したものとは異なる二乗値を返す可能性があります。正しいコードは次のとおりです。
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
注意:频繁地使用volatile很可能会增加代码尺寸和降低性能,因此要合理的使用volatile。