使用 AVX 逐个添加两个双精度数组

Entrywise addition of two double arrays using AVX

本文关键字:两个 双精度 数组 AVX 添加 使用      更新时间:2023-10-16

我需要一个函数来条目式添加两个双精度数组的元素并将结果存储在第三个数组中。目前我使用(简化)

void add( double* result, const double* a, const double* b, size_t size) {
    memcpy(result, a, size*sizeof(double));
    for(size_t i = 0; i < size; ++i) {
        result[i] += b[i];
    }
}

据我所知,memcpy函数使用AVX。为了提高性能,我还想强制使用 AVX 进行添加。这应该是AVX最基本的示例之一,但是我找不到任何描述如何在C/C++中执行此操作。如果可能的话,我想避免使用外部库。

假设 AVX-512,您将需要这样的东西:

void add( double* result, const double* a, const double* b, size_t size) 
{
    size_t i = 0;
    // Note we are doing as many blocks of 8 as we can.  If the size is not divisible by 8
    // then we will have some left over that will then be performed serially.
    // AVX-512 loop
    for( ; i < (size & ~0x7); i += 8) 
    {
        const __m512d kA8   = _mm512_load_pd( &a[i] );
        const __m512d kB8   = _mm512_load_pd( &b[i] );
        const __m512d kRes = _mm512_add_pd( kA8, kB8 );
        _mm512_stream_pd( &res[i], kRes );
    }
    // AVX loop
    for ( ; i < (size & ~0x3); i += 4 )
    {
        const __m256d kA4   = _mm256_load_pd( &a[i] );
        const __m256d kB4   = _mm256_load_pd( &b[i] );
        const __m256d kRes = _mm256_add_pd( kA4, kB4 );
        _mm256_stream_pd( &res[i], kRes );
    }
    // SSE2 loop
    for ( ; i < (size & ~0x1); i += 2 )
    {
        const __m128d kA2   = _mm_load_pd( &a[i] );
        const __m128d kB2   = _mm_load_pd( &b[i] );
        const __m128d kRes = _mm_add_pd( kA2, kB2 );
        _mm_stream_pd( &res[i], kRes );
    }
    // Serial loop
    for( ; i < size; i++ )
    {
        result[i] = a[i] + b[i];
    }
}

(虽然请注意,我只是把它从我的头顶上扔了起来)。

从上面的代码中需要注意的是,我基本上使用下一个最佳并行代码处理剩余的值。 这主要是为了说明您可以并行执行的 3 种可能方法。 循环将自行完美运行。 例如,如果您不支持AVX-512,那么您将直接跳转到AVX循环。 如果您不支持 AVX,那么如果您直接跳转到 SSE2 循环,那么您将使用您的硬件可以支持的性能最高的循环。

为了获得最佳性能,您的阵列应与负载中使用的相关大小对齐。 因此,对于 AVX-512,您需要 512 位的 64 字节对齐。对于 AVX,256 位或 32 字节对齐。对于 SSE2 128 位或 16 字节对齐。 如果您对所有数组使用 64 字节对齐,那么您将始终具有良好的对齐方式,尽管您可能希望采用 128 字节对齐方式,以便在出现时轻松移动到 AVX-1024 ;)