4

ポインタ変換は高価だと考えられていますか? (たとえば、ポインター/アドレスを変換するのに必要な CPU サイクルの数)、特に非常に頻繁に実行する必要がある場合、たとえば (頻度のスケールを示す単なる例であり、この特定のケースにはより良い方法があることを知っています) :

unsigned long long *x;
/* fill data to x*/

for (int i = 0; i < 1000*1000*1000; i++)
{

    A[i]=foo((unsigned char*)x+i);

};
4

4 に答える 4

9

(例: ポインタ/アドレスの変換にかかる CPU サイクル数)

ほとんどの機械語言語では、ポインターの「型」は 1 つしかないため、それらの間の変換に費用はかかりません。C++ 型は実際にはコンパイル時にのみ存在することに注意してください。

本当の問題は、この種のコードが厳密なエイリアシング ルールに違反する可能性があることです。これについては別の場所で詳しく読むことができますが、基本的に、コンパイラは未定義の動作によって誤ったコードを生成するか、保守的な仮定を強いられて低速なコードを生成します。char*( and friends は、未定義の動作部分から多少免除されていることに注意してください)

オプティマイザーは、多くの場合、ポインターの存在下で変数について保守的な仮定を行う必要があります。たとえば、変数 x の値が 5 であることを知っている定数伝播プロセスは、別の変数 (たとえば、*y = 10) への代入後にこの情報を使用し続けることができません。これは、*y が別名である可能性があるためです。 xの。これは、y = &x のような代入の後に発生する可能性があります。

*y への代入の影響として、x の値も変更されるため、x が 5 であるという情報を *y = 10 に続くステートメントに伝搬すると、間違っている可能性があります (*y が実際に x のエイリアスである場合)。 )。ただし、ポインターに関する情報がある場合、定数伝播プロセスは次のようなクエリを作成できます。次に、答えがノーの場合、x = 5 は安全に伝播できます。エイリアシングによって影響を受けるもう 1 つの最適化は、コードの並べ替えです。x が *y によってエイリアス化されていないとコンパイラが判断した場合、x の値を使用または変更するコードは、割り当て *y = 10 の前に移動できます。これにより、スケジューリングが改善されるか、より多くのループ最適化を実行できるようになります。

このような最適化を予測可能な方法で有効にするために、C プログラミング言語 (新しい C99 版を含む。セクション 6.5、パラグラフ 7 を参照) の ISO 標準では、異なる型のポインターが同じものを参照することは (いくつかの例外を除いて) 違法であると規定しています。メモリの場所。「厳密なエイリアシング」として知られるこの規則により、パフォーマンスが大幅に向上する場合があります[1]が、そうでなければ有効なコードを壊してしまうことが知られています。いくつかのソフトウェア プロジェクトは、C99 標準のこの部分に意図的に違反しています。たとえば、Python 2.x は参照カウントを実装するためにそうしました[2]。この最適化を可能にするために、Python 3 の基本的なオブジェクト構造体に変更を加える必要がありました。Linux カーネルがこれを行うのは、厳密なエイリアシングがインライン コードの最適化で問題を引き起こすためです。[3] そのような場合、gcc でコンパイルすると、オプション -fno-strict-aliasing は、予期しないコードを生成する可能性のある望ましくない最適化を防ぐために呼び出されます。[編集]

http://en.wikipedia.org/wiki/Aliasing_(computing)#Conflicts_with_optimization

厳密なエイリアシング規則とは何ですか?

于 2013-01-23T11:36:30.630 に答える
7

遭遇する可能性が高いアーキテクチャでは、すべてのポインター型が同じ表現を持っているため、同じアドレスを表す異なるポインター型間の変換に実行時間のコストはかかりません。これは、C のすべてのポインター変換に適用されます。

C++ では、一部のポインター変換にはコストがかかり、一部はコストがかかりません。

  • reinterpret_castand const_cast(または、問題のような同等の C スタイルのキャスト) とへの変換またはからの変換はvoid*、コストなしでポインター値を単純に再解釈します。
  • 基本クラスへのポインターと派生クラスへのポインターの間の変換 (暗黙的、またはstatic_cast同等の C スタイル キャストによる) では、複数の基本クラスがある場合、ポインター値に固定オフセットを追加する必要がある場合があります。
  • dynamic_cast指すオブジェクトの動的タイプに基づいてポインター値を検索するために、重要な量の作業が行われます。

歴史的に、一部のアーキテクチャ (PDP-10 など) では、バイトへのポインタとワードへのポインタの表現が異なっていました。そこでは、変換にいくらかのランタイム コストがかかる場合があります。

于 2013-01-23T11:59:10.380 に答える
1
unsigned long long *x;
/* fill data to x*/

for (int i = 0; i < 1000*1000*1000; i++)
{

    A[i]=foo((unsigned char*)x+i); // bad cast

}

マシンはメモリ アドレス、データ、およびコードしか認識していないことを忘れないでください。他のすべて (型など) はコンパイラ (プログラマーを支援する) だけが知っており、すべてのポインター演算を行い、コンパイラーだけが各型のサイズを知っています..などなど。

実行時には、変換が実行時に行われないため、あるポインター型を別のポインター型に変換するために無駄なマシン サイクルはありません。すべてのポインターは、4 バイト長 (32 ビット マシン上) として扱われ、それ以上でもそれ以下でもありません。

于 2013-01-23T11:37:30.773 に答える
0

それはすべて、基盤となるハードウェアに依存します。

ほとんどマシン アーキテクチャでは、すべてのポインターはバイト ポインターであり、バイト ポインターとバイト ポインターの間の変換はノーオペレーションです。一部のアーキテクチャでは、状況によってはポインタ変換に特別な操作が必要になる場合があります (たとえば、ワード ベースのアドレスを処理するマシンがあり、ワード ポインタをバイト ポインタに、またはその逆に変換するには特別な操作が必要になります) 。

さらに、これは一般に安全でない手法です。コンパイラは、実行中の動作に対して健全性チェックを実行できず、予期しないデータを上書きしてしまう可能性があるためです。

于 2013-01-23T11:51:36.480 に答える