两个代码段之间执行时间的奇怪差异

zom*_*bom 7 c++ if-statement execution-time

因此,我希望通过在比较之前不将变量的值复制到另一个变量中来提高程序性能的程度(这将在示例中更好地解释),并且我注意到一些奇怪的东西.我有这两个代码段:

string a = "";
for (int i = 0; i < 1000000; i++) a += 'a';

for (int i = 0; i < 1000000; i++) {
    if ('b' == a.at(i));//compare the two chars directly
}
Run Code Online (Sandbox Code Playgroud)

string a = "";
for (int i = 0; i < 100000000; i++) a += 'a';

for (int i = 0; i < 100000000; i++) {
    char c = a.at(i);//declare a new variable
    if ('b' == c);//compare the char with the newly created variable,
                  //instead of comparing it to the other char directly
}
Run Code Online (Sandbox Code Playgroud)

我认为第二段需要更长的时间来执行,因为与第一段相比,还有一个变量被声明.当我实际计算两个时,我发现第二个花费的时间少于第一个.我计时了几次,而第二次似乎总是花费大约0.13秒的时间来执行.这是完整的代码:

#include <string>
#include <iostream>
#include <ctime>

using namespace std;

int main() {
    clock_t timer;

    string a = "";
    string b;

    for (int i = 0; i < 100000000; i++)
        a += "a";

    timer = clock();

    for (int i = 0; i < 100000000; i++) {
        if ('b'==a.at(i)) b += "a";
    }

    cout << (clock()-timer)/(float)CLOCKS_PER_SEC << "sec" << endl;

    timer = clock();

    for (int i = 0; i < 100000000; i++) {
        char c = a.at(i);
        if ('b'==c) b += "a";
    }

    cout << (clock()-timer)/(float)CLOCKS_PER_SEC << "sec" << endl;

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

为什么会这样?

编辑:我遵循NathanOliver的建议,我为每个循环添加了单独的字符串,所以现在代码如下所示:

#include <string>
#include <iostream>
#include <ctime>

using namespace std;

int main() {
    clock_t timer;

    string compare_string_1 = "";
    string compare_string_2 = "";
    string segment_1 = "";
    string segment_2 = "";

    for (int i = 0; i < 100000000; i++)
        compare_string_1 += "a";

    for (int i = 0; i < 100000000; i++)
        compare_string_2 += "a";

    timer = clock();

    for (int i = 0; i < 100000000; i++) {
        if ('b'==compare_string_1.at(i)) segment_1 += "a";
    }

    cout << (clock()-timer)/(float)CLOCKS_PER_SEC << "sec" << endl;

    timer = clock();

    for (int i = 0; i < 100000000; i++) {
        char c = compare_string_2.at(i);
        if ('b'==c) segment_2 += "a";
    }

    cout << (clock()-timer)/(float)CLOCKS_PER_SEC << "sec" << endl;

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

Ken*_*ima 2

使用 Visual C++ 2010,我得到了与上面评论中相同的计时结果 - 平均而言,第二个循环占用了第一个循环大约 80% 的运行时间。一两次,第一个循环有点快,但这可能是由于操作系统中的一些线程中断造成的。检查反汇编结果如下:

第一个循环:

01231120  cmp         dword ptr [ebp-38h],esi  
01231123  jbe         main+1CBh (123120Bh)  
01231129  cmp         dword ptr [ebp-34h],10h  
0123112D  mov         eax,dword ptr [ebp-48h]  
01231130  jae         main+0F5h (1231135h)  
01231132  lea         eax,[ebp-48h]  
01231135  cmp         byte ptr [eax+esi],62h  
01231139  jne         main+108h (1231148h)  
0123113B  mov         ebx,1  
01231140  lea         eax,[ebp-80h]  
01231143  call        std::basic_string<char,std::char_traits<char>,std::allocator<char> >::append (1231250h)  
01231148  inc         esi  
01231149  cmp         esi,5F5E100h  
0123114F  jl          main+0E0h (1231120h)
Run Code Online (Sandbox Code Playgroud)

第二个循环:

01231155  cmp         dword ptr [ebp-1Ch],esi  
01231158  jbe         main+1CBh (123120Bh)  
0123115E  cmp         dword ptr [ebp-18h],10h  
01231162  mov         eax,dword ptr [ebp-2Ch]  
01231165  jae         main+12Ah (123116Ah)  
01231167  lea         eax,[ebp-2Ch]  
0123116A  cmp         byte ptr [eax+esi],62h  
0123116E  jne         main+13Dh (123117Dh)  
01231170  mov         ebx,1  
01231175  lea         eax,[ebp-64h]  
01231178  call        std::basic_string<char,std::char_traits<char>,std::allocator<char> >::append (1231250h)  
0123117D  inc         esi  
0123117E  cmp         esi,5F5E100h  
01231184  jl          main+115h (1231155h) 
Run Code Online (Sandbox Code Playgroud)

由于生成的程序集看起来或多或少相同,我考虑了操作系统或 CPU 内的节流机制,你猜怎么着?添加睡眠(5000);两个循环之间的错误导致第二个循环(几乎)总是比第一个循环慢。运行 20 次后,第二个循环平均花费了第一个循环的 150% 左右的运行时间。

编辑:将旋转计数增加五倍会得到相同的结果。我认为 0.5 秒左右的运行时间或多或少是可以可靠测量的。:-)

在原始代码中,我认为,操作系统可能需要几个时间片来检测 CPU 负载,然后开始在调度期间给予线程更高的优先级,而且 CPU 可能会在 while 之后提升,从而使第一个循环的部分内容“未提升”。当第二个循环开始执行时,操作系统/CPU 可能会为繁重的工作负载做好准备,并且执行速度会更快一些。MMU 或操作系统内部内存页面处理也可能发生同样的情况。当在循环之间添加睡眠时,可能会发生相反的情况,导致操作系统将线程搁置一段时间,直到最终检测到新的工作负载,从而使第二个循环执行速度稍慢。

你的结果是什么?是否有人手头有合适的分析器(例如 Intel 的 Amplifier)来测量循环内的 CPI 速率和 CPU 速度步进?