何をしないregister
キーワードは、C言語でいますか?私はそれが最適化に使用されるが、どの標準でも明確に定義されていないことを読みました。それはまだ関連していますか?そうであれば、いつ使用しますか?
register
変数のアドレスを取得してみてください。
何をしないregister
キーワードは、C言語でいますか?私はそれが最適化に使用されるが、どの標準でも明確に定義されていないことを読みました。それはまだ関連していますか?そうであれば、いつ使用しますか?
register
変数のアドレスを取得してみてください。
回答:
変数が頻繁に使用され、可能であればプロセッサレジスタに保持することをお勧めします。
最近のほとんどのコンパイラはそれを自動的に行い、人間よりもそれらを選ぶのが得意です。
register
変数のアドレスが取得されないようにコンパイラーが必要です。これがキーワードの唯一の必須の効果ですregister
。変数はこの関数内でのみ変更できると言うのは簡単になるので、これでも最適化を改善するには十分です。
コンパイラーが変数をレジスターではなくメモリーに保持することを決定した場合でも、レジスター変数のアドレスを取得できないと誰も言っていないことに驚いています。
そのため、使用しregister
ても何も得られず(とにかくコンパイラーが変数をどこに配置するかを決定します)、&
演算子を失います-使用する理由はありません。
register
はエイリアシングが発生しないことを証明することで悪名高いので、コンパイラーがそれをレジスターに入れなくても、これに役立ちます。
const
は、何も得られないので役に立たず、変数を変更する機能を失うだけです。register
将来誰も考えずに変数のアドレスを取得しないようにするために使用できます。私は使う理由がありませんでしたregister
。
変数を格納するために、RAMではなくCPUレジスタを使用するようコンパイラーに指示します。レジスタはCPU内にあり、RAMよりもアクセスが高速です。しかし、これはコンパイラへの提案にすぎず、フォロースルーされない場合があります。
私はこの質問がCに関するものであることを知っていますが、C ++に対する同じ質問がこの質問の完全な複製としてクローズされました。したがって、この回答はCには適用されない場合があります。
C ++ 11標準の最新のドラフトであるN3485は、7.1.1 / 3で次のように述べています。
register
指定子はそう宣言した変数は頻繁に使用されることを、実装へのヒントです。[ 注:ヒントは無視できます。ほとんどの実装では、変数のアドレスが取得された場合、ヒントは無視されます。この使用は非推奨です... —end note ]
(ただし、C ++でない Cで)、標準はあなたが宣言された変数のアドレスを取ることができないという状態ではないんregister
。ただし、その存続期間を通じてCPUレジスタに格納された変数にはメモリロケーションが関連付けられていないため、そのアドレスを取得しようとしても無効になり、コンパイラはregister
キーワードを無視してアドレスを取得できます。
実際、registerは、変数がプログラム内のほかのもの(charでさえも)とエイリアスしないことをコンパイラーに伝えます。
これは、さまざまな状況で最新のコンパイラーによって悪用される可能性があり、複雑なコードでコンパイラーをかなり助けることができます-単純なコードでは、コンパイラーは自分でこれを理解できます。
それ以外の場合は、目的がなく、レジスター割り当てには使用されません。ご使用のコンパイラーが十分に最新である限り、それを指定してもパフォーマンスの低下は通常発生しません。
register
アドレスの取得を禁止します。そうしないと、変数のアドレスが外部関数に渡されているにもかかわらず、コンパイラーがレジスターの最適化を適用できる場合があることをコンパイラーに知らせるのに役立ちます(変数はその特定の呼び出しのためにメモリにフラッシュされますが、関数が戻ると、コンパイラは再びそれをアドレスが取得されたことのない変数として扱うことができます)。
bar
ているregister
変数、コンパイラがそのレジャーでも置き換えfoo(&bar);
でint temp=bar; foo(&temp); bar=temp;
はなく、のアドレス取ってbar
他のほとんどの状況では禁止されるだろうことは、過度に複雑なルールのように見えるではないでしょう。変数をレジスタに保持できる場合は、置換によってコードが小さくなります。とにかく変数をRAMに保持する必要がある場合は、置換によってコードが大きくなります。置換をコンパイラーに任せるかどうかの問題は、どちらの場合もコードの改善につながります。
register
コンパイラーがアドレスの取得を許可するかどうかに関係なく、グローバル変数の修飾を許可すると、グローバル変数を使用するインライン関数がループで繰り返し呼び出される場合に、いくつかの優れた最適化が可能になります。ループの繰り返しの間、その変数をレジスターに保持する他の方法は考えられません-できますか?
私はそれが最適化に使用されるが、どの標準でも明確に定義されていないことを読みました。
実際にはそれがされ明確にC標準で定義されました。N1570ドラフトセクション6.7.1パラグラフ6を引用してください(他のバージョンでも同じ文言があります):
ストレージクラス指定子
register
を使用したオブジェクトの識別子の宣言は、オブジェクトへのアクセスが可能な限り高速であることを示唆しています。そのような提案が効果的である範囲は、実装によって定義されます。
単項&
演算子はregister
、で定義されたオブジェクトには適用register
できません。また、外部宣言では使用できません。
register
-qualifiedオブジェクトに固有のその他の(かなりあいまいな)ルールがいくつかあります。
register
動作が未定義になります。register
ですが、そのようなオブジェクトを使用して何かを行うことはできません(配列へのインデックス付けには、その初期要素のアドレスを取得する必要があります)。_Alignas
指定子(C11で新しい)は、オブジェクトに適用されなくてもよいです。va_start
マクロに渡されるパラメーター名がregister
-qualifiedの場合、動作は未定義です。他にもいくつかあるかもしれません。規格のドラフトをダウンロードし、興味がある場合は「登録」を検索してください。
名前が示すように、の本来の意味はregister
、オブジェクトをCPUレジスタに格納することを要求することでした。しかし、コンパイラの最適化の改善により、これはあまり役に立たなくなってきました。C標準の最新バージョンはCPUレジスタを参照していません。なぜなら、それらはもはやそのようなものがあることを前提とする必要はないからです(レジスタを使用しないアーキテクチャーがあります)。一般的な知識としてはregister
、オブジェクト宣言に適用すると、コンパイラー自身のレジスター割り当てに干渉するため、生成されたコードが悪化する可能性が高くなります。それが役立ついくつかのケースがまだあるかもしれません(たとえば、変数がアクセスされる頻度が本当にわかっていて、最新の最適化コンパイラが把握できるよりも知識が優れている場合など)。
の主な具体的な効果はregister
、オブジェクトのアドレスを取得しようとする試みを防ぐことです。これは最適化のヒントとしては特に有用ではありません。ローカル変数にのみ適用でき、最適化コンパイラーはそのようなオブジェクトのアドレスが取得されていないことを自分で確認できるためです。
register
あなたが考えていることなら、修飾された配列オブジェクトはありません。
register
キーワードは、そのような変数のアドレスを取得することが合法である場合に有用な目的に役立ちますが、アドレスが取得されたときに変数を一時変数にコピーし、一時変数から再読み込みすることによってセマンティクスに影響がない場合に限られます。次のシーケンスポイント。これにより、コンパイラーは、アドレスが取得される任意の場所でフラッシュされる場合、変数がすべてのポインターアクセスにわたってレジスターに安全に保持されると想定できます。
ストーリータイム!
言語としてのCは、コンピューターの抽象概念です。それはあなたがコンピュータをすることに関して、メモリを操作すること、数学をすること、物事を印刷することなどをすることを可能にします。
しかし、Cは単なる抽象概念です。そして最終的に、それはあなたから抽出するものはアセンブリ言語です。アセンブリは、CPUが読み取る言語であり、それを使用する場合、CPUの観点から物事を行います。CPUは何をしますか?基本的に、メモリから読み取り、計算を行い、メモリに書き込みます。CPUはメモリ内の数値を計算するだけではありません。まず、レジスタと呼ばれるCPU内のメモリからメモリに数値を移動する必要があります。この数値に必要なことをすべて実行したら、通常のシステムメモリに戻すことができます。システムメモリを使用する理由 レジスターの数は限られています。最新のプロセッサーでは約100バイトしか取得できず、古い人気のあるプロセッサーはさらに制限されていました(6502には無料で使用できる3つの8ビットレジスターがありました)。したがって、平均的な数学演算は次のようになります。
load first number from memory
load second number from memory
add the two
store answer into memory
その多くは...数学ではありません。これらのロードおよびストア操作は、処理時間の最大半分を占める可能性があります。Cはコンピューターを抽象化したものであり、プログラマーがレジスターを使用したりジャグリングしたりする心配をなくしました。また、コンピューター間で数とタイプが異なるため、Cはコンパイラーにのみレジスター割り当ての責任を負います。1つの例外を除いて。
変数を宣言するとき register
、あなたはコンパイラに「そうです、私はこの変数が頻繁に使用されるか、または短命になることを意図しています。私があなただったら、私はそれをレジスタに保持しようと思います。」C標準では、コンパイラは実際には何もする必要がないと言われています。これは、C標準がコンパイル対象のコンピュータを認識していないためです。これは、動作するために3つのレジスタすべてが必要な上記の6502のようなものである可能性があります。 、そしてあなたの番号を保持するための予備のレジスタはありません。ただし、アドレスを取得できないと表示されている場合は、レジスタにアドレスがないためです。彼らはプロセッサの手です。コンパイラーはアドレスを指定する必要がないため、アドレスをまったく指定できないため、いくつかの最適化がコンパイラーに開かれています。たとえば、常にレジスターに番号を保持することができます。それはしません コンピューターのメモリのどこに保存されているかを心配する必要があります(再度取得する必要はありません)。それを別の変数にパンしたり、別のプロセッサに渡したり、場所を変更したりすることもできます。
tl; dr:多くの計算を行う短期間の変数。一度に多くを宣言しないでください。
コンパイラの洗練されたグラフの色付けアルゴリズムをいじっています。これは、レジスタ割り当てに使用されます。まあ、ほとんど。それはコンパイラーへのヒントとして機能します-それは本当です。ただし、レジスタ変数のアドレスを取得することは許可されていないため、全体として無視されません(コンパイラーは、今すぐに、別の方法で動作しようとします)。ある意味でそれを使用しないように言っています。
キーワードは長い間使用されていました。人差し指を使用してすべてをカウントできるレジスターが非常に少ない場合。
ただし、すでに述べたように、廃止されたからといって使用できないという意味ではありません。
比較のために(任意の実世界の目的なし)ちょっとデモ:削除するときにregister
、各変数の前にキーワードを、コードのこの作品は、私のi7の(GCC)に3.41秒を要して register
0.7秒で同じコードが完了する。
#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です
レジスターはコンパイラーに、この変数は変数の使用に使用できるいくつかのレジスターの1つにそのストレージを正当化するのに十分な量の書き込み/読み取りがあると信じていることをコンパイラーに通知します。レジスターからの読み取り/書き込みは、通常、より高速であり、より小さなオペコードセットを必要とします。
現在のところ、ほとんどのコンパイラーのオプティマイザーは、その変数にレジスターを使用する必要があるかどうか、およびどのくらいの期間使用するかを決定するよりも優れているため、あまり役に立ちません。
70年代、C言語の冒頭にregisterキーワードが導入され、プログラマーがコンパイラーにヒントを与えることができるようになり、変数が非常に頻繁に使用されること、およびその値をプロセッサーの内部レジスターの1つに保持します。
現在、オプティマイザーは、レジスターに保持される可能性が高い変数を決定するプログラマーよりもはるかに効率的であり、オプティマイザーは常にプログラマーのヒントを考慮に入れていません。
そのため、多くの人々は、registerキーワードを使用しないことを誤って推奨しています。
理由を見てみましょう!
registerキーワードには関連する副作用があります。レジスタタイプの変数を参照(アドレスを取得)することはできません。
レジスタを使用しないように他の人に助言する人々は、これを追加の引数として誤って受け取ります。
ただし、レジスター変数のアドレスを取得できないことを知っているという単純な事実により、コンパイラー(およびそのオプティマイザー)は、この変数の値がポインターを介して間接的に変更できないことを知ることができます。
命令ストリームの特定の時点で、レジスタ変数にプロセッサのレジスタに割り当てられた値があり、別の変数の値を取得するためにレジスタが使用されていない場合、コンパイラは再ロードする必要がないことを認識しますそのレジスター内の変数の値。これにより、費用のかかる無駄なメモリアクセスを回避できます。
独自のテストを行うと、最も内側のループでパフォーマンスが大幅に向上します。
最適化フラグを使用しないgcc 9.3 asm出力(この回答のすべては、最適化フラグなしの標準コンパイルを指します):
#include <stdio.h>
int main(void) {
int i = 3;
i++;
printf("%d", i);
return 0;
}
.LC0:
.string "%d"
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], 3
add DWORD PTR [rbp-4], 1
mov eax, DWORD PTR [rbp-4]
mov esi, eax
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
mov eax, 0
leave
ret
#include <stdio.h>
int main(void) {
register int i = 3;
i++;
printf("%d", i);
return 0;
}
.LC0:
.string "%d"
main:
push rbp
mov rbp, rsp
push rbx
sub rsp, 8
mov ebx, 3
add ebx, 1
mov esi, ebx
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
add rsp, 8
pop rbx
pop rbp
ret
これは、強制的ebx
に計算に使用されることを意味します。つまり、呼び出し先に保存されるため、スタックにプッシュし、関数の最後に復元する必要があります。register
より多くのコード行と1つのメモリの書き込みと1つのメモリの読み取りを生成します(実際には、計算がで行われたesi
場合、これは0 R / Wに最適化された可能性があり、これはC ++を使用して行われconst register
ます)。使用しないregister
と、2回の書き込みと1回の読み取りが発生します(ただし、ストアからロードへの転送は読み取りで発生します)。これは、値がスタックに直接存在して更新される必要があるため、正しい値をアドレス(ポインター)で読み取ることができるためです。register
この要件はなく、ポイントすることもできません。const
そしてregister
基本的に反対でvolatile
あり、使用していますvolatile
ファイルとブロックのスコープでのconst最適化とブロックスコープでのregister
最適化をオーバーライドします。const register
またregister
、constはブロックスコープでCに対して何も実行しないため、同一の出力を生成し、register
最適化のみが適用されます。
clangではregister
無視されますが、const
最適化は引き続き行われます。