2013-04-30 9 views
5

私は、次の機能を有する:これはallocaを使う良い理由ですか?

double 
neville (double xx, size_t n, const double *x, const double *y, double *work); 

xyに格納されているn点を用いてxxでラグランジュ補間を行います。 workアレイのサイズは2 * nです。これは多項式補間であるため、nは〜5の球場にあり、非常にまれに10を超えることはほとんどありません。

この関数は積極的に最適化されており、きついループで呼び出されるはずです。プロファイリングでは、ループ内のワーク配列を割り当てるヒープが悪いことが示唆されています。残念ながら、私はこれを関数に似たクラスにパッケージ化し、クライアントはワーク配列を認識しなくてはなりません。の可変メンバーとして作業配列を格納することも可能だったでしょう

template <size_t n> 
struct interpolator 
{ 
    double operator() (double xx) const 
    { 
     std::array<double, 2 * n> work; 
     size_t i = locate (xx); // not shown here, no performance impact 
           // due to clever tricks + nice calling patterns 

     return neville (xx, n, x + i, y + i, work.data()); 
    }   

    const double *x, *y; 
}; 

は今のところ、私はwork配列の動的割り当てを避けるために、学位やstd::arrayのテンプレート整数の引数を使用しますしかし、operator()は、複数のスレッドによって同時に使用されることになっています。このバージョンはコンパイル時にnを知っていればOKです。

実行時にnパラメータを指定する必要があります。 allocaを使用した場合

