首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >微指令优化指令

微指令优化指令
EN

Stack Overflow用户
提问于 2011-09-01 10:03:49
回答 4查看 2.2K关注 0票数 10

我注意到,有时MSVC 2010根本不重新排序SSE指令。我认为我不必关心循环中的指令顺序,因为编译器处理这个问题的效果最好,但情况似乎并非如此。

我该怎么想呢?什么决定了最佳的指令顺序?我知道有些指令比其他指令具有更高的延迟,有些指令可以在cpu级别上并行/异步地运行。哪些指标与上下文相关?我在哪里能找到他们?

我知道我可以通过分析来避免这个问题,但是这样的分析器是昂贵的(VTune XE),--我想知道它背后的理论--,而不仅仅是帝王式的结果。

另外,我应该关心软件预取(_mm_prefetch),还是假设cpu会比我做得更好呢?

假设我有以下功能。我应该把一些指令插进去吗?我应该在溪流之前做商店,把所有的货物按顺序排列,然后计算,等等.?我是否需要考虑USWC和非USWC,以及非USWC?

代码语言:javascript
运行
复制
            auto cur128     = reinterpret_cast<__m128i*>(cur);
            auto prev128    = reinterpret_cast<const __m128i*>(prev);
            auto dest128    = reinterpret_cast<__m128i*>(dest;
            auto end        = cur128 + count/16;

            while(cur128 != end)            
            {
                auto xmm0 = _mm_add_epi8(_mm_load_si128(cur128+0), _mm_load_si128(prev128+0));
                auto xmm1 = _mm_add_epi8(_mm_load_si128(cur128+1), _mm_load_si128(prev128+1));
                auto xmm2 = _mm_add_epi8(_mm_load_si128(cur128+2), _mm_load_si128(prev128+2));
                auto xmm3 = _mm_add_epi8(_mm_load_si128(cur128+3), _mm_load_si128(prev128+3));

                                    // dest128 is USWC memory
                _mm_stream_si128(dest128+0, xmm0);  
                _mm_stream_si128(dest128+1, xmm1);
                _mm_stream_si128(dest128+2, xmm2);;
                _mm_stream_si128(dest128+3, xmm3);

                                    // cur128 is temporal, and will be used next time, which is why I choose store over stream
                _mm_store_si128 (cur128+0, xmm0);               
                _mm_store_si128 (cur128+1, xmm1);                   
                _mm_store_si128 (cur128+2, xmm2);                   
                _mm_store_si128 (cur128+3, xmm3);

                cur128  += 4;
                dest128 += 4;
                prev128 += 4;
            }

           std::swap(cur, prev);
EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2011-09-01 19:34:23

我同意每个人的观点,测试和调整是最好的方法。但是有一些技巧可以帮助它。

首先,MSVC 执行重排序的SSE指令.您的例子可能太简单,或者已经是最优的。

一般来说,如果你有足够的寄存器这样做,完全交错往往会得到最好的结果。要更进一步,打开循环以使用所有寄存器,但不要过多地溢出。在您的示例中,循环完全受内存访问的约束,因此没有多少空间可以做得更好。

在大多数情况下,没有必要使指令的顺序达到最优的性能。只要它“足够接近”,编译器或硬件的无序执行就会为您修复它。

我用来确定我的代码是否最优的方法是关键路径和瓶颈分析。写完循环之后,我会查找哪些指令使用了哪些资源。使用这些信息,我可以计算性能的上限,然后将其与实际结果进行比较,以了解我离最佳性能有多近/有多远。

例如,假设我有一个有100个相加和50个乘法的循环。在英特尔和AMD (预推土机),每个核心可以维持一个SSE/AVX加法和一个SSE/AVX乘法每一个周期。因为我的循环有100个加法,所以我知道我不能比100个循环做得更好。是的,乘法器会有一半时间空闲,但加法器是瓶颈。

现在我去计时我的循环,我每次迭代得到105个周期。这意味着我非常接近最佳状态,而且没有更多的收获。但如果我有250个周期,那就意味着循环出了问题,值得对它做更多的修改。

临界路径分析遵循同样的思想。查找所有指令的延迟,并找到循环关键路径的循环时间。如果你的实际表现非常接近它,你已经是最佳的。

Agner对当前处理器的内部细节有很好的参考:http://www.agner.org/optimize/microarchitecture.pdf

票数 9
EN

Stack Overflow用户

发布于 2011-09-01 11:05:22

我刚刚使用VS2010 32位编译器构建了这个程序,我得到了以下内容:

代码语言:javascript
运行
复制
void F (void *cur, const void *prev, void *dest, int count)
{
00901000  push        ebp  
00901001  mov         ebp,esp  
00901003  and         esp,0FFFFFFF8h  
  __m128i *cur128     = reinterpret_cast<__m128i*>(cur);
00901006  mov         eax,220h  
0090100B  jmp         F+10h (901010h)  
0090100D  lea         ecx,[ecx]  
  const __m128i *prev128    = reinterpret_cast<const __m128i*>(prev);
  __m128i *dest128    = reinterpret_cast<__m128i*>(dest);
  __m128i *end        = cur128 + count/16;

  while(cur128 != end)            
  {
    auto xmm0 = _mm_add_epi8(_mm_load_si128(cur128+0), _mm_load_si128(prev128+0));
00901010  movdqa      xmm0,xmmword ptr [eax-220h]  
    auto xmm1 = _mm_add_epi8(_mm_load_si128(cur128+1), _mm_load_si128(prev128+1));
00901018  movdqa      xmm1,xmmword ptr [eax-210h]  
    auto xmm2 = _mm_add_epi8(_mm_load_si128(cur128+2), _mm_load_si128(prev128+2));
00901020  movdqa      xmm2,xmmword ptr [eax-200h]  
    auto xmm3 = _mm_add_epi8(_mm_load_si128(cur128+3), _mm_load_si128(prev128+3));
00901028  movdqa      xmm3,xmmword ptr [eax-1F0h]  
00901030  paddb       xmm0,xmmword ptr [eax-120h]  
00901038  paddb       xmm1,xmmword ptr [eax-110h]  
00901040  paddb       xmm2,xmmword ptr [eax-100h]  
00901048  paddb       xmm3,xmmword ptr [eax-0F0h]  

    // dest128 is USWC memory
    _mm_stream_si128(dest128+0, xmm0);  
00901050  movntdq     xmmword ptr [eax-20h],xmm0  
    _mm_stream_si128(dest128+1, xmm1);
00901055  movntdq     xmmword ptr [eax-10h],xmm1  
    _mm_stream_si128(dest128+2, xmm2);;
0090105A  movntdq     xmmword ptr [eax],xmm2  
    _mm_stream_si128(dest128+3, xmm3);
0090105E  movntdq     xmmword ptr [eax+10h],xmm3  

    // cur128 is temporal, and will be used next time, which is why I choose store over stream
    _mm_store_si128 (cur128+0, xmm0);               
00901063  movdqa      xmmword ptr [eax-220h],xmm0  
    _mm_store_si128 (cur128+1, xmm1);                   
0090106B  movdqa      xmmword ptr [eax-210h],xmm1  
    _mm_store_si128 (cur128+2, xmm2);                   
00901073  movdqa      xmmword ptr [eax-200h],xmm2  
    _mm_store_si128 (cur128+3, xmm3);
0090107B  movdqa      xmmword ptr [eax-1F0h],xmm3  

    cur128  += 4;
00901083  add         eax,40h  
00901086  lea         ecx,[eax-220h]  
0090108C  cmp         ecx,10h  
0090108F  jne         F+10h (901010h)  
    dest128 += 4;
    prev128 += 4;
  }
}

这表明编译器正在重新排序指令,遵循“写入寄存器后不要立即使用寄存器”的一般规则。它还将两个加载和一个添加转换为单个加载和从内存中添加。您没有理由不能自己编写这样的代码并使用所有SIMD寄存器,而不是您当前使用的四个寄存器。您可能希望匹配加载到缓存行大小的字节总数。这将给硬件预取一个机会来填充下一个缓存行,然后再需要它。

另外,预取,特别是在代码中--按顺序读取内存--通常是不必要的。MMU一次可以预取四条流。

票数 6
EN

Stack Overflow用户

发布于 2011-09-01 12:54:04

您可能会发现英特尔架构优化参考手册的第5章到第7章非常有趣,它详细介绍了Intel认为您应该如何编写最优的SSE代码,并详细介绍了您正在问的许多问题。

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/7268800

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档