register
キーワードはC言語で何をしますか?最適化に使用されていることを読みましたが、どの規格でも明確に定義されていません。それはまだ関連性がありますか?もしそうなら、いつそれを使用しますか?
19 に答える
変数が頻繁に使用されること、および可能であればプロセッサレジスタに保持することをお勧めすることは、コンパイラへのヒントです。
最近のほとんどのコンパイラはそれを自動的に行い、私たち人間よりもそれらを選ぶのが得意です。
コンパイラが変数をレジスタではなくメモリに保持することを決定したとしても、レジスタ変数のアドレスを取得できないと誰も言及していないことに驚いています。
したがって、register
あなたを使用しても何も得られず(とにかくコンパイラーは変数をどこに置くかを決定します)、演算子を失います&
-それを使用する理由はありません。
変数を格納するために、RAMではなくCPUレジスタを使用するようにコンパイラに指示します。レジスタはCPUにあり、RAMよりもはるかに高速にアクセスできます。ただし、これはコンパイラへの提案にすぎず、実行されない場合があります。
この質問はCに関するものですが、C ++に関する同じ質問は、この質問の完全な複製として閉じられました。したがって、この回答はCには当てはまらない場合があります。
C ++ 11標準の最新ドラフトであるN3485は、7.1.1/3で次のように述べています。
指定子は、その
register
ように宣言された変数が頻繁に使用されるという実装へのヒントです。[注:ヒントは無視できます。ほとんどの実装では、変数のアドレスが取得された場合、ヒントは無視されます。この使用は非推奨です...—end note ]
C ++( Cではない)では、標準では、宣言された変数のアドレスを取得できないとは述べられていませんregister
。ただし、CPUレジスタに保存されている変数には、その存続期間を通じてメモリの場所が関連付けられていないため、そのアドレスを取得しようとすると無効になり、コンパイラはregister
キーワードを無視してアドレスの取得を許可します。
オプティマイザーがこれについてあなたができるよりも良い決定をするので、それは少なくとも15年間関係がありませんでした。関連性がある場合でも、SPARCやM68000などの多くのレジスタを備えたCPUアーキテクチャでは、レジスタが不足しているIntelの場合よりもはるかに理にかなっており、そのほとんどはコンパイラによって独自の目的で予約されています。
最適化に使用されていることを読みましたが、どの標準でも明確に定義されていません。
実際、これはC 標準によって明確に定義されています。N1570 ドラフトセクション 6.7.1 パラグラフ 6 を引用します (他のバージョンの文言は同じです) 。
ストレージ クラス指定子を使用したオブジェクトの識別子の宣言は、オブジェクト
register
へのアクセスが可能な限り高速であることを示唆しています。そのような提案が有効である範囲は、実装によって定義されます。
単項演算子は、 で&
定義されたオブジェクトに適用できません。また、外部宣言で使用することもできません。register
register
register
-修飾されたオブジェクトに固有の他の (かなりあいまいな) 規則がいくつかあります。
で配列オブジェクトを定義すると、register
動作が未定義になります。
訂正:を使用して配列オブジェクトを定義することは正当register
ですが、そのようなオブジェクトでは何の役にも立ちません (配列にインデックスを付けるには、最初の要素のアドレスを取得する必要があります)。- 指定子 (C11の
_Alignas
新機能) は、そのようなオブジェクトには適用されない場合があります。 va_start
マクロに渡されたパラメーター名が修飾されている場合register
、動作は未定義です。
他にもいくつかあるかもしれません。興味があれば、規格のドラフトをダウンロードし、「登録」を検索してください。
名前が示すように、 の本来の意味はregister
、オブジェクトを CPU レジスタに格納する必要があるということでした。しかし、コンパイラの最適化の改善により、これはあまり役に立たなくなりました。C標準の最新バージョンは、CPUレジスタを参照していません。これは、そのようなものがあると想定する必要がなくなったためです(レジスタを使用しないアーキテクチャがあります)。register
オブジェクト宣言に適用すると、コンパイラ自体のレジスタ割り当てに干渉するため、生成されたコードが悪化する可能性が高くなるというのが一般的な考えです。それが役立つケースがまだいくつかあるかもしれません (たとえば、変数がアクセスされる頻度を本当に知っていて、最新の最適化コンパイラが把握できる知識よりも優れている場合など)。
の主な具体的な効果はregister
、オブジェクトのアドレスを取得しようとする試みを防ぐことです。これは、ローカル変数にしか適用できず、最適化コンパイラは、そのようなオブジェクトのアドレスが取得されていないことを自身で確認できるため、最適化のヒントとしては特に有用ではありません。
実際、 register は、変数がプログラム内の他のもの (char でさえも) とエイリアスしないことをコンパイラーに伝えます。
これは、さまざまな状況で最新のコンパイラによって悪用される可能性があり、コンパイラが複雑なコードでかなり役立つ可能性があります。単純なコードでは、コンパイラはこれを独自に理解できます。
それ以外の場合は、何の役にも立たず、レジスタの割り当てには使用されません。コンパイラが十分に新しいものである限り、通常、これを指定してもパフォーマンスが低下することはありません。
コンパイラの洗練されたグラフの色付けアルゴリズムをいじっています。これは、レジスタの割り当てに使用されます。まあ、ほとんど。これはコンパイラへのヒントとして機能します。これは本当です。ただし、レジスター変数のアドレスを取得することは許可されていないため、完全に無視されるわけではありません (コンパイラーは勝手に別の動作を試みることを思い出してください)。ある意味では、それを使用しないように言っています。
キーワードは、長い間使用されていました。人差し指で数えられる数のレジスターしかなかった時代。
しかし、私が言ったように、廃止されたからといって、それを使用できないわけではありません。
比較のためのちょっとしたデモ (実世界の目的なし): 各変数の前のキーワードを削除すると、このコードは i7 (GCC)でregister
3.41 秒かかり、同じコードは 0.7 秒で完了します。 register
#include <stdio.h>
int main(int argc, char** argv) {
register int numIterations = 20000;
register int i=0;
unsigned long val=0;
for (i; i<numIterations+1; i++)
{
register int j=0;
for (j;j<i;j++)
{
val=j+i;
}
}
printf("%d", val);
return 0;
}
次のコードを使用して、QNX 6.5.0 で register キーワードをテストしました。
#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <sys/neutrino.h>
#include <sys/syspage.h>
int main(int argc, char *argv[]) {
uint64_t cps, cycle1, cycle2, ncycles;
double sec;
register int a=0, b = 1, c = 3, i;
cycle1 = ClockCycles();
for(i = 0; i < 100000000; i++)
a = ((a + b + c) * c) / 2;
cycle2 = ClockCycles();
ncycles = cycle2 - cycle1;
printf("%lld cycles elapsed\n", ncycles);
cps = SYSPAGE_ENTRY(qtime) -> cycles_per_sec;
printf("This system has %lld cycles per second\n", cps);
sec = (double)ncycles/cps;
printf("The cycles in seconds is %f\n", sec);
return EXIT_SUCCESS;
}
次の結果が得られました。
-> 807679611 サイクル経過
-> このシステムは毎秒 3300830000 サイクルです
-> 秒単位のサイクルは ~0.244600 です
そして今レジスタintなし:
int a=0, b = 1, c = 3, i;
私が得た:
-> 1421694077 サイクル経過
-> このシステムは毎秒 3300830000 サイクルです
-> 秒単位のサイクルは ~0.430700
70 年代、C 言語の黎明期に、変数が非常に頻繁に使用されるため、変数を使用するのが賢明であることをプログラマーがコンパイラーに通知できるようにするために、 register キーワードが導入されました。その値をプロセッサの内部レジスタの 1 つに保持します。
今日では、オプティマイザーは、プログラマーよりもはるかに効率的に、レジスターに保持される可能性が高い変数を決定します。オプティマイザーは、プログラマーのヒントを常に考慮しているわけではありません。
多くの人が register キーワードを使用しないことを誤って推奨しています。
その理由を見てみましょう!
register キーワードには関連する副作用があります。レジスタ型変数を参照 (アドレスを取得) することはできません。
他の人にレジスタを使用しないようにアドバイスする人々は、これを追加の議論として誤って受け取ります。
ただし、レジスター変数のアドレスを取得できないという単純な事実により、コンパイラー (およびそのオプティマイザー) は、この変数の値がポインターを介して間接的に変更できないことを認識できます。
命令ストリームの特定の時点で、レジスタ変数にプロセッサのレジスタに割り当てられた値があり、別の変数の値を取得するためにそのレジスタが使用されていない場合、コンパイラは再ロードする必要がないことを認識しますそのレジスタ内の変数の値。これにより、コストのかかる無駄なメモリ アクセスを回避できます。
独自のテストを行うと、最も内側のループでパフォーマンスが大幅に向上します。
レジスターは、この変数が変数の使用に使用できる数少ないレジスターの1つに格納されることを正当化するのに十分な書き込み/読み取りが行われると、コーダーが信じていることをコンパイラーに通知します。レジスタからの読み取り/書き込みは通常より高速であり、より小さなオペコードセットが必要になる場合があります。
現在、これはあまり役に立ちません。ほとんどのコンパイラのオプティマイザは、その変数にレジスタを使用するかどうか、およびその期間を決定する際に、あなたよりも優れているからです。
Registerキーワードは、特定の変数をCPUレジスタに格納して、高速にアクセスできるようにするようにコンパイラに指示します。プログラマーの観点からは、registerキーワードは、プログラムで頻繁に使用される変数に使用されるため、コンパイラーはコードを高速化できます。変数をCPUレジスタに保持するかメインメモリに保持するかはコンパイラによって異なりますが。
サポートされているCコンパイラでは、変数の値が実際のプロセッサレジスタに保持されるようにコードを最適化しようとします。
Microsoft の Visual C++ コンパイラは、register
グローバル レジスタ割り当ての最適化 (/Oe コンパイラ フラグ) が有効になっている場合、キーワードを無視します。
MSDN のregister キーワードを参照してください。
Register は、特定の変数をレジスタに格納してからメモリに格納することで、このコードを最適化するようコンパイラに指示します。これはコンパイラへの要求であり、コンパイラはこの要求を考慮する場合と考慮しない場合があります。変数の一部が非常に頻繁にアクセスされる場合に、この機能を使用できます。例: ループ。
もう1つ、変数をレジスタとして宣言すると、メモリに格納されないため、そのアドレスを取得できません。CPUレジスタで割り当てを取得します。