当前位置:  开发笔记 > 编程语言 > 正文

如何在循环中添加代码使其更快?

如何解决《如何在循环中添加代码使其更快?》经验,为你挑选了1个好方法。

我有一个带内循环的简单函数 - 它缩放输入值,在查找表中查找输出值,并将其复制到目标.(ftol_ambient是我从网上复制的一种技巧,用于将float快速转换为int).

for (i = 0;  i < iCount;  ++i)
{
    iScaled = ftol_ambient(*pSource * PRECISION3);
    if (iScaled <= 0)
        *pDestination = 0;
    else if (iScaled >= PRECISION3)
        *pDestination = 255;
    else
    {
        iSRGB = FloatToSRGBTable3[iScaled];
        *pDestination = iSRGB;
    }
    pSource++;
    pDestination++;
}

现在我的查找表是有限的,并且浮点数是无限的,因此有可能出现一个一个错误.我用一些代码创建了一个函数副本来处理这种情况.请注意,唯一的区别是添加了2行代码 - 请忽略丑陋的指针转换.

for (i = 0;  i < iCount;  ++i)
{
    iScaled = ftol_ambient(*pSource * PRECISION3);
    if (iScaled <= 0)
        *pDestination = 0;
    else if (iScaled >= PRECISION3)
        *pDestination = 255;
    else
    {
        iSRGB = FloatToSRGBTable3[iScaled];
        if (((int *)SRGBCeiling)[iSRGB] <= *((int *)pSource))
            ++iSRGB;
        *pDestination = (unsigned char) iSRGB;
    }
    pSource++;
    pDestination++;
}

这是奇怪的部分.我正在测试两个版本,输入相同的100000个元素,重复100次.在我的Athlon 64 1.8 GHz(32位模式)上,第一个功能需要0.231秒,第二个(更长)功能需要0.185秒.两个函数在相同的源文件中相邻,因此不可能有不同的编译器设置.我已经多次运行测试,扭转它们运行的​​顺序,每次的时间大致相同.

我知道现代处理器有很多神秘之处,但这怎么可能呢?

这里用于比较Microsoft VC++ 6编译器的相关汇编器输出.


; 173  :    for (i = 0;  i < iCount;  ++i)

$L4455:

