web-dev-qa-db-ja.com

constはC / C ++でどのような最適化を提供しますか?

読みやすくするために、参照またはポインターでパラメーターを渡す場合は、可能であればconstキーワードを使用する必要があることを知っています。引数が定数であることを指定した場合にコンパイラーが実行できる最適化はありますか?

いくつかのケースが考えられます:

関数パラメーター:

定数リファレンス:

void foo(const SomeClass& obj)

定数SomeClassオブジェクト:

void foo(const SomeClass* pObj)

SomeClassへの定数ポインター:

void foo(SomeClass* const pObj)

変数宣言:

const int i = 1234

関数宣言:

const char* foo()

それぞれが提供するコンパイラーの最適化の種類(ある場合)

56
UnTraDe

[読者は、この投稿の大部分がハーブサッターの記事から引用されていることに注意してください- http://www.gotw.ca/gotw/081.htm -OPの帰属なし。]

CASE_1:-

プログラムでconstを宣言すると、

int const x = 2;

コンパイラは、この変数にストレージを提供せずに、シンボルテーブルに追加することで、このconstを最適化できます。したがって、後続の読み取りでは、メモリから値をフェッチする命令ではなく、シンボルテーブルへの間接参照が必要です。

注:-以下のようなことをする場合:-

const int x = 1;
const int* y = &x;

この場合、コンパイラは'x'にスペースを割り当てます。したがって、この場合、その程度の最適化は不可能です。

関数パラメーターに関してconstは、パラメーターが関数内で変更されないことを意味します。私の知る限り、constを使用してもパフォーマンスが大幅に向上することはなく、正確性を確保するための手段です。

CASE_2:-

「パラメーターや戻り値をconstとして宣言すると、コンパイラーがより最適なコードを生成するのに役立ちますか?」

  const Y& f( const X& x )
  {
    // ... do something with x and find a Y object ...
    return someY;
  }

Ques =>コンパイラは何を改善できますか?

=>パラメータまたは戻り値のコピーを回避できますか?

いいえ、引数はすでに参照渡しされているためです。

=> xまたはsomeYのコピーを読み取り専用メモリに入れることができますか?

いいえ、xとsomeYは両方ともその範囲外に住んでおり、外の世界から来たり、外の世界に与えられたりします。 someYがf()自体内で動的に動的に割り当てられた場合でも、そのYとその所有権は呼び出し元に渡されます。

Ques => f()の本体内に表示されるコードの可能な最適化はどうですか? constのために、コンパイラはf()の本体に対して生成するコードを何らかの形で改善できますか?

Constメンバー関数を呼び出しても、コンパイラはオブジェクトxまたはオブジェクトsomeYのビットが変更されないことを想定できません。さらに、追加の問題があります(コンパイラーがグローバル最適化を実行しない限り):コンパイラーは、xおよび/またはsomeYと同じオブジェクトをエイリアスする非const参照が他のコードにないこと、およびそのような同じオブジェクトへの非const参照は、f()の実行中に偶然使用される場合があります。また、コンパイラは、xとsomeYが単なる参照である実際のオブジェクトが実際にconstで宣言されているかどうかさえ知らない場合があります。

CASE_3:-

  void f( const Z z )
  {
    // ...
  }

Ques =>これに最適化はありますか?

はい、コンパイラーはzが本当にconstオブジェクトであることを知っているため、グローバルな分析がなくても、いくつかの便利な最適化を実行できます。たとえば、f()の本体にg(&z)のような呼び出しが含まれている場合、コンパイラは、g(の呼び出し中にzの非可変部分が変更されないことを確認できます。 )

28
ravi

答えを出す前に、constを使用するかどうかの理由は、コンパイラの最適化よりも、プログラムの正確性と他の開発者にとっての明確さのためであると強調したいと思います。つまり、パラメータを作成するとconstはメソッドがそのパラメータを変更しないことを文書化し、メンバ関数を作成するとconstはそのメンバがメンバであるオブジェクトを変更しないことを文書化します(少なくとも、他のconstメンバー関数からの出力を論理的に変更する方法ではありません)。これを行うと、たとえば、開発者はオブジェクトの不要なコピーの作成を回避できます(元のオブジェクトが破壊または変更されることを心配する必要がないため)または不要なスレッドの同期を回避します(たとえば、すべてのスレッドが単に読み取り、問題のオブジェクトを変更しないでください)。

最適化に関しては、少なくとも理論上、コンパイラは、標準C++コードを壊す可能性がある特定の非標準の仮定を可能にする最適化モードではありますが、次のことを考慮してください。

for (int i = 0; i < obj.length(); ++i) {
   f(obj);
}

length関数がconstとしてマークされているが、実際には高価な操作であるとします(実際にO(n) timeではなくO(1) time)。関数fconst参照によってパラメーターを取る場合、コンパイラーはこのループを次のように最適化できます。

int cached_length = obj.length();
for (int i = 0; i < cached_length; ++i) {
   f(obj);
}

...関数fがパラメーターを変更しないという事実により、オブジェクトが変更されていない場合、length関数が毎回同じ値を返すことが保証されるためです。ただし、fが可変参照によってパラメーターを取るように宣言されている場合、lengthが変更された可能性があるため、ループの各反復でfを再計算する必要があります。値の変化を生成する方法でオブジェクト。

コメントで指摘されているように、これはいくつかの追加の警告を想定しており、追加の仮定を行うことができる非標準モードでコンパイラを呼び出す場合にのみ可能です(constメソッドは厳密に入力の関数であり、最適化ではコードがconst_castは、const参照パラメーターを可変参照に変換します)。

12

関数パラメーター:

constは参照メモリにとって重要ではありません。オプティマイザーの後ろに手を縛るようなものです。

foo内の別の関数(たとえば、void bar())を呼び出しますが、これには目に見える定義がありません。オプティマイザーには、barfooに渡される関数パラメーターを変更したかどうかを知る方法がないため、制限があります(たとえば、グローバルメモリへのアクセスを介して)。メモリを外部で変更し、エイリアシングを発生させる可能性があるため、この領域のオプティマイザーに大きな制限が生じます。

質問はしませんでしたが、オプティマイザーにはconstオブジェクトが保証されているため、関数パラメーターのconstvaluesは最適化を許可します。もちろん、そのパラメーターをコピーするコストは、オプティマイザーの利点よりもはるかに高い場合があります。

参照: http://www.gotw.ca/gotw/081.htm


変数宣言:_const int i = 1234_

これは、宣言されている場所、作成されるタイミング、およびタイプによって異なります。このカテゴリは、主にconst最適化が存在する場所です。 constオブジェクトまたは既知の定数を変更することは未定義であるため、コンパイラーはいくつかの最適化を行うことができます。未定義の動作を呼び出さないことを前提としているため、いくつかの保証が導入されます。

_const int A(10);
foo(A);
// compiler can assume A's not been modified by foo
_

明らかに、オプティマイザーは変更されない変数を識別することもできます。

_for (int i(0), n(10); i < n; ++i) { // << n is not const
 std::cout << i << ' ';
}
_

関数宣言:const char* foo()

重要ではありません。参照されるメモリは外部で変更できます。 fooによって返される参照変数が表示されている場合、オプティマイザーは最適化を行うことができますが、関数の戻り値型にconstが存在するかどうかは関係ありません。

繰り返しますが、const値またはオブジェクトは異なります。

_extern const char foo[];_

6
justin

Constの正確な効果は、使用されるコンテキストごとに異なります。変数の宣言中にconstが使用される場合、それは物理的にconstであり、読み取り専用メモリに潜在的に常駐します。

const int x = 123;

Const-nessを捨てようとするのは未定義の動作です:

Const_castは、ポインターまたは参照からconstnessまたはvolatilityを削除できますが、結果のポインターまたは参照を使用して、constとして宣言されたオブジェクトへの書き込みまたはvolatileとして宣言されたオブジェクトへのアクセスは、未定義の動作を呼び出します。 cppreference/const_cast

そのため、この場合、コンパイラはxの値が常に123。これにより、いくつかの最適化の可能性が開きます(定数の伝播)

機能については、別の問題です。仮定:

void doFancyStuff(const MyObject& o);

関数doFancyStuffは、oを使用して次のいずれかを実行できます。

  1. オブジェクトを変更しないでください。
  2. 定数を捨ててから、オブジェクトを変更します
  3. 変更 mutable MyObjectのデータメンバー

ConstとしてdeclaredであったMyObjectのインスタンスを使用して関数を呼び出す場合、#2で未定義の動作を呼び出すことに注意してください。

第一人者の質問:次は未定義の動作を呼び出しますか?

const int x = 1;
auto lam = [x]() mutable {const_cast<int&>(x) = 2;};
lam();
3
Stefan

SomeClass* const pObjは、ポインタ型の定数オブジェクトを作成します。そのようなオブジェクトを変更する安全な方法は存在しないため、コンパイラは、たとえば、アドレスが取得された場合でも、1回のメモリ読み取りでレジスタにキャッシュできます。

他のものは、特に最適化を有効にしませんが、型のconst修飾子はオーバーロードの解決に影響し、場合によっては異なる高速な関数が選択されます。

3
Ben Voigt