Какой стиль кодирования лучше подходит для оптимизации компилятора? В частности, меня интересует 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++? Я не нацелен на какой-то конкретный компилятор. Если у вас есть опыт работы с каким-либо компилятором и вы знаете, как он работает с оптимизациями, о которых я спрашиваю, поделитесь своим опытом.
Вторая версия выдает много временных значений. Может ли компилятор избавиться от них, если он в конечном итоге встроит все вызовы операторов и увидит все содержащиеся в них арифметические выражения? (Я предполагаю, что без встраивания ни один компилятор не может устранить временные файлы из-за возможных побочных эффектов)
Первая версия минимизирует количество временных, но строит строго последовательный расчет. Может ли компилятор по-прежнему определить намерение и переупорядочить операции таким образом, чтобы свести к минимуму количество операций и обеспечить их распараллеливание (на уровне инструкций ЦП)?
Насколько сложно современному компилятору векторизовать циклы выше?