使用 AVX 或更高版本编译我的 DLL 的 32 位版本有什么真正的好处吗?

Are there any real benefits to compiling a 32-bit version of my DLL with AVX or higher?

我有一个遗留 Windows DLL(用 c++ 编写),我需要为它维护 32 位版本和 64 位版本。我正在使用 Agner 的向量 class 库使用 simd 更新繁重的数学代码,与 SSE4.2 相比,使用 AVX 编译时,32 位版本的速度几乎没有或没有提高。我知道使用 32 位代码总是只有 8 个向量寄存器可用,但我不清楚(经过大量搜索)这在使用 AVX、AVX2 或 AVX512 进行编译时究竟意味着什么。是否有编译器选项(Microsoft 或 Clang)可以让我比 SSE4.2(对于简单的浮点运算循环)有一些有价值的速度改进,或者我应该为自己省去一些麻烦并使用 SSE4.2 编译 32 位版本?

我自己在回答这个问题,尽管可以说这个问题应该被删除......也许它会在某个时候帮助某人。

当我完成我的 simd 代码(对齐内存有很大的不同)并摆弄 MSVC 编译器选项时,我的 32 位编译开始完全按预期运行,当没有 simd 与 SSE4.2 进行比较时、AVX 和 AVX512。对下面的示例代码进行基准测试显示,对于 32 位,SSE4.2、AVX、AVX512 的速度提升率分别为 48%、22% 和 10%。

奇怪的是,对于没有 simd 的 64 位编译运行速度要快得多,但对于所有三个 simd 选项(新问题的好主题)来说比 32 位编译运行速度稍慢。

我编译代码时没有使用 /Qpar 开关和 /Qvec-report:2 /Qpar-report:2 以尽可能验证没有自动矢量化或自动并行化。

int Simd_debug(int idebug_branch, int iters, int asize)
{
    int j, k, iret = -3;
    double u, d;
    double* TR = 0; 
    double* UP = 0;
    double* DN = 0; 
    char* TR_unaligned = 0;
    char* UP_unaligned = 0;
    char* DN_unaligned = 0;

    const int vectorsize = SIMD_SIZE_SPN;   //8, 4, 2 = AVX512, AVX, SSE size
    #if SIMD_SIZE_SPN == 8
        Vec8d vec_up, vec_dn, vec_tree;
    #elif SIMD_SIZE_SPN == 4
        Vec4d vec_up, vec_dn, vec_tree;
    #else
        Vec2d vec_up, vec_dn, vec_tree;
    #endif

    const bool go_align_mem = true; 

    bool go_simd = (idebug_branch != 2);
    bool go_intrinsic = (idebug_branch == 1);
    int alignby = sizeof(double) * vectorsize;
    int datasize = asize;
    int arraysize = (datasize + vectorsize - 1) & (-vectorsize);
    int regularpart = arraysize & (-vectorsize);

    if (go_simd)
    {
        if (go_align_mem)
        {
            TR_unaligned = new char[arraysize * sizeof(double) + alignby];
            char* TR_aligned = (char*)(((size_t)TR_unaligned + alignby - 1) & (-alignby));
            TR = (double*)TR_aligned;

            UP_unaligned = new char[arraysize * sizeof(double) + alignby];
            char* UP_aligned = (char*)(((size_t)UP_unaligned + alignby - 1) & (-alignby));
            UP = (double*)UP_aligned;

            DN_unaligned = new char[arraysize * sizeof(double) + alignby];
            char* DN_aligned = (char*)(((size_t)DN_unaligned + alignby - 1) & (-alignby));
            DN = (double*)DN_aligned;

            //debug check alignment
            if ((((uintptr_t)TR & (alignby - 1)) != 0) || (((uintptr_t)UP & (alignby - 1)) != 0) || (((uintptr_t)DN & (alignby - 1)) != 0))
            {
                iret = -703;
                goto bail_out;
            }
        }
        else
        {
            TR = new double[arraysize];
            UP = new double[arraysize];
            DN = new double[arraysize];
        }//if (go_align_mem)
    }
    else
    {
        TR = new double[arraysize];
        UP = new double[arraysize];
        DN = new double[arraysize];
    }//if (go_simd)
    
    u = 1.01;
    d = 0.99;
    UP[0] = u;
    DN[0] = d;
    for (k = 1; k < arraysize; k++)
    {
        UP[k] = u * UP[k - 1];
        DN[k] = d * DN[k - 1];
    }

    for (j = 0; j < iters; j++)
    {
        if (go_simd)
        {
            for (k = 0; k < regularpart; k += vectorsize)
            {
                vec_up.load(UP + k);
                vec_dn.load(DN + k);
                vec_tree = vec_up * vec_dn;
                vec_tree.store(TR + k);
            }
        }
        else
        {
            #pragma loop(no_vector) //don't need this, according to /Qvec-report:2 ...
            for (k = 0; k < arraysize; k++)
            {
                TR[k] = UP[k] * DN[k];
            }
        }//if (go_simd)
    }

    iret = 10000 * idebug_branch + arraysize;

bail_out:
    if (go_simd && go_align_mem)
    {
        delete[] TR_unaligned;
        delete[] UP_unaligned;
        delete[] DN_unaligned;
    }
    else
    {
        delete[] TR;
        delete[] UP;
        delete[] DN;
    }
    return iret;
}