SSE 复制、AVX 复制和 std::copy 性能

新手上路,请多包涵

我试图通过 SSE 和 AVX 提高复制操作的性能:

     #include <immintrin.h>

    const int sz = 1024;
    float *mas = (float *)_mm_malloc(sz*sizeof(float), 16);
    float *tar = (float *)_mm_malloc(sz*sizeof(float), 16);
    float a=0;
    std::generate(mas, mas+sz, [&](){return ++a;});

    const int nn = 1000;//Number of iteration in tester loops
    std::chrono::time_point<std::chrono::system_clock> start1, end1, start2, end2, start3, end3;

    //std::copy testing
    start1 = std::chrono::system_clock::now();
    for(int i=0; i<nn; ++i)
        std::copy(mas, mas+sz, tar);
    end1 = std::chrono::system_clock::now();
    float elapsed1 = std::chrono::duration_cast<std::chrono::microseconds>(end1-start1).count();

    //SSE-copy testing
    start2 = std::chrono::system_clock::now();
    for(int i=0; i<nn; ++i)
    {
        auto _mas = mas;
        auto _tar = tar;
        for(; _mas!=mas+sz; _mas+=4, _tar+=4)
        {
           __m128 buffer = _mm_load_ps(_mas);
           _mm_store_ps(_tar, buffer);
        }
    }
    end2 = std::chrono::system_clock::now();
    float elapsed2 = std::chrono::duration_cast<std::chrono::microseconds>(end2-start2).count();

    //AVX-copy testing
    start3 = std::chrono::system_clock::now();
    for(int i=0; i<nn; ++i)
    {
        auto _mas = mas;
        auto _tar = tar;
        for(; _mas!=mas+sz; _mas+=8, _tar+=8)
        {
           __m256 buffer = _mm256_load_ps(_mas);
           _mm256_store_ps(_tar, buffer);
        }
    }
    end3 = std::chrono::system_clock::now();
    float elapsed3 = std::chrono::duration_cast<std::chrono::microseconds>(end3-start3).count();

    std::cout<<"serial - "<<elapsed1<<", SSE - "<<elapsed2<<", AVX - "<<elapsed3<<"\nSSE gain: "<<elapsed1/elapsed2<<"\nAVX gain: "<<elapsed1/elapsed3;

    _mm_free(mas);
    _mm_free(tar);

有用。然而,虽然测试循环中的迭代次数 - nn - 增加了,但 simd-copy 的性能增益却降低了:

nn=10:SSE 增益=3,AVX 增益=6;

nn=100:SSE 增益=0.75,AVX 增益=1.5;

nn=1000:SSE 增益=0.55,AVX 增益=1.1;

任何人都可以解释提到的性能下降影响的原因是什么,是否建议手动对复制操作进行矢量化?

原文由 gorill 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 885
2 个回答

问题是您的测试在迁移硬件中使基准测试变得困难的某些因素方面做得很差。为了测试这一点,我制作了自己的测试用例。像这样的东西:

 for blah blah:
    sleep(500ms)
    std::copy
    sse
    axv

输出:

 SSE: 1.11753x faster than std::copy
AVX: 1.81342x faster than std::copy

所以在这种情况下,AVX 比 std::copy 快很多。当我将测试用例更改为..时会发生什么

for blah blah:
    sleep(500ms)
    sse
    axv
    std::copy

请注意,除了测试的顺序外,绝对没有任何变化。

 SSE: 0.797673x faster than std::copy
AVX: 0.809399x faster than std::copy

哇!这怎么可能? CPU 需要一段时间才能达到全速,因此稍后运行的测试具有优势。这个问题现在有 3 个答案,包括一个“已接受”的答案。但只有投票数最少的那个才是正确的。

这就是为什么基准测试很难的原因之一,你永远不应该相信任何人的微基准,除非他们已经包含了他们设置的详细信息。不仅仅是代码可能出错。省电功能和奇怪的驱动程序可能会完全搞乱您的基准测试。有一次,我通过切换不到 1% 的笔记本电脑提供的 bios 开关来测量性能差异 7 倍。

原文由 Stefan 发布,翻译遵循 CC BY-SA 3.0 许可协议

我认为这是因为测量对于有点短的操作来说并不准确。

在 Intel CPU 上测量性能时

  1. 禁用“Turbo Boost”和“SpeedStep”。您可以在系统 BIOS 上执行此操作。

  2. 将进程/线程优先级更改为高或实时。这将使您的线程保持运行。

  3. 将 Process CPU Mask 设置为仅一个内核。具有较高优先级的 CPU 掩码将最大限度地减少上下文切换。

  4. 使用 __rdtsc() 内在函数。 Intel Core 系列使用 __rdtsc() 返回 CPU 内部时钟计数器。您将从 3.4Ghz CPU 获得 3400000000 计数/秒。 __rdtsc() 刷新 CPU 中的所有计划操作,以便更准确地测量时序。

这是我用于测试 SSE/AVX 代码的测试平台启动代码。

     int GetMSB(DWORD_PTR dwordPtr)
    {
        if(dwordPtr)
        {
            int result = 1;
    #if defined(_WIN64)
            if(dwordPtr & 0xFFFFFFFF00000000) { result += 32; dwordPtr &= 0xFFFFFFFF00000000; }
            if(dwordPtr & 0xFFFF0000FFFF0000) { result += 16; dwordPtr &= 0xFFFF0000FFFF0000; }
            if(dwordPtr & 0xFF00FF00FF00FF00) { result += 8;  dwordPtr &= 0xFF00FF00FF00FF00; }
            if(dwordPtr & 0xF0F0F0F0F0F0F0F0) { result += 4;  dwordPtr &= 0xF0F0F0F0F0F0F0F0; }
            if(dwordPtr & 0xCCCCCCCCCCCCCCCC) { result += 2;  dwordPtr &= 0xCCCCCCCCCCCCCCCC; }
            if(dwordPtr & 0xAAAAAAAAAAAAAAAA) { result += 1; }
    #else
            if(dwordPtr & 0xFFFF0000) { result += 16; dwordPtr &= 0xFFFF0000; }
            if(dwordPtr & 0xFF00FF00) { result += 8;  dwordPtr &= 0xFF00FF00; }
            if(dwordPtr & 0xF0F0F0F0) { result += 4;  dwordPtr &= 0xF0F0F0F0; }
            if(dwordPtr & 0xCCCCCCCC) { result += 2;  dwordPtr &= 0xCCCCCCCC; }
            if(dwordPtr & 0xAAAAAAAA) { result += 1; }
    #endif
            return result;
        }
        else
        {
            return 0;
        }
    }

    int _tmain(int argc, _TCHAR* argv[])
    {
        // Set Core Affinity
        DWORD_PTR processMask, systemMask;
        GetProcessAffinityMask(GetCurrentProcess(), &processMask, &systemMask);
        SetProcessAffinityMask(GetCurrentProcess(), 1 << (GetMSB(processMask) - 1) );

        // Set Process Priority. you can use REALTIME_PRIORITY_CLASS.
        SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);

        DWORD64 start, end;
        start = __rdtsc();
    // your code here.
        end = __rdtsc();
        printf("%I64d\n", end - start);
        return 0;
    }

原文由 zupet 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题