; 174  :    {
; 175  :        iScaled = ftol_ambient(*pSource * PRECISION3);

    fld DWORD PTR [esi]
    fmul    DWORD PTR __real@4@400b8000000000000000
    fstp    QWORD PTR $T5011[ebp]

; 170  :    int i;
; 171  :    int iScaled;
; 172  :    unsigned int iSRGB;

    fld QWORD PTR $T5011[ebp]

; 173  :    for (i = 0;  i < iCount;  ++i)

    fistp   DWORD PTR _i$5009[ebp]

; 176  :        if (iScaled <= 0)

    mov edx, DWORD PTR _i$5009[ebp]
    test    edx, edx
    jg  SHORT $L4458

; 177  :            *pDestination = 0;

    mov BYTE PTR [ecx], 0

; 178  :        else if (iScaled >= PRECISION3)

    jmp SHORT $L4461
$L4458:
    cmp edx, 4096               ; 00001000H
    jl  SHORT $L4460

; 179  :            *pDestination = 255;

    mov BYTE PTR [ecx], 255         ; 000000ffH

; 180  :        else

    jmp SHORT $L4461
$L4460:

; 181  :        {
; 182  :            iSRGB = FloatToSRGBTable3[iScaled];
; 183  :            *pDestination = (unsigned char) iSRGB;

    mov dl, BYTE PTR _FloatToSRGBTable3[edx]
    mov BYTE PTR [ecx], dl
$L4461:

; 184  :        }
; 185  :        pSource++;

    add esi, 4

; 186  :        pDestination++;

    inc ecx
    dec edi
    jne SHORT $L4455

$L4472:

; 199  :    {
; 200  :        iScaled = ftol_ambient(*pSource * PRECISION3);

    fld DWORD PTR [esi]
    fmul    DWORD PTR __real@4@400b8000000000000000
    fstp    QWORD PTR $T4865[ebp]

; 195  :    int i;
; 196  :    int iScaled;
; 197  :    unsigned int iSRGB;

    fld QWORD PTR $T4865[ebp]

; 198  :    for (i = 0;  i < iCount;  ++i)

    fistp   DWORD PTR _i$4863[ebp]

; 201  :        if (iScaled <= 0)

    mov edx, DWORD PTR _i$4863[ebp]
    test    edx, edx
    jg  SHORT $L4475

; 202  :            *pDestination = 0;

    mov BYTE PTR [edi], 0

; 203  :        else if (iScaled >= PRECISION3)

    jmp SHORT $L4478
$L4475:
    cmp edx, 4096               ; 00001000H
    jl  SHORT $L4477

; 204  :            *pDestination = 255;

    mov BYTE PTR [edi], 255         ; 000000ffH

; 205  :        else

    jmp SHORT $L4478
$L4477:

; 206  :        {
; 207  :            iSRGB = FloatToSRGBTable3[iScaled];

    xor ecx, ecx
    mov cl, BYTE PTR _FloatToSRGBTable3[edx]

; 208  :            if (((int *)SRGBCeiling)[iSRGB] <= *((int *)pSource))

    mov edx, DWORD PTR _SRGBCeiling[ecx*4]
    cmp edx, DWORD PTR [esi]
    jg  SHORT $L4481

; 209  :                ++iSRGB;

    inc ecx
$L4481:

; 210  :            *pDestination = (unsigned char) iSRGB;

    mov BYTE PTR [edi], cl
$L4478:

; 211  :        }
; 212  :        pSource++;

    add esi, 4

; 213  :        pDestination++;

    inc edi
    dec eax
    jne SHORT $L4472


编辑:试图测试Nils Pipenbrinck的假设,我在第一个函数的循环之前和之内添加了几行:

int one = 1;
int two = 2;

        if (one == two)
            ++iSRGB;

第一个功能的运行时间现在下降到0.152秒.有趣.


编辑2: Nils指出比较将在发布版本中进行优化,实际上是.汇编代码中的更改非常微妙,我将在此处发布,以查看它是否提供了任何线索.在这一点上,我想知道它是否是代码对齐?

; 175  :    for (i = 0;  i < iCount;  ++i)

$L4457:

; 176  :    {
; 177  :        iScaled = ftol_ambient(*pSource * PRECISION3);

    fld DWORD PTR [edi]
    fmul    DWORD PTR __real@4@400b8000000000000000
    fstp    QWORD PTR $T5014[ebp]

; 170  :    int i;
; 171  :    int iScaled;
; 172  :    int one = 1;

    fld QWORD PTR $T5014[ebp]

; 173  :    int two = 2;

    fistp   DWORD PTR _i$5012[ebp]

; 178  :        if (iScaled <= 0)

    mov esi, DWORD PTR _i$5012[ebp]
    test    esi, esi
    jg  SHORT $L4460

; 179  :            *pDestination = 0;

    mov BYTE PTR [edx], 0

; 180  :        else if (iScaled >= PRECISION3)

    jmp SHORT $L4463
$L4460:
    cmp esi, 4096               ; 00001000H
    jl  SHORT $L4462

; 181  :            *pDestination = 255;

    mov BYTE PTR [edx], 255         ; 000000ffH

; 182  :        else

    jmp SHORT $L4463
$L4462:

; 183  :        {
; 184  :            iSRGB = FloatToSRGBTable3[iScaled];

    xor ecx, ecx
    mov cl, BYTE PTR _FloatToSRGBTable3[esi]

; 185  :            if (one == two)
; 186  :                ++iSRGB;
; 187  :            *pDestination = (unsigned char) iSRGB;

    mov BYTE PTR [edx], cl
$L4463:

; 188  :        }
; 189  :        pSource++;

    add edi, 4

; 190  :        pDestination++;

    inc edx
    dec eax
    jne SHORT $L4457

Nils Pipenbr.. 11

我的猜测是,在第一种情况下,两个不同的分支最终在CPU的同一分支预测槽中.如果这两个分支在每次代码减速时预测不同.

在第二循环中,添加的代码可能足以将一个分支移动到不同的分支预测时隙.

确保您可以尝试使用英特尔VTune分析器或AMD CodeAnalyst工具.这些工具将向您显示代码中的确切内容.

但是,请记住,进一步优化此代码很可能不值得.如果您在CPU上调整代码速度更快,则可能会在不同品牌上变慢.


编辑:

如果您想阅读分支预测,请尝试Agner Fog的优秀网站:http://www.agner.org/optimize/

该pdf详细解释了分支预测时隙分配:http://www.agner.org/optimize/microarchitecture.pdf



1> Nils Pipenbr..:

我的猜测是,在第一种情况下,两个不同的分支最终在CPU的同一分支预测槽中.如果这两个分支在每次代码减速时预测不同.

在第二循环中,添加的代码可能足以将一个分支移动到不同的分支预测时隙.

确保您可以尝试使用英特尔VTune分析器或AMD CodeAnalyst工具.这些工具将向您显示代码中的确切内容.

但是,请记住,进一步优化此代码很可能不值得.如果您在CPU上调整代码速度更快,则可能会在不同品牌上变慢.


编辑:

如果您想阅读分支预测,请尝试Agner Fog的优秀网站:http://www.agner.org/optimize/

该pdf详细解释了分支预测时隙分配:http://www.agner.org/optimize/microarchitecture.pdf

推荐阅读
路人甲
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有