私はベクターが大好きです。彼らは気の利いた、高速です。しかし、valarrayと呼ばれるものが存在することは知っています。なぜベクターではなくvalarrayを使用するのですか? valarraysには構文上の糖質があることは知っていますが、それ以外の場合はいつ有用ですか?
Valarray(値配列)は、Fortranの速度の一部をC++にもたらすことを目的としています。コンパイラーがコードについて仮定を立てて最適化できるように、ポインターのvalarrayを作成しません。 (Fortranが非常に高速である主な理由は、ポインター型がないため、ポインターのエイリアシングがないことです。)
また、Valarrayには、かなり簡単な方法でそれらをスライスできるクラスがありますが、標準のその部分ではもう少し作業が必要になる場合があります。それらのサイズを変更すると破壊的であり、イテレータがありません。
ですから、もしそれがあなたが作業している数字であり、利便性がそれほど重要でないなら、valarrayを使用してください。それ以外の場合は、ベクターの方がはるかに便利です。
valarray
は、間違った場所で間違った時間に生まれた一種の孤児です。これは最適化の試みであり、特に具体的には、Craysのようなベクトルプロセッサなど、ヘビーデューティーの数学が記述されたときに使用されたマシン向けです。
ベクトルプロセッサの場合、一般的にやりたいことは、配列全体に単一の操作を適用し、次に配列全体に次の操作を適用し、必要なことをすべて完了するまで続けます。
ただし、かなり小さな配列を扱っている場合を除き、キャッシュではうまく機能しない傾向があります。ほとんどの最新のマシンでは、(可能な範囲で)一般的に好まれるのは、配列の一部をロードし、目的のすべての操作を実行してから、配列の次の部分に移動することです。
valarray
は、エイリアスの可能性を排除することも想定されており、(少なくとも理論的には)レジスタに値を保存する方が自由なので、コンパイラの速度が向上します。しかし、実際には、実際の実装がこれをかなりの程度まで活用するかどうかはまったくわかりません。私はそれはむしろ鶏と卵のような問題だと思う-コンパイラのサポートなしではそれは普及しなかった、そしてそれが普及していない限り、誰もそれをサポートするために彼らのコンパイラで作業する手間をかけるつもりはない。
また、valarrayで使用する補助クラスの戸惑う(文字通り)配列もあります。 slice
、slice_array
、gslice
およびgslice_array
を使用してvalarray
の断片を再生し、多次元配列のように動作させます。また、mask_array
操作を「マスク」します(たとえば、xからyにアイテムを追加しますが、zがゼロ以外の位置にのみ追加します)。 valarray
を簡単に使用するには、これらの補助クラスについて多くのことを学ぶ必要があります。その一部は非常に複雑であり、どれも(少なくとも私には)十分に文書化されているとは思えません。
結論:素晴らしい瞬間があり、いくつかのことをかなりきちんと行うことができますが、それがあいまいである(そしてほぼ確実に残る)理由もいくつかあります。
編集(8年後、2017年):上記のいくつかは、少なくともある程度廃止されています。一例として、インテルはコンパイラー向けに最適化されたバージョンのvalarrayを実装しています。 Intel Integrated Performance Primitives(Intel IPP)を使用してパフォーマンスを改善します。正確なパフォーマンスの改善は間違いなく異なりますが、単純なコードを使用した簡単なテストでは、valarray
の「標準」実装でコンパイルされた同一のコードと比較して、速度が約2:1向上します。
そのため、C++プログラマーがvalarray
を大量に使用し始めることを完全に確信しているわけではありませんが、速度を改善できる状況はほとんどありません。
C++ 98の標準化中に、valarrayはある種の高速な数学計算を可能にするように設計されました。しかし、その頃、Todd Veldhuizenは式テンプレートを発明し、 blitz ++ を作成し、同様のテンプレートメタテクニックが発明されました。 valarrayの元の提案者であるIIRCは、標準化の途中でそれを放棄しました。
ISTRが標準から削除されなかった主な理由は、だれも時間をかけて問題を徹底的に評価し、それを削除する提案を書かないことです。
ただし、これはすべて漠然と記憶されている伝聞であることに留意してください。これを一粒の塩で取り、誰かがこれを修正または確認することを望みます。
Valarraysには構文糖質があることは知っています
私は、std::valarrays
に構文糖の方法があまりないと思います。構文は異なりますが、違いを「砂糖」とは呼びません。 APIは奇妙です。 C++プログラミング言語のstd::valarray
sのセクションでは、この異常なAPIと、std::valarray
sが高度に最適化されることが期待されるため、エラーメッセージそれらを使用しているときに得られるのは、おそらく直感的ではないでしょう。
好奇心から、約1年前にstd::valarray
をstd::vector
と比較しました。コードや正確な結果はもうありません(ただし、独自のコードを書くのは難しくないはずです)。 GCC Ididを使用すると、std::valarray
を単純な数学に使用するとパフォーマンスが少し向上しますが、標準偏差を計算する実装ではありません(もちろん標準偏差isn数学に関する限り、それほど複雑ではありません)。 大きな ([〜#〜] note [〜#〜]、 musiphil からのアドバイスに従って、私はほとんど得ることができたstd::vector
内の各アイテムに対する操作は、std::valarray
sに対する操作よりもキャッシュの方が適切に動作すると思われます。vector
およびvalarray
)と同じパフォーマンス。
最終的に、std::vector
を使用することにしましたが、メモリの割り当てや一時的なオブジェクトの作成などに細心の注意を払っています。
std::vector
とstd::valarray
の両方は、連続したブロックにデータを保存します。ただし、それらは異なるパターンを使用してそのデータにアクセスし、さらに重要なことには、std::valarray
のAPIはstd::vector
のAPIよりも異なるアクセスパターンを推奨します。
標準偏差の例では、特定のステップでコレクションの平均値と各要素の値と平均値の差を見つける必要がありました。
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
または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つのコードスニペットが異なることを行うことは明らかです。たとえば、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は、C++でFORTRANのベクトル処理の良さを損なうことになっていた。どういうわけか、必要なコンパイラのサポートは実際には決して起こりませんでした。
Josuttisの本には、valarray( here および here )に関する興味深い(やや軽par的な)解説が含まれています。
ただし、Intelは現在、最近のコンパイラリリースでvalarrayを再検討しているようです(例: スライド9 を参照)。これは、4ウェイSIMD SSE命令セットが8ウェイAVXおよび16ウェイLarrabee命令によって結合されようとしているため、移植性のために興味深い展開です。 (たとえば)組み込み関数よりも、valarrayのような抽象化を使用してコーディングする方がはるかに優れています。
Valarrayの適切な使用法を見つけました。 numpy配列のようにvalarrayを使用することです。
auto x = linspace(0, 2 * 3.14, 100);
plot(x, sin(x) + sin(3.f * x) / 3.f + sin(5.f * x) / 5.f);
Valarrayを使用して上記を実装できます。
valarray<float> linspace(float start, float stop, int size)
{
valarray<float> v(size);
for(int i=0; i<size; i++) v[i] = start + i * (stop-start)/size;
return v;
}
std::valarray<float> arange(float start, float step, float stop)
{
int size = (stop - start) / step;
valarray<float> v(size);
for(int i=0; i<size; i++) v[i] = start + step * i;
return v;
}
string psstm(string command)
{//return system call output as string
string s;
char tmp[1000];
FILE* f = popen(command.c_str(), "r");
while(fgets(tmp, sizeof(tmp), f)) s += tmp;
pclose(f);
return s;
}
string plot(const valarray<float>& x, const valarray<float>& y)
{
int sz = x.size();
assert(sz == y.size());
int bytes = sz * sizeof(float) * 2;
const char* name = "plot1";
int shm_fd = shm_open(name, O_CREAT | O_RDWR, 0666);
ftruncate(shm_fd, bytes);
float* ptr = (float*)mmap(0, bytes, PROT_WRITE, MAP_SHARED, shm_fd, 0);
for(int i=0; i<sz; i++) {
*ptr++ = x[i];
*ptr++ = y[i];
}
string command = "python plot.py ";
string s = psstm(command + to_string(sz));
shm_unlink(name);
return s;
}
また、pythonスクリプト。
import sys, posix_ipc, os, struct
import matplotlib.pyplot as plt
sz = int(sys.argv[1])
f = posix_ipc.SharedMemory("plot1")
x = [0] * sz
y = [0] * sz
for i in range(sz):
x[i], y[i] = struct.unpack('ff', os.read(f.fd, 8))
os.close(f.fd)
plt.plot(x, y)
plt.show()
C++ 11標準には次のように書かれています。
Valarray配列クラスは、特定の形式のエイリアシングがないように定義されているため、これらのクラスの操作を最適化できます。
C++ 11 26.6.1-2を参照してください。
std :: valarrayは、Computational Fluid DynamicsやComputational Structure Dynamicsなど、数百万、時には数千万のアイテムを持つ配列を持ち、数百万のタイムステップを持つループでそれらを反復する、重い数値タスクを対象としています。たぶん今日std :: vectorは同等のパフォーマンスを持っていますが、15年ほど前に、効率的な数値ソルバーを作成する場合、valarrayはほぼ必須でした。