ベクター大好きです。彼らは気の利いた、速いです。しかし、valarray と呼ばれるものが存在することは知っています。ベクトルの代わりに valarray を使用するのはなぜですか? valarray には構文糖衣があることは知っていますが、それ以外に、いつ役立つのでしょうか?
9 に答える
valarray
間違った時期に間違った場所で生まれた孤児のようなものです。これは最適化の試みであり、特に Cray のようなベクトル プロセッサなど、作成時に負荷の高い数学に使用されていたマシンに特化したものです。
ベクトル プロセッサの場合、一般的にやりたいことは、1 つの操作を配列全体に適用してから、次の操作を配列全体に適用するということでした。必要な操作がすべて完了するまで、これを繰り返します。
ただし、かなり小さな配列を扱っている場合を除き、キャッシングではうまく機能しない傾向があります。最近のほとんどのマシンでは、(可能な範囲で) 配列の一部をロードし、その部分に対してすべての操作を実行してから、配列の次の部分に移動することが一般的に好まれます。
valarray
また、エイリアシングの可能性を排除することも想定されています。これにより、(少なくとも理論的には) レジスターに値をより自由に格納できるため、コンパイラーの速度が向上します。ただし、実際には、実際の実装でこれが大幅に活用されているかどうかはまったくわかりません。私はこれはどちらかというとニワトリが先か卵が先かというような問題だと思っています。コンパイラのサポートがなければ普及しませんでした。普及していない限り、コンパイラをサポートするために苦労する人は誰もいないでしょう。
valarray で使用する補助クラスの (文字通り) 困惑させるような配列もあります。slice
、slice_array
、gslice
およびを取得して、gslice_array
の断片を操作しvalarray
、多次元配列のように動作させます。また、操作を「マスク」することもできますmask_array
(たとえば、x から y にアイテムを追加しますが、z がゼロでない位置にのみ追加します)。を簡単に使用するにはvalarray
、これらの補助クラスについて多くのことを学ばなければなりません。その中には非常に複雑なものもあり、(少なくとも私には)十分に文書化されているものはありません。
要するに、それは素晴らしい瞬間を持ち、いくつかのことを非常にきれいに行うことができますが、それが不明瞭である (そしてほぼ確実に残る) いくつかの非常に正当な理由もあります.
編集(8年後の2017年):上記のいくつかは、少なくともある程度時代遅れになっています. 一例として、Intel はコンパイラ用に最適化されたバージョンの valarray を実装しています。Intel Integrated Performance Primitives (Intel IPP) を使用してパフォーマンスを向上させます。正確なパフォーマンスの改善は間違いなくさまざまですが、単純なコードを使用した簡単なテストでは、valarray
.
そのため、C++ プログラマーが膨大な数で C++ を使い始めるとは完全には確信していませんがvalarray
、速度が向上する状況は少なくともいくつかあります。
valarray (値配列) は、Fortran の速度の一部を C++ にもたらすことを目的としています。ポインターの valarray を作成しないので、コンパイラーはコードに関する仮定を作成し、より適切に最適化できます。(Fortran が非常に高速である主な理由は、ポインター型がないため、ポインターのエイリアシングが発生しないためです。)
valarray には、かなり簡単な方法でそれらをスライスできるクラスもありますが、標準のその部分はもう少し作業が必要になる可能性があります。それらのサイズ変更は破壊的であり、C++ 11 以降の反復子を持つ反復子がありません。
したがって、作業しているのが数値であり、利便性がそれほど重要ではない場合は、valarray を使用します。それ以外の場合は、ベクトルの方がはるかに便利です。
C++98 の標準化中に、valarray はある種の高速な数学的計算を可能にするように設計されました。ただし、その頃、Todd Veldhuizen が式テンプレートを発明してblitz++を作成し、同様のテンプレートメタ技術が発明されたため、標準がリリースされる前に valarray はほとんど時代遅れになりました。valarray の最初の提案者である IIRC は、標準化の途中でそれを放棄しました。
ISTR は、標準から削除されなかった主な理由は、問題を徹底的に評価し、削除するための提案を書くのに時間をかけなかったことにあると述べています。
ただし、これらはすべて漠然とした伝聞であることを覚えておいてください。これを一粒の塩で取り、誰かがこれを修正または確認することを願っています.
valarray には構文糖衣があることは知っています
std::valarrays
私は、シンタックスシュガーの邪魔になるものはあまりないと言わざるを得ません。構文は異なりますが、その違いを「シュガー」とは呼びません。APIは奇妙です。The C++ Programming Languagestd::valarray
の sに関するセクションでは、この異常な API と、s は高度に最適化されていることが期待されるため、使用中に表示されるエラー メッセージはおそらく非直感的であるという事実について言及しています。std::valarray
好奇心から、約 1 年前に とstd::valarray
対戦しstd::vector
ました。コードや正確な結果はもうありません (自分で書くのは難しくありませんが)。GCC を使用すると、単純な計算に使用すると少しパフォーマンスが向上しましstd::valarray
たが、標準偏差を計算する実装ではそうではありませんでした (もちろん、数学に関する限り、標準偏差はそれほど複雑ではありません)。 大規模なアイテムの各アイテムに対する操作は、s(注、musiphilstd::vector
に対する操作よりもキャッシュの方が優れていると思いstd::valarray
ます。vector
からのアドバイスに従って、とからほぼ同じパフォーマンスを得ることができましたvalarray
)。
結局、std::vector
メモリ確保やテンポラリオブジェクト作成などに気をつけながら使うことにしました。
両方とも、連続したブロックにデータstd::vector
をstd::valarray
格納します。ただし、それらは異なるパターンを使用してそのデータにアクセスします。さらに重要なことに、API for は API for とstd::valarray
は異なるアクセス パターンを推奨しますstd::vector
。
標準偏差の例では、特定のステップで、コレクションの平均と、各要素の値と平均の差を見つける必要がありました。
のためにstd::valarray
、私は次のようなことをしました:
std::valarray<double> original_values = ... // obviously I put something here
double mean = original_values.sum() / original_values.size();
std::valarray<double> temp(mean, original_values.size());
std::valarray<double> differences_from_mean = original_values - temp;
std::slice
私はorの方が賢かったかもしれませんstd::gslice
。もう5年以上経ちます。
のためstd::vector
に、私は次の行に沿って何かをしました:
std::vector<double> original_values = ... // obviously, I put something here
double mean = std::accumulate(original_values.begin(), original_values.end(), 0.0) / original_values.size();
std::vector<double> differences_from_mean;
differences_from_mean.reserve(original_values.size());
std::transform(original_values.begin(), original_values.end(), std::back_inserter(differences_from_mean), std::bind1st(std::minus<double>(), mean));
今日、私は確かにそれを別の方法で書きます。他に何もなければ、C++11 ラムダを利用します。
これら 2 つのコード スニペットが異なることを行うことは明らかです。1 つには、この例では、std::vector
例のように中間コレクションを作成していませんstd::valarray
。std::vector
ただし、違いはとの違いに結びついているため、それらを比較するのは公平だと思いますstd::valarray
。
この回答を書いたとき、要素の値を 2 つstd::valarray
の s (例の最後の行) から減算することは、例の対応する行(たまたま最後の行)std::valarray
よりもキャッシュフレンドリーではないのではないかと思いました。std::vector
しかし、
std::valarray<double> original_values = ... // obviously I put something here
double mean = original_values.sum() / original_values.size();
std::valarray<double> differences_from_mean = original_values - mean;
例と同じことを行い、std::vector
ほぼ同じパフォーマンスを持っています。最後に、問題はどの API を好むかです。
valarray は、FORTRAN のベクトル処理の良さを C++ に反映させるはずでした。どういうわけか、必要なコンパイラのサポートが実際に行われることはありませんでした。
Josuttis の本には、valarray に関する興味深い (やや中傷的な) 解説が含まれています ( hereおよびhere )。
ただし、Intel は現在、最近のコンパイラ リリースで valarray を再検討しているようです (たとえば、スライド 9を参照)。彼らの 4 方向の SIMD SSE 命令セットが 8 方向の AVX と 16 方向の Larrabee 命令によって結合されようとしていることを考えると、これは興味深い開発であり、移植性のために、 (たとえば) 組み込み関数よりも valarray 。
C++11標準は次のように述べています。
valarray配列クラスは、特定の形式のエイリアシングがないように定義されているため、これらのクラスの操作を最適化できます。
C ++1126.6.1-2を参照してください。
std::valarray は、計算流体力学や計算構造力学など、数百万、場合によっては数千万のアイテムを持つ配列があり、数百万のタイムステップでループ内でそれらを反復処理する、重い数値タスクを対象としています。おそらく今日では std::vector のパフォーマンスは同等ですが、約 15 年前には、効率的な数値ソルバーを作成する場合、valarray はほぼ必須でした。