C ++ valarrayとベクター
-
05-07-2019 - |
質問
ベクターが大好きです。彼らは気の利いた、高速です。しかし、valarrayと呼ばれるものが存在することは知っています。なぜベクターの代わりにvalarrayを使用するのですか? valarraysには構文上の糖質があることは知っていますが、それ以外では、いつ有用なのですか?
解決
Valarray(値配列)は、Fortranの速度の一部をC ++にもたらすことを目的としています。コンパイラーがコードについての仮定を立てて最適化できるように、ポインターのvalarrayを作成しません。 (Fortranが非常に高速である主な理由は、ポインター型がないため、ポインターのエイリアシングがないことです。)
Valarrayには、かなり簡単な方法でそれらをスライスできるクラスもありますが、標準のその部分ではもう少し作業が必要になる場合があります。それらのサイズ変更は破壊的であり、イテレータがありません。
それで、もしそれがあなたが作業している数字であり、利便性がそれほど重要でないなら、valarraysを使用してください。それ以外の場合は、ベクターの方がはるかに便利です。
他のヒント
valarray
は、間違った場所で間違った時間に生まれた一種の孤児です。これは最適化の試みです。具体的には、ヘビーデューティー数学が記述されたときに使用されたマシン、具体的にはCraysのようなベクトルプロセッサ向けです。
ベクトルプロセッサの場合、一般的にやりたいことは、単一の操作を配列全体に適用し、次に次の操作を配列全体に適用するなど、必要なすべてを実行するまででした。
ただし、かなり小さな配列を扱っている場合を除き、キャッシュではうまく機能しない傾向があります。ほとんどの最新のマシンでは、(可能な範囲で)一般的に好むのは、配列の一部をロードし、目的のすべての操作を実行してから、配列の次の部分に移動することです。
valarray
は、エイリアスの可能性を排除することも想定されています。これにより、レジスターに値をより自由に格納できるため、コンパイラーの速度が向上します(少なくとも理論的には)。しかし、実際には、実際の実装がこれをかなりの程度まで活用するかどうかはまったくわかりません。私はそれがむしろ鶏と卵のような問題だと思う-コンパイラのサポートなしではそれは普及しなかった、そしてそれが普及していない限り、誰もそれをサポートするために彼らのコンパイラで作業する手間をかけるつもりはないだろう。 / p>
また、valarrayで使用する補助クラスの戸惑う(文字通り)配列もあります。 svalid
、 slice_array
、 gslice
、および gslice_array
を取得して、 valarray
、それを多次元配列のように動作させます。また、 mask_array
を" mask"に取得します。操作(たとえば、xのアイテムをyに追加しますが、zがゼロ以外の位置にのみ追加します)。 valarray
を簡単に使用するには、これらの補助クラスについて多くを学ぶ必要があります。その一部は非常に複雑であり、どれも(少なくとも私には)十分に文書化されていません。
下の行:素晴らしい瞬間があり、いくつかのことをかなりきちんと行うことができますが、それがあいまいである(そしてほぼ確実に残る)理由もいくつかあります。
編集(8年後、2017年):上記のいくつかは、少なくともある程度廃止されています。一例として、インテルはコンパイラー向けに最適化されたバージョンのvalarrayを実装しています。 Intel Integrated Performance Primitives(Intel IPP)を使用してパフォーマンスを改善します。正確なパフォーマンスの改善は間違いなく異なりますが、単純なコードを使用した簡単なテストでは、「標準」でコンパイルされた同一のコードと比較して、速度が約2:1改善されています。 valarray
の実装。
したがって、C ++プログラマーが valarray
を大量に使用し始めることを完全に確信しているわけではありませんが、速度を改善できる状況は少なくともいくつかあります。
C ++ 98の標準化中に、valarrayはある種の高速な数学計算を可能にするように設計されました。しかし、その頃、Todd Veldhuizenは式テンプレートを発明し、 blitz ++ を作成し、同様のテンプレートメタ技術が発明されました。 、標準がリリースされる前にvalarraysをほとんど廃止しました。 valarrayの元の提案者であるIIRCは、標準化の途中でそれを放棄しましたが、(もしそうなら)それも助けにはなりませんでした。
ISTRが標準から削除されなかった主な理由は、だれも問題を徹底的に評価し、それを削除するための提案書を書く時間を取らなかったからです。
ただし、これはすべて漠然と記憶されている伝聞であることに留意してください。これを一粒の塩で取り、誰かがこれを修正または確認することを望みます。
valarrayには構文糖がいくつかあることがわかっています
私は、 std :: valarrays
が構文糖に関してはあまりないと思う必要があると言わざるを得ません。構文は異なりますが、違いを「砂糖」とは呼びません。 APIは奇妙です。 The C ++ Programming Language の std :: valarray
sのセクションでは、この珍しいAPIと、 std :: valarray
sが高度に最適化されることが期待されるため、使用中に表示されるエラーメッセージはおそらく直感的ではありません。
好奇心から、約1年前に std :: valarray
を std :: vector
と比較しました。コードや正確な結果はもうありません(ただし、独自のコードを書くのは難しくないはずです)。 GCCを使用すると、 std :: valarray
を単純な数学に使用すると did パフォーマンスが少し向上しますが、標準偏差(そしてもちろん、標準偏差数学に関する限り、それほど複雑ではありません)。 大規模な(< strong>注、 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に関する興味深い(やや非難する)コメントが含まれています(こちらおよびこちら)。
ただし、Intelは最近のコンパイラリリースでvalarrayを再検討しているようです(たとえば、スライド9 );これは、4ウェイSIMD SSE命令セットが8ウェイAVX命令と16ウェイLarrabee命令によって結合されようとしていることを考えると、興味深い展開です。移植性の観点から、次のような抽象化でコーディングする方がはるかに良いでしょう(たとえば)組み込み関数よりもvalarray。
valarrayの適切な使用法が1つ見つかりました。 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がほぼ必須でした。