当前位置: 首页 > 知识库问答 >
问题:

代码对齐会显着影响性能

许亦
2023-03-14

今天,我发现在添加了一些不相关的代码后,示例代码的速度降低了50%。调试后,我发现问题出在循环对齐中。根据循环代码的位置,有不同的执行时间,例如:

我以前没想到代码对齐会产生如此大的影响。我认为我的编译器足够聪明,可以正确对齐代码。

到底是什么导致了执行时间的如此大的差异?(我想是一些处理器架构细节)。

我用Visual Studio 2019在发布模式下编译的测试程序,并在Windows 10上运行。我在两个处理器上检查了该程序:i7-8700k(以上结果)和intel i5-3570k,但那里不存在问题,执行时间始终在1250us左右。我也尝试过用clang编译程序,但用clang的结果总是约1500us(在i7-8700k上)。

我的测试程序:

#include <chrono>
#include <iostream>
#include <intrin.h>
using namespace std;

template<int N>
__forceinline void noops()
{
    __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop();
    noops<N - 1>();
}
template<>
__forceinline void noops<0>(){}

template<int OFFSET>
__declspec(noinline) void SumHorizontalLine(const unsigned char* __restrict src, int width, int a, unsigned short* __restrict dst)
{
    unsigned short sum = 0;
    const unsigned char* srcP1 = src - a - 1;
    const unsigned char* srcP2 = src + a;

    //some dummy loop,just a few iterations
    for (int i = 0; i < a; ++i)
        dst[i] = src[i] / (double)dst[i];

    noops<OFFSET>();
    //the important loop
    for (int x = a + 1; x < width - a; x++)
    {
        unsigned char v1 = srcP1[x];
        unsigned char v2 = srcP2[x];
        sum -= v1;
        sum += v2;
        dst[x] = sum;
    }

}

template<int OFFSET>
void RunTest(unsigned char* __restrict src, int width, int a, unsigned short* __restrict dst)
{
    double minTime = 99999999;
    for(int i = 0; i < 20; ++i)
    {
        auto start = chrono::steady_clock::now();

        for (int i = 0; i < 1024; ++i)
        {
            SumHorizontalLine<OFFSET>(src, width, a, dst);
        }

        auto end = chrono::steady_clock::now();
        auto us = chrono::duration_cast<chrono::microseconds>(end - start).count();
        if (us < minTime)
        {
            minTime = us;
        }
    }

    cout << OFFSET << " : " << minTime << " us" << endl;
}

int main()
{
    const int width = 2048;
    const int x = 3;
    unsigned char* src = new unsigned char[width * 5];
    unsigned short* dst = new unsigned short[width];
    memset(src, 0, sizeof(unsigned char) * width);
    memset(dst, 0, sizeof(unsigned short) * width);

    while(true)
    RunTest<1>(src, width, x, dst);
}

要验证不同的对齐方式,只需重新编译程序并更改RunTest

为OFFSET=1的循环生成的汇编代码(对于其他偏移量,只有npad的数量不同):

  0007c 90       npad    1
  0007d 90       npad    1
  0007e 49 83 c1 08  add     r9, 8
  00082 90       npad    1
  00083 90       npad    1
  00084 90       npad    1
  00085 90       npad    1
  00086 90       npad    1
  00087 90       npad    1
  00088 90       npad    1
  00089 90       npad    1
  0008a 90       npad    1
  0008b 90       npad    1
  0008c 90       npad    1
  0008d 90       npad    1
  0008e 90       npad    1
  0008f 90       npad    1
$LL15@SumHorizon:

; 25   : 
; 26   :    noops<OFFSET>();
; 27   : 
; 28   :    for (int x = a + 1; x < width - a; x++)
; 29   :    {
; 30   :        unsigned char v1 = srcP1[x];
; 31   :        unsigned char v2 = srcP2[x];
; 32   :        sum -= v1;

  00090 0f b6 42 f9  movzx   eax, BYTE PTR [rdx-7]
  00094 4d 8d 49 02  lea     r9, QWORD PTR [r9+2]

; 33   :        sum += v2;

  00098 0f b6 0a     movzx   ecx, BYTE PTR [rdx]
  0009b 48 8d 52 01  lea     rdx, QWORD PTR [rdx+1]
  0009f 66 2b c8     sub     cx, ax
  000a2 66 44 03 c1  add     r8w, cx

; 34   :        dst[x] = sum;

  000a6 66 45 89 41 fe   mov     WORD PTR [r9-2], r8w
  000ab 49 83 ea 01  sub     r10, 1
  000af 75 df        jne     SHORT $LL15@SumHorizon

; 35   :    }
; 36   : 
; 37   : }

  000b1 c3       ret     0
