【问题标题】:How can a variable be accessed faster via a pointer then straightly?如何通过指针更快地访问变量然后直接?
【发布时间】:2017-03-15 12:49:58
【问题描述】:

我正在使用下一个程序来测试不同的简单动作的速度。这里我测试将变量加载到寄存器中的速度:

#include <iostream>
#include <Windows.h>

void main()
{
    DWORD _time;

    int val = 1;
    int* ptra = &val;
    for (auto a = 0; a < 20; a++)
    {
        _time = GetTickCount();
        for (auto i = 0; i < 100000000; i++)
        {
            _asm
            {
                mov         eax, val
            }
        }
        _time = GetTickCount() - _time;
        std::cout << _time << std::endl;
    };
    std::cout << buf << std::endl;
    system("pause");
    for (auto a = 0; a < 20; a++)
    {
        _time = GetTickCount();
        for (auto i = 0; i < 100000000; i++)
        {
            _asm
            {
                mov     eax, dword ptr[ptra]
                mov     ebx, dword ptr[eax]
            }
        }
        _time = GetTickCount() - _time;
        std::cout << _time << std::endl;
    };
    std::cout << buf << std::endl;
    system("pause");
}

在我的电脑上测试的平均值是 234 203

由于某种原因,它通过指针变得更快,然后是直线。我已经在两台 PC 上测试了这个并得到了相同的结果。起初我认为一些 CP 优化正在起作用,但这意味着使用指针比变量本身更有效,听起来很尴尬。现在我想我做错了什么,可能是 GetTickCount() 有问题,无论如何,我没有找到任何可以帮助理解发生了什么的东西,也没有人能解释这个。

双指针的结果与直接访问的结果相同 指针链越长,工作越慢

将 val 替换为 1 会增加一些速度,但仍然比指针慢

空循环需要相同的时间才能完成。任何更改都不会影响行为。

这是我目前正在使用的代码

#include <iostream>
#include <Windows.h>

int* ptra;
int val;

void main()
{
    DWORD _time;

    val = 1;
    ptra = &val;

    HANDLE TH = GetCurrentThread();
    HANDLE PH = GetCurrentProcess();
    PDWORD_PTR APMask = new ULONG_PTR;
    PDWORD_PTR ASMask = new ULONG_PTR;
    ULONG_PTR Core = 1;
    GetProcessAffinityMask(PH, APMask, ASMask);
    while (!(Core && *APMask)) Core = Core << 1;
    SetThreadAffinityMask(TH, Core);
    SetThreadPriority(TH, THREAD_PRIORITY_TIME_CRITICAL);
    SetPriorityClass(PH, REALTIME_PRIORITY_CLASS);
    DWORD64 ProcessorTime;
#define order 0
#define loops 10000000

    //=========================================
    for (auto a = 0; a < 20; a++)
    {
        //_time = GetTickCount();
        ProcessorTime = __rdtsc();
        for (auto i = 0; i < loops; i++)
        {
            _asm
            {
#if order == 1
                mov     eax, dword ptr[ptra]
                mov     ebx, dword ptr[eax]
#else
                mov         eax, val
#endif
            }
        }
        //_time = GetTickCount() - _time;
        ProcessorTime = __rdtsc() - ProcessorTime;
        std::cout << ProcessorTime << std::endl;
    };
    //system("pause");
    //=========================================
    std::cout << "=" << std::endl;
    //=========================================
    for (auto a = 0; a < 20; a++)
    {
        //_time = GetTickCount();
        ProcessorTime = __rdtsc();
        for (auto i = 0; i < loops; i++)
        {
            _asm
            {
#if order == 1
                mov         eax, val
#else
                mov     eax, dword ptr[ptra]
                mov     ebx, dword ptr[eax]
#endif
            }
        }
        //_time = GetTickCount() - _time;
        ProcessorTime = __rdtsc() - ProcessorTime;
        std::cout << ProcessorTime << std::endl;
    };
    //=========================================
    SetPriorityClass(PH, NORMAL_PRIORITY_CLASS);
    SetThreadPriority(TH, THREAD_PRIORITY_NORMAL);
    SetThreadAffinityMask(TH, *APMask);
    system("pause");
}

