提示编译器浮点向量计数可以被 8 整除?
Hint the compiler that float-vector count is divisible by 8?
static inline void R1_sub_R0(float *vec, size_t cnt, float toSubtract){
for(size_t i=0; cnt; ++i){
vec[i] -= toSubtract;
}
}
我知道 cnt
总是可以被 8 整除,因此代码可以通过 SSE 和 AVX 进行矢量化。换句话说,我们可以将 *vec
作为 __m256
类型进行迭代。
但是编译器可能不知道这一点。 如何再次向编译器保证此计数可以被 8 整除?
这样的事情会有帮助吗? (如果我们把它贴在函数的开头)
assert(((cnt*sizeof(float)) % sizeof(__m256)) ==0 ); //checks that it's "multiple of __m256 type".
当然,我可以简单地将整个事情写成矢量化代码:
static inline void R1_sub_R0(float *vec, size_t cnt, float toSubtract){
assert(cnt*sizeof(float) % sizeof(__m256) == 0);//check that it's "multiple of __m256 type".
assert(((uintptr_t)(const void *)(POINTER)) % (16) == 0);//assert that 'vec' is 16-byte aligned
__m256 sToSubtract = _mm256_set1_ps(toSubtract);
__m256 *sPtr = (__m256*)vec;
const __m256 *sEnd = (const __m256*)(vec+cnt);
for(sPtr; sPtr != sEnd; ++sPtr){
*sPtr = _mm256_sub_ps(*sPtr, sToSubtract);
}
}
不过,它的运行速度比原来的版本慢了 10%。
所以我只想给编译器一些额外的信息。这样它可以更有效地矢量化代码。
Hint the compiler that float-vector count is divisible by 8?
您可以通过嵌套另一个来半展开循环:
for(size_t i=0; i < cnt; i += 8){
for(size_t j=0; j < 8; j++){
vec[i + j] -= toSubtract;
}
}
编译器可以很容易地看到内部循环具有常量迭代,并且可以将其展开,如果选择的话,可能会使用 SIMD。
Hint the compiler that float-vector count is [16-byte aligned]?
这有点棘手。
您可以使用类似的东西:
struct alignas(16) sse {
float arr[8];
};
// cnt is now number of structs which is 8th fraction of original cnt
R1_sub_R0(sse *vec, size_t cnt, float toSubtract) {
for(size_t i=0; i < cnt; i ++){
for(size_t j=0; j < 8; j++){
vec[i].arr[j] -= toSubtract;
}
}
除此之外,还有编译器扩展,例如 __builtin_assume_aligned
可以与普通浮点数组一起使用。
我不会在这里尝试微优化,如果您只与它沟通您想要完成的事情,而不是您希望它如何完成,编译器将生成最快的代码。因此我会使用 std::transform.
std::transform(vec, vec + cnt, vec,
[toSubstract](float f){return f - toSubstract;});
static inline void R1_sub_R0(float *vec, size_t cnt, float toSubtract){
for(size_t i=0; cnt; ++i){
vec[i] -= toSubtract;
}
}
我知道 cnt
总是可以被 8 整除,因此代码可以通过 SSE 和 AVX 进行矢量化。换句话说,我们可以将 *vec
作为 __m256
类型进行迭代。
但是编译器可能不知道这一点。 如何再次向编译器保证此计数可以被 8 整除?
这样的事情会有帮助吗? (如果我们把它贴在函数的开头)
assert(((cnt*sizeof(float)) % sizeof(__m256)) ==0 ); //checks that it's "multiple of __m256 type".
当然,我可以简单地将整个事情写成矢量化代码:
static inline void R1_sub_R0(float *vec, size_t cnt, float toSubtract){
assert(cnt*sizeof(float) % sizeof(__m256) == 0);//check that it's "multiple of __m256 type".
assert(((uintptr_t)(const void *)(POINTER)) % (16) == 0);//assert that 'vec' is 16-byte aligned
__m256 sToSubtract = _mm256_set1_ps(toSubtract);
__m256 *sPtr = (__m256*)vec;
const __m256 *sEnd = (const __m256*)(vec+cnt);
for(sPtr; sPtr != sEnd; ++sPtr){
*sPtr = _mm256_sub_ps(*sPtr, sToSubtract);
}
}
不过,它的运行速度比原来的版本慢了 10%。 所以我只想给编译器一些额外的信息。这样它可以更有效地矢量化代码。
Hint the compiler that float-vector count is divisible by 8?
您可以通过嵌套另一个来半展开循环:
for(size_t i=0; i < cnt; i += 8){
for(size_t j=0; j < 8; j++){
vec[i + j] -= toSubtract;
}
}
编译器可以很容易地看到内部循环具有常量迭代,并且可以将其展开,如果选择的话,可能会使用 SIMD。
Hint the compiler that float-vector count is [16-byte aligned]?
这有点棘手。
您可以使用类似的东西:
struct alignas(16) sse {
float arr[8];
};
// cnt is now number of structs which is 8th fraction of original cnt
R1_sub_R0(sse *vec, size_t cnt, float toSubtract) {
for(size_t i=0; i < cnt; i ++){
for(size_t j=0; j < 8; j++){
vec[i].arr[j] -= toSubtract;
}
}
除此之外,还有编译器扩展,例如 __builtin_assume_aligned
可以与普通浮点数组一起使用。
我不会在这里尝试微优化,如果您只与它沟通您想要完成的事情,而不是您希望它如何完成,编译器将生成最快的代码。因此我会使用 std::transform.
std::transform(vec, vec + cnt, vec,
[toSubstract](float f){return f - toSubstract;});