??$SumHorizontalLine@$00@@YAXPEIBEHHPEIAG@Z ENDP    ; SumHorizont

共有2个答案

任飞鸣
2023-03-14

我认为我的编译器足够聪明,可以正确地对齐代码。

正如你所说,编译器总是将事物对齐到16字节的倍数。这可能确实解释了对齐的直接影响。但是编译器的“智能”是有限制的。

除了对齐之外,代码放置也有间接的性能影响,因为缓存关联性。如果可以映射到该地址的少数缓存行的争用过多,性能就会受到影响。移动到争用较少的地址会使问题消失。

编译器可能足够聪明,可以处理缓存争用效应,但前提是启用概要文件引导优化。这些相互作用过于复杂,在合理的工作量内无法预测;通过实际运行程序,可以更容易地监视缓存冲突,而PGO就是这样做的。

杨良平
2023-03-14

在慢速情况下(即00007FF7750B1280和00007FF7750B12A0),jne指令跨越32字节边界。“跳转条件代码”(JCC)勘误表的缓解措施(https://www.intel.com/content/dam/support/us/en/documents/processors/mitigations-jump-conditional-code-erratum.pdf)防止在DSB中缓存此类指令。JCC勘误表仅适用于基于Skylake的CPU,这就是为什么这种影响不会在i5-3570k CPU上发生。

正如彼得·科尔德斯(PeterCordes)在一篇评论中指出的那样,最近的编译器有一些选项试图减轻这种影响。英特尔JCC勘误表-JCC真的应该单独对待吗?提及MSVC的QIntel jcc勘误表选项;另一个相关问题是,我如何减轻英特尔jcc勘误表对gcc的影响?

 类似资料:
  • `多处理系统中,使用并发的方式来提高代码的效率时,你需要了解一下有哪些因素会影响并发的效率。即使已经使用多线程对关注进行分离,还需要确定是否会对性能造成负面影响。因为,在16核机器上应用的速度与单核机器相当时,用户是不会打死你的。 之后你会看到,在多线程代码中有很多因素会影响性能——对线程处理的数据做一些简单的改动(其他不变),都可能对性能产生戏剧性的效果。所以,多言无益,让我们来看一下这些因素吧

  • 使用时,是否有需要考虑的性能影响? 我正在编写一个从目录检索文件的查询,这就是查询: 那么,在决定进行这样的转换时,是否应该考虑某种性能影响--还是只在处理大量文件时才考虑?这是一个可以忽略不计的转换吗?

  • 问题内容: 我有这个JavaWeb应用程序,它可以从电子表格上传成千上万的数据,该电子表格是从上到下按行读取的。我用来在服务器端显示应用程序当前正在读取的行。 -我知道要创建一个日志文件。实际上,我正在创建一个日志文件,同时在服务器提示符下显示日志。 还有其他方法可以在提示上打印当前数据? 问题答案: 它可能会影响您的应用程序性能。大小会因您所运行的硬件类型和主机上的负载而异。 可以将其转化为性能

  • 前言 HTTPS 在保护用户隐私,防止流量劫持方面发挥着非常关键的作用,但与此同时,HTTPS 也会降低用户访问速度,增加网站服务器的计算资源消耗。 本文主要介绍 https 对用户体验的影响。 HTTPS 对访问速度的影响 在介绍速度优化策略之前,先来看下 HTTPS 对速度有什么影响。影响主要来自两方面: 协议交互所增加的网络 RTT(round trip time)。 加解密相关的计算耗时。

  • 我编写了所需的带有注释的接口和作为装饰器的抽象类。生成(mvn清理包)后,通过“默认”过程更新修饰的函数,得到的是参数和结果类型。我不知道,有什么问题。你能帮帮我吗? 环境:mapstruct版本1.4.2。lombok最终版本1.18.22(Spring boot 2.6.3)lombok mapstruct绑定:0.2.0 和 Mapper接口声明: 装饰师: } 以及生成的源:

  • 问题内容: varchar列上的索引是否会使查询运行缓慢?我可以将其设为int。而且我不需要做LIKE%比较。 问题答案: varchar列上的索引是否会使查询运行缓慢? 不,不是的。 如果优化器决定使用索引,则查询将运行得更快。 该表上的s / s / s会变慢,但不太可能引起注意。 我不需要做LIKE%比较 请注意,使用: …将 不 使用索引,但以下内容将: 关键是在字符串的左侧使用通配符,这