コンパイラの最適化に適したコーディングスタイルはどれですか?具体的には、1)直ちに捨てられる一時的な値の数を最小限に抑えること、2)自動ベクトル化、すなわち算術演算のためのSIMD命令を生成することに興味がある。変更可能なベクトルと不変のベクトル演算の最適化
私はこの構造体があるとします。この構造体の
#define FOR_EACH for (int i = 0; i < N; ++i)
template<typename T, unsigned N>
struct Vector {
void scale(T scalar) {
FOR_EACH v[i] *= scalar;
}
void add(const Vector<T, N>& other) {
FOR_EACH v[i] += other.v[i];
}
void mul(const Vector<T, N>& other) {
FOR_EACH v[i] *= other.v[i];
}
T v[N];
};
使用例:
Vector<int, 3> v1 = ...;
Vector<int, 3> v2 = ...;
v1.scale(10);
v1.add(v2);
v1.mul(v2);
これは変更可能なアプローチです。
代替不変のアプローチは、次のようになります。
template<typename T, unsigned N>
struct Vector {
Vector(const Vector<T, N>& other) {
memcpy(v, other.v, sizeof(v));
}
Vector<T, N> operator+(const Vector<T, N>& other) const {
Vector<T, N> result(*this);
FOR_EACH result.v[i] += other.v[i];
return result;
}
Vector<T, N> operator*(T scalar) const {
Vector<T, N> result(*this);
FOR_EACH result.v[i] *= scalar;
return result;
}
Vector<T, N> operator*(const Vector<T, N>& other) const {
Vector<T, N> result(*this);
FOR_EACH result.v[i] *= other.v[i];
return result;
}
T v[N];
};
使用例:今すぐ
Vector<int, 3> v1 = ...;
Vector<int, 3> v2 = ...;
auto result = (v1 * 10 + v2) * v2;
を、私はこの問題のAPIの設計に関係していませんよ。この点に関して、両方の解決策が実行可能であると仮定する。
また、サンプルコードのint
の代わりに、float
またはdouble
でもかまいません。
私にとって興味深いのは、これは現代のC++コンパイラによってどのデザインがより簡単に分析できるのでしょうか?私は特に単一のコンパイラを対象としていません。コンパイラの経験があり、最適化の方法を知っていれば、あなたの経験を共有してください。
第2バージョンでは、多くの一時的な値が生成されます。コンパイラーは最終的にすべての演算子呼び出しをインライン化し、その中に保持されているすべての算術式を見ることができるでしょうか? (私はインライン化せずに、コンパイラなしで副作用の可能性があるため一時的なものを排除できると仮定しています)
最初のバージョンでは一時的な数は最小限に抑えられますが、コンパイラは、操作の数を最小限に抑え、並列化(CPU命令レベルで)を可能にする方法で、インテントを引き出し、オペレーションの順序を変更できますか?
現代のコンパイラが上記のループをベクトル化するのはどれくらい難しいですか?
直接インデックスは、それが簡単にコンパイラがそれらをベクトル化できるようになります。インデックスが複雑なアルゴリズムで間接的に適用されると、コンパイラが失敗することがあります。 –