汇编代码:

; 5    : {

    push    ebp
    mov ebp, esp
    sub esp, 124                ; 0000007cH
    mov eax, DWORD PTR ___security_cookie
    xor eax, ebp
    mov DWORD PTR __$ArrayPad$[ebp], eax
    push    ebx
    push    esi
    push    edi

; 6    :    //DWORD _time;
; 7    : 
; 8    :    int* ptra;
; 9    :    int val;
; 10   : 
; 11   :    val = 1;

    mov DWORD PTR _val$[ebp], 1

; 12   :    ptra = &val;

    lea eax, DWORD PTR _val$[ebp]
    mov DWORD PTR _ptra$[ebp], eax

; 13   :    short unsigned a;
; 14   : 
; 15   :    HANDLE TH = GetCurrentThread();

    call    DWORD PTR __imp__GetCurrentThread@0
    mov DWORD PTR _TH$[ebp], eax

; 16   :    HANDLE PH = GetCurrentProcess();

    call    DWORD PTR __imp__GetCurrentProcess@0
    mov DWORD PTR _PH$[ebp], eax

; 17   :    PDWORD_PTR APMask = new ULONG_PTR;

    push    4
    call    ??2@YAPAXI@Z                ; operator new
    add esp, 4
    mov DWORD PTR $T2[ebp], eax
    mov eax, DWORD PTR $T2[ebp]
    mov DWORD PTR _APMask$[ebp], eax

; 18   :    PDWORD_PTR ASMask = new ULONG_PTR;

    push    4
    call    ??2@YAPAXI@Z                ; operator new
    add esp, 4
    mov DWORD PTR $T1[ebp], eax
    mov eax, DWORD PTR $T1[ebp]
    mov DWORD PTR _ASMask$[ebp], eax

; 19   :    ULONG_PTR Core = 1;

    mov DWORD PTR _Core$[ebp], 1

; 20   :    GetProcessAffinityMask(PH, APMask, ASMask);

    mov eax, DWORD PTR _ASMask$[ebp]
    push    eax
    mov ecx, DWORD PTR _APMask$[ebp]
    push    ecx
    mov edx, DWORD PTR _PH$[ebp]
    push    edx
    call    DWORD PTR __imp__GetProcessAffinityMask@12
$LN2@main:

; 21   :    while (!(Core && *APMask)) Core = Core << 1;

    cmp DWORD PTR _Core$[ebp], 0
    je  SHORT $LN16@main
    mov eax, DWORD PTR _APMask$[ebp]
    cmp DWORD PTR [eax], 0
    jne SHORT $LN3@main
$LN16@main:
    mov eax, DWORD PTR _Core$[ebp]
    shl eax, 1
    mov DWORD PTR _Core$[ebp], eax
    jmp SHORT $LN2@main
$LN3@main:

; 22   :    SetThreadAffinityMask(TH, Core);

    mov eax, DWORD PTR _Core$[ebp]
    push    eax
    mov ecx, DWORD PTR _TH$[ebp]
    push    ecx
    call    DWORD PTR __imp__SetThreadAffinityMask@8

; 23   :    SetThreadPriority(TH, THREAD_PRIORITY_TIME_CRITICAL);

    push    15                  ; 0000000fH
    mov eax, DWORD PTR _TH$[ebp]
    push    eax
    call    DWORD PTR __imp__SetThreadPriority@8

; 24   :    SetPriorityClass(PH, REALTIME_PRIORITY_CLASS);

    push    256                 ; 00000100H
    mov eax, DWORD PTR _PH$[ebp]
    push    eax
    call    DWORD PTR __imp__SetPriorityClass@8

; 25   :    DWORD64 ProcessorTime;
; 26   : #define order 0
; 27   : #define loops 10000000
; 28   : #define tests 50
; 29   : 
; 30   :    //=========================================
; 31   :    for (a = 0; a < tests; a++)

    xor eax, eax
    mov WORD PTR _a$[ebp], ax
    jmp SHORT $LN6@main
$LN4@main:
    mov ax, WORD PTR _a$[ebp]
    add ax, 1
    mov WORD PTR _a$[ebp], ax
$LN6@main:
    movzx   eax, WORD PTR _a$[ebp]
    cmp eax, 50                 ; 00000032H
    jge SHORT $LN5@main

; 32   :    {
; 33   :        //_time = GetTickCount();
; 34   :        ProcessorTime = __rdtsc();

    rdtsc
    mov DWORD PTR _ProcessorTime$[ebp], eax
    mov DWORD PTR _ProcessorTime$[ebp+4], edx

; 35   :        for (auto i = 0; i < loops; i++)

    mov DWORD PTR _i$4[ebp], 0
    jmp SHORT $LN9@main
$LN7@main:
    mov eax, DWORD PTR _i$4[ebp]
    add eax, 1
    mov DWORD PTR _i$4[ebp], eax
$LN9@main:
    cmp DWORD PTR _i$4[ebp], 10000000       ; 00989680H
    jge SHORT $LN8@main

; 36   :        {
; 37   :            _asm
; 38   :            {
; 39   : #if order == 1
; 40   :                mov     eax, dword ptr[ptra]
; 41   :                mov     ebx, dword ptr[eax]
; 42   : #else
; 43   :                mov         eax, val

    mov eax, DWORD PTR _val$[ebp]

; 44   : #endif
; 45   :            }
; 46   :        }

    jmp SHORT $LN7@main
$LN8@main:

; 47   :        //_time = GetTickCount() - _time;
; 48   :        ProcessorTime = __rdtsc() - ProcessorTime;

    rdtsc
    sub eax, DWORD PTR _ProcessorTime$[ebp]
    sbb edx, DWORD PTR _ProcessorTime$[ebp+4]
    mov DWORD PTR _ProcessorTime$[ebp], eax
    mov DWORD PTR _ProcessorTime$[ebp+4], edx

; 49   :        std::cout << ProcessorTime << std::endl;

    push    OFFSET ??$endl@DU?$char_traits@D@std@@@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@@Z ; std::endl<char,std::char_traits<char> >
    mov eax, DWORD PTR _ProcessorTime$[ebp+4]
    push    eax
    mov ecx, DWORD PTR _ProcessorTime$[ebp]
    push    ecx
    mov ecx, DWORD PTR __imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A
    call    DWORD PTR __imp_??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@_K@Z
    mov ecx, eax
    call    DWORD PTR __imp_??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@P6AAAV01@AAV01@@Z@Z

; 50   :    };

    jmp SHORT $LN4@main
$LN5@main:

; 51   :    //system("pause");
; 52   :    //=========================================
; 53   :    std::cout << "=" << std::endl;

    push    OFFSET ??$endl@DU?$char_traits@D@std@@@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@@Z ; std::endl<char,std::char_traits<char> >
    push    OFFSET ??_C@_01NEMOKFLO@?$DN?$AA@
    mov eax, DWORD PTR __imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A
    push    eax
    call    ??$?6U?$char_traits@D@std@@@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@PBD@Z ; std::operator<<<std::char_traits<char> >
    add esp, 8
    mov ecx, eax
    call    DWORD PTR __imp_??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@P6AAAV01@AAV01@@Z@Z

; 54   :    //=========================================
; 55   :    for (a = 0; a < tests; a++)

    xor eax, eax
    mov WORD PTR _a$[ebp], ax
    jmp SHORT $LN12@main
$LN10@main:
    mov ax, WORD PTR _a$[ebp]
    add ax, 1
    mov WORD PTR _a$[ebp], ax
$LN12@main:
    movzx   eax, WORD PTR _a$[ebp]
    cmp eax, 50                 ; 00000032H
    jge SHORT $LN11@main

; 56   :    {
; 57   :        //_time = GetTickCount();
; 58   :        ProcessorTime = __rdtsc();

    rdtsc
    mov DWORD PTR _ProcessorTime$[ebp], eax
    mov DWORD PTR _ProcessorTime$[ebp+4], edx

; 59   :        for (auto i = 0; i < loops; i++)

    mov DWORD PTR _i$3[ebp], 0
    jmp SHORT $LN15@main
$LN13@main:
    mov eax, DWORD PTR _i$3[ebp]
    add eax, 1
    mov DWORD PTR _i$3[ebp], eax
$LN15@main:
    cmp DWORD PTR _i$3[ebp], 10000000       ; 00989680H
    jge SHORT $LN14@main

; 60   :        {
; 61   :            _asm
; 62   :            {
; 63   : #if order == 1
; 64   :                mov         eax, val
; 65   : #else
; 66   :                mov     eax, dword ptr[ptra]

    mov eax, DWORD PTR _ptra$[ebp]

; 67   :                mov     ebx, dword ptr[eax]

    mov ebx, DWORD PTR [eax]

; 68   : #endif
; 69   :            }
; 70   :        }

    jmp SHORT $LN13@main
$LN14@main:

; 71   :        //_time = GetTickCount() - _time;
; 72   :        ProcessorTime = __rdtsc() - ProcessorTime;

    rdtsc
    sub eax, DWORD PTR _ProcessorTime$[ebp]
    sbb edx, DWORD PTR _ProcessorTime$[ebp+4]
    mov DWORD PTR _ProcessorTime$[ebp], eax
    mov DWORD PTR _ProcessorTime$[ebp+4], edx

; 73   :        std::cout << ProcessorTime << std::endl;

    push    OFFSET ??$endl@DU?$char_traits@D@std@@@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@@Z ; std::endl<char,std::char_traits<char> >
    mov eax, DWORD PTR _ProcessorTime$[ebp+4]
    push    eax
    mov ecx, DWORD PTR _ProcessorTime$[ebp]
    push    ecx
    mov ecx, DWORD PTR __imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A
    call    DWORD PTR __imp_??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@_K@Z
    mov ecx, eax
    call    DWORD PTR __imp_??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@P6AAAV01@AAV01@@Z@Z

; 74   :    };

    jmp SHORT $LN10@main
$LN11@main:

; 75   :    //=========================================
; 76   :    SetPriorityClass(PH, NORMAL_PRIORITY_CLASS);

    push    32                  ; 00000020H
    mov eax, DWORD PTR _PH$[ebp]
    push    eax
    call    DWORD PTR __imp__SetPriorityClass@8

; 77   :    SetThreadPriority(TH, THREAD_PRIORITY_NORMAL);

    push    0
    mov eax, DWORD PTR _TH$[ebp]
    push    eax
    call    DWORD PTR __imp__SetThreadPriority@8

; 78   :    SetThreadAffinityMask(TH, *APMask);

    mov eax, DWORD PTR _APMask$[ebp]
    mov ecx, DWORD PTR [eax]
    push    ecx
    mov edx, DWORD PTR _TH$[ebp]
    push    edx
    call    DWORD PTR __imp__SetThreadAffinityMask@8

; 79   :    system("pause");

    push    OFFSET ??_C@_05PDJBBECF@pause?$AA@
    call    DWORD PTR __imp__system
    add esp, 4

; 80   : }

    jmp SHORT $LN19@main
    jmp SHORT $LN18@main