double operator() (double xx) const 
{ 
    auto work = static_cast<double*> (alloca (n * sizeof (double))); 
    ... 

いくつかの鐘リング:私はこのような何かについて疑問に思って、私はもちろん、とにかくそれが度100を使用することは非常に愚かだ(オーバーフローするalloca呼び出しを避けるためにn上のキャップを持っているつもりです多項式補間)。

しかし私は、アプローチにはかなりunconfortableだ:

  • 私はallocaのいくつかの明白な危険が足りませんか?
  • ここでヒープ割り当てを避けるより良い方法はありますか?
+2

Cでこの機能を書いてC99 VLAを使うことはできませんか? –

+1

@KonradRudolph 'double neville(double xx、size_t n、const double * x、const double * y、double * work);' - この関数を書くには演算子のオーバーロードが必要ですか?うわー、私は知らなかった! –

+2

@ H2CO3ヘヒェ、私を捕まえた。さて、私の最後の議論は、CとC++コードのリンクを強く嫌うということです。もちろん、実際には問題はありません(正しく行われれば!そして、私はそれを間違ってしまい、多くの苦痛を与えた多くのCライブラリに遭遇しました)。しかし、その後、私はallocaでVLAを使うことには何のメリットもありませんが、何かが欠けているのでしょうか? –

答えて

5

しかし私は、アプローチにはかなりunconfortableだ:

  • 私はallocaをいくつかの明白な危険が足りませんか?

あなたは1つの本当の危険を指摘した:スタックオーバーフローの動作はallocaのために定義されていません。さらに、allocaは実際には標準化されていません。たとえば、Visual C++の代わりに_allocaGCC by default defines it as a macroがあります。しかし、この問題は、いくつかの既存の実装の周りに薄いラッパーを提供することによって、かなり簡単に回避することができます。

  • ここでヒープ割り当てを避けるより良い方法はありますか?

実際にはありません。 C++ 14には、(潜在的に!)スタックに可変長配列型が割り当てられます。しかし、それまでにstd::arrayが適切でないと考えるときは、あなたのような場合はallocaに行ってください。

マイナーニックネーム:あなたのコードには戻り値のキャストがありませんalloca。コンパイルすべきではありません。

+1

私は匿名のdownvotesによって傷ついています。誰も私の傷を和らげる気に? –

+0

downvotesについて推測してみましょう:非標準機能の使用を示唆しています(オリジナル版では特に言及していませんが、基本的にはC++の基本的なCライブラリ関数の使用を提案すると純粋なdownvotesを引き付ける可能性があります。 – hyde

2

スタックメモリの使用には、常にメモが追加されます。あなたが指摘しているように、スタックは、そのスペースがなくなると、有限のサイズとかなり重大な誤動作をします。ガードページがあると、スタックオーバーフローがうまくクラッシュすることがありますが、一部のプラットフォームやスレッド環境では、サイレントな破損(悪い)やセキュリティの問題(悪化)が生じることがあります。

またそのスタック割り当てを覚えてmalloc、(それは、スタック・ポインタ・レジスタからわずか減算です)に比べて非常に高速です。しかし、そのメモリのを使用することはできません。スタックフレームを大量に押し込むことによる副作用は、呼び出すリーフ関数のキャッシュラインがもはや常駐していないことです。したがって、そのメモリを使用すると、キャッシュラインを排他的(MESIの意味で)の状態に戻すためにSMP環境に出る必要があります。 SMPバスは、L1キャッシュよりもはるかに制約の厳しい環境です。これを回避するためにスタックフレームをスパムしているのであれば、実際のスケーラビリティの問題になる可能性があります。

また、gccとclang(そしてIntelのコンパイラはどちらも)C99の可変長配列構文をC++拡張としてサポートしていることに注意してください。 libc alloca()ルーチンを実際に呼び出す必要はありません。

最後に、mallocが本当に遅くないことに注意してください。数十キロバイト以上の領域の単一バッファを扱っている場合、あなたが行う作業をサービスするために必要なメモリ帯域幅は、mallocからのオーバヘッドを溜めてしまいます。

基本的に:alloca()は可愛く、その用途がありますが、必要なことを証明する準備ができていない限り、従来の割り当てに固執する必要はありません。

+1

キャッシュアソシエイビリティについて特定の前提を設定していますか?なぜ私は、ダイナミックメモリがより少ないページをキャッシュに持ってくるのか見ていないので、ヒープの内部データ構造にアクセスする必要があるため、実際にはもっと多くのページに触れるはずです。したがって、リーフ関数によって使用されるページの追い出しを引き起こす可能性は少なくないでしょう。キャッシュに入っていないページが気になる場合は、その理由がわかりません。大量のスタック割り当てを頻繁に使用するプログラムでは、それらのスタックページはキャッシュ内で暖かくなります。 –

+0

関数 'neville'は小さく、誰も呼び出しません。私の実際の 'operator()'( 'std :: array')の実行時間を3倍にするたびにワーク配列をmallocします。また、 'work'のサイズは最大で数十バイトです。しかし、洞察に感謝します。 –

+0

ベン:キャッシュフットプリントではなく、キャッシュラインの状態です。 L1キャッシュラインに格納またはロードするには、ラインがE状態である限り、ローカルCPUの外部にトラフィックは必要ありません。したがって、これらの行の上にリーフ関数を呼び出すことは高速で、スタック上に14kを落とした後に同じ関数を呼び出すことはできません。その代わりに、CPUは最初にすべての他のCPUに操作をブロードキャストして、スヌープ論理がそれを見ることができるようにする必要があります。急速に呼ばれるリーフ関数の場合、これは自明ではありません。 –

1

これはどう:

double operator() (double xx) const 
{ 
    double work_static[STATIC_N_MAX]; 
    double* work = work_static; 
    std::vector<double> work_dynamic; 

    if (n > STATIC_N_MAX) { 
     work_dynamic.resize(n); 
     work = &work_dynamic[0]; 
    } 

    ///... 

ノー非ポータブル機能、例外安全、およびnが大きすぎると優雅に低下します。もちろん、work_staticstd::arrayにすることもできますが、その点でどのようなメリットがあるのか​​分かりません。

+0

時々私は明らかに見逃しています...私は 'n'の値を20(またはいくつかのプリプロセッサ定数、私の実際の関数は' y'パラメータにテンプレート化されているので、完全なソースコードが利用できるように)よりも大きくすることを禁止していると考えています。スタックに '320'バイトを割り当ててもパフォーマンスが低下しない場合は、これは有効です。 –

+0

@AlexandreC .:この醜さを包み込む「小さな文字列最適化」を持つ 'std :: vector'実装を想像することができます。 –

+0

@AlexandreC。ヒープallocがボトルネックかもしれないと思うならば、スタック内の不必要に大きな配列のCPUキャッシュへの影響は望ましくないかもしれません。しかし、実際のマルチスレッド負荷下でのベンチマーク。 – hyde

関連する問題