顶点分量的 AVX2 重心插值
AVX2 barycentric interpolation of a vertex component
我刚刚开始使用 simd intrinsics。我的探查器显示在顶点插值上花费了大量时间。我的目标是 AVX2 并试图为以下内容找到优化 - 鉴于我有 3 个需要插值的 vector2,我想我应该能够将它们加载到一个 __m256 中并有效地进行乘法和加法。这是我要转换的代码——作为 256 位操作值得这样做吗?向量未对齐。
Vector2 Interpolate( Vector3 uvw, Vector2 v0, Vector2 v1, Vector2 v2 )
{
Vector2 out;
out = v0 * uvw.x;
out += v1 * uvw.y;
out += v2 * uvw.z;
return out;
}
struct Vector2 { float x; float y; } ;
struct Vector3 { float x; float y; float z; } ;
我的问题是 - 如何将三个未对齐的 vector2 加载到单个 256 位寄存器中以便我可以进行乘法和加法?
我正在使用 VS2013。
我很无聊所以我写了它,没有测试(但是编译了,Clang 和 GCC 都从中制作了合理的代码)
void interpolateAll(int n, float* scales, float* vin, float* vout)
{
// preconditions:
// (n & 7 == 0) (not really, but vout must be padded)
// scales & 31 == 0
// vin & 31 == 0
// vout & 31 == 0
// vin format:
// float v0x[8]
// float v0y[8]
// float v1x[8]
// float v1y[8]
// float v2x[8]
// float v2y[8]
// scales format:
// float scale0[8]
// float scale1[8]
// float scale2[8]
// vout format:
// float vx[8]
// float vy[8]
for (int i = 0; i < n; i += 8) {
__m256 scale_0 = _mm256_load_ps(scales + i * 3);
__m256 scale_1 = _mm256_load_ps(scales + i * 3 + 8);
__m256 scale_2 = _mm256_load_ps(scales + i * 3 + 16);
__m256 v0x = _mm256_load_ps(vin + i * 6);
__m256 v0y = _mm256_load_ps(vin + i * 6 + 8);
__m256 v1x = _mm256_load_ps(vin + i * 6 + 16);
__m256 v1y = _mm256_load_ps(vin + i * 6 + 24);
__m256 v2x = _mm256_load_ps(vin + i * 6 + 32);
__m256 v2y = _mm256_load_ps(vin + i * 6 + 40);
__m256 x = _mm256_mul_ps(scale_0, v0x);
__m256 y = _mm256_mul_ps(scale_0, v0y);
x = _mm256_fmadd_ps(scale_1, v1x, x);
y = _mm256_fmadd_ps(scale_1, v1y, y);
x = _mm256_fmadd_ps(scale_2, v2x, x);
y = _mm256_fmadd_ps(scale_2, v2y, y);
_mm256_store_ps(vout + i * 2, x);
_mm256_store_ps(vout + i * 2 + 8, y);
}
}
使用 Z 玻色子的格式,如果我理解正确的话。无论如何,从 SIMD 的角度来看,它都是一种不错的格式。从 C++ 的角度来看有点不方便。
FMA 确实会不必要地序列化乘法,但这无关紧要,因为它不是 loop-carried 依赖项的一部分。
这个(假设数组足够小)的预测吞吐量是每 9 个周期 2 次迭代,受到负载的瓶颈。在实践中可能稍微更糟,有人谈论简单的商店偶尔会窃取 p2 或 p3 之类的事情,我不太确定。无论如何,这对于 18 个 "FMAs" 来说已经足够了,但是只有 12 个(8 和 4 个乘法),所以如果有的话,在这里移动一些额外的计算可能会有用。
我刚刚开始使用 simd intrinsics。我的探查器显示在顶点插值上花费了大量时间。我的目标是 AVX2 并试图为以下内容找到优化 - 鉴于我有 3 个需要插值的 vector2,我想我应该能够将它们加载到一个 __m256 中并有效地进行乘法和加法。这是我要转换的代码——作为 256 位操作值得这样做吗?向量未对齐。
Vector2 Interpolate( Vector3 uvw, Vector2 v0, Vector2 v1, Vector2 v2 )
{
Vector2 out;
out = v0 * uvw.x;
out += v1 * uvw.y;
out += v2 * uvw.z;
return out;
}
struct Vector2 { float x; float y; } ;
struct Vector3 { float x; float y; float z; } ;
我的问题是 - 如何将三个未对齐的 vector2 加载到单个 256 位寄存器中以便我可以进行乘法和加法?
我正在使用 VS2013。
我很无聊所以我写了它,没有测试(但是编译了,Clang 和 GCC 都从中制作了合理的代码)
void interpolateAll(int n, float* scales, float* vin, float* vout)
{
// preconditions:
// (n & 7 == 0) (not really, but vout must be padded)
// scales & 31 == 0
// vin & 31 == 0
// vout & 31 == 0
// vin format:
// float v0x[8]
// float v0y[8]
// float v1x[8]
// float v1y[8]
// float v2x[8]
// float v2y[8]
// scales format:
// float scale0[8]
// float scale1[8]
// float scale2[8]
// vout format:
// float vx[8]
// float vy[8]
for (int i = 0; i < n; i += 8) {
__m256 scale_0 = _mm256_load_ps(scales + i * 3);
__m256 scale_1 = _mm256_load_ps(scales + i * 3 + 8);
__m256 scale_2 = _mm256_load_ps(scales + i * 3 + 16);
__m256 v0x = _mm256_load_ps(vin + i * 6);
__m256 v0y = _mm256_load_ps(vin + i * 6 + 8);
__m256 v1x = _mm256_load_ps(vin + i * 6 + 16);
__m256 v1y = _mm256_load_ps(vin + i * 6 + 24);
__m256 v2x = _mm256_load_ps(vin + i * 6 + 32);
__m256 v2y = _mm256_load_ps(vin + i * 6 + 40);
__m256 x = _mm256_mul_ps(scale_0, v0x);
__m256 y = _mm256_mul_ps(scale_0, v0y);
x = _mm256_fmadd_ps(scale_1, v1x, x);
y = _mm256_fmadd_ps(scale_1, v1y, y);
x = _mm256_fmadd_ps(scale_2, v2x, x);
y = _mm256_fmadd_ps(scale_2, v2y, y);
_mm256_store_ps(vout + i * 2, x);
_mm256_store_ps(vout + i * 2 + 8, y);
}
}
使用 Z 玻色子的格式,如果我理解正确的话。无论如何,从 SIMD 的角度来看,它都是一种不错的格式。从 C++ 的角度来看有点不方便。
FMA 确实会不必要地序列化乘法,但这无关紧要,因为它不是 loop-carried 依赖项的一部分。
这个(假设数组足够小)的预测吞吐量是每 9 个周期 2 次迭代,受到负载的瓶颈。在实践中可能稍微更糟,有人谈论简单的商店偶尔会窃取 p2 或 p3 之类的事情,我不太确定。无论如何,这对于 18 个 "FMAs" 来说已经足够了,但是只有 12 个(8 和 4 个乘法),所以如果有的话,在这里移动一些额外的计算可能会有用。