$LN19@main:
    xor eax, eax
$LN18@main:
    pop edi
    pop esi
    pop ebx
    mov ecx, DWORD PTR __$ArrayPad$[ebp]
    xor ecx, ebp
    call    @__security_check_cookie@4
    mov esp, ebp
    pop ebp
    ret 0

我在 MSVC++ 14.0 上得到了这个,所有优化都被取消了(它削减了所有漫无目的的代码,测试代码也是如此)。

【问题讨论】:

  • 衡量未优化代码的意义何在?
  • 那么如何优化呢?
  • 天真地比较平均值可能会给您带来非常错误的结果。你测量了多少?差异是什么?您是否尝试排除系统错误,例如执行这两个测试的顺序是否重要?
  • @DanilК 在您的编译器中启用优化。
  • 请注意,您在这里测量的是吞吐量,而不是延迟。两者的吞吐量(假设代码已优化)可能是 1 次迭代/周期,受到循环分支的瓶颈。即使使用优化的代码,它也是一个相当无用的基准。

标签: c++ performance pointers assembly


【解决方案1】:

您没有在相关代码中使用循环对齐,这可能会在结果中产生足够的不变性,从而使它们变得毫无意义。 $LN13@main:$LN7@main: 必须以相同的方式对齐(取决于您的平台,多少,实际上您可能应该在 ProcessorTime = __rdtsc(); 之前对齐整个块,并且可能在两种情况下都跳转到该地址(在清除后缓存或预热它们取决于您要测量的内容)。

也就是说,在现代 x86 上,您的两个代码都处于指令级别,甚至没有使单核饱和,因此 mov 很可能与现实世界代码中的其他指令一起执行。通过访问堆栈内存和依赖项来消耗所有“时间”。例如,在第二种间接情况下,如果您只使用 eax 作为两个 mov 的目的地,那么由于更大的冲突,它可能会减慢它的速度.. 嗯.. 可能不会,CPU 仍然有许多备用寄存器用于每个新的循环迭代,因此它可能会通过每次重命名eax 来避免任何虚假的依赖/冲突。

总的来说,衡量“脚手架”是没有意义的,你应该衡量真正的算法在做什么,因为你衡量的东西几乎不会再多花+1个周期,这很可能会在数十/数百个循环的瓶颈中丢失真正的代码。

【讨论】:

  • 真正的代码由相似的小部分组成,所以如果我想写一个好的代码,我应该使用合适的部分,我只是想知道这些部分的特点。它已经解决了 - 差异是由循环引起的;不知道确切的原因,我可能很幸运找到了一些特定的东西,这些东西永远不会适用于真正的代码。我用重复的指令替换了循环,一切都从一开始就按照预期的方式进行了。
  • @DanilК 循环的对齐方式将与您描述的完全一样,并且可以通过展开来解决(在循环整个展开的块时,对齐很少会碰到您)。关于编写代码......但是当你实现一些算法时,你只需要使用那些指令吗?你不会写额外的无意义的。除非您将编程理解为“编写指令”,否则恐怕您会这样做。无论如何,这种规模的“小”部分是无用的,因为如果将它们一起编写,它们的执行特性会发生巨大变化(在现代 x86 CPU 上)。
  • @DanilК 您的方法可以在高达 80486 的地方工作,在 Pentium (586) 上,您的结果可能已经出现偏差,并且大约从 786 开始,您在 ASM 源代码中编写的内容实际上与 CPU 的内容相差甚远使用,将 x86 指令动态编译为微操作,重新排序它们等(只需尝试将原始循环对齐到 256B 边界(8 可能就足够了,但只是为了确定),看看它是否“修复”结果)
  • 好吧,写算法我,通常,有写什么的变种。这个程序诞生于测试一个小算法,当我得到奇怪的结果并找到了罪魁祸首,正如我所想的。这是第一个有一次我遇到了这样的事情:我的意思是我现在知道处理器是如何工作的,但它是不同的 - 知道并在你的屏幕上看到它,特别是当你不知道细节时,对于 fitr 时间,这就是我迷失方向的原因.从一开始的错误就在于测试代码的大小以及缺乏经验。
  • 如果你告诉我如何更改代码对齐方式我会很高兴,快速谷歌搜索没有发现任何问题,所以我需要时间来测试你的建议。虽然我必须研究它无论如何。
猜你喜欢
  • 2018-07-04
  • 2013-01-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-02
  • 1970-01-01
  • 1970-01-01
  • 2010-12-17
相关资源
最近更新 更多