C++ 缓存、循环和性能

C++ 缓存、循环和性能,c++,performance,memory,caching,C++,Performance,Memory,Caching,不久前,我写了一段代码,询问采访中的情况,看看人们是如何理解缓存和内存的概念的: #include "stdafx.h" #include <stdlib.h> #include <windows.h> #include <iostream> #define TOTAL 0x20000000 using namespace std; __int64 count(int INNER, int OUTER) { int a = 0; int*

不久前,我写了一段代码,询问采访中的情况,看看人们是如何理解缓存和内存的概念的:

#include "stdafx.h"
#include <stdlib.h>
#include <windows.h>
#include <iostream>

#define TOTAL 0x20000000

using namespace std;

__int64 count(int INNER, int OUTER)
{
    int a = 0;
    int* arr = (int*) HeapAlloc(GetProcessHeap(), 0, INNER * sizeof(int));
    if (!arr) {
        cerr << "HeapAlloc failed\n";
        return 1;
    }
    LARGE_INTEGER freq;
    LARGE_INTEGER startTime, endTime;
    __int64 elapsedTime, elapsedMilliseconds;
    QueryPerformanceFrequency(&freq);
    QueryPerformanceCounter(&startTime);

    /* Начало работы */

    for (int i = 0; i < OUTER; i++) {
        for (int j = 0; j < INNER; j++) {
            a |= i;
            arr[j] = a;
        }
    }

    /* Конец работы */

    QueryPerformanceCounter(&endTime);
    elapsedTime = endTime.QuadPart - startTime.QuadPart;
    elapsedMilliseconds = (1000 * elapsedTime) / freq.QuadPart;
    HeapFree(GetProcessHeap(), 0, arr);
    return elapsedMilliseconds;
}

int _tmain(int argc, _TCHAR* argv[])
{
    __int64 time;
    for (int INNER = 0x10; INNER <= 0x2000000; INNER <<= 1) {
        int OUTER = TOTAL / INNER;
        time = count(INNER, OUTER);
        cout << INNER << "\t" << OUTER << "\t" << time << "\n";
    }
}
这就是程序在我的机器上输出的内容:



我要求人们解释发生了什么。随着内部阵列的增长,周期数会随着时间的推移而减少。当内部阵列超出缓存时,缓存未命中开始发生,时间也随之增加。到目前为止还可以

但是:当内部数组大小为16(即提供64字节的数据)时,尽管代码中有更多的
jmp
,但性能几乎没有提升。它很小(523对569),但可以复制


问题是:为什么会有这样的提升?

可能是因为64是您机器上的缓存线大小,而您基本上是在单个缓存线之外运行每个迭代。

我不确定您在问什么。你只是在核实我们所知道的吗?为什么要麻烦?或者你问我们是因为你自己无法解释64字节的增长?或者是为了找出这是否是一个好的面试问题,还是

无论如何,如果只是想为你的面试问题提供背景,你应该删除所有不必要的代码。健康重要吗?为什么不能在堆栈上声明数组?还是与new/malloc合作

为什么您需要在这个小测试程序中进行错误处理?同样,这只会分散注意力,增加噪音。QPC调用也是如此。我甚至不会问你为什么需要一个预编译头

为了向受访者询问6行循环,他必须筛选16行不相关的噪音。为什么?

正如注释中提到的,输出表基本上是不可读的

我完全赞成观察受访者是否能够阅读代码,但我不认为有必要对性能和缓存特性提出如此难以阅读的问题


在任何情况下,一个64字节的内部数组正好适合大多数CPU的缓存线。这意味着,每次外部循环都必须读取一条缓存线。

为了澄清,他的表中的一些空格实际上应该是,'s。你们都能弄明白。

那个输出表非常混乱-你们能重新格式化它吗?我试着用的。它在预览中看起来不错,但在实际文章中却很糟糕;这是一种单间距字体,所以应该可以使用。我在这里发布了完整的代码,让您可以复制并编译它——以备不时之需。当然,在实际的面试中,我只展示了循环。抱歉,如果我伤害了您的感情:)@Quassnoi:HeapAlloc()不在64字节边界上对齐内存。阵列更有可能跨越两条相邻的缓存线……但是,当大小小于cahce缓存线大小时,为什么它运行较慢?例如,当数组大小为16时,它是否应该能够从一条缓存线处理4次完整的外部循环迭代,从而更快?谢谢它可能更像是分支预测的一种度量。较小的内部循环会更频繁地失败。
00401062  xor         ecx,ecx 
00401064  test        ebp,ebp 
00401066  jle         count+83h (401083h) 
00401068  xor         eax,eax 
0040106A  test        ebx,ebx 
0040106C  jle         count+7Ch (40107Ch) 
0040106E  mov         edi,edi 
00401070  or          esi,ecx 
00401072  mov         dword ptr [edi+eax*4],esi 
00401075  add         eax,1 
00401078  cmp         eax,ebx 
0040107A  jl          count+70h (401070h) 
0040107C  add         ecx,1 
0040107F  cmp         ecx,ebp 
00401081  jl          count+68h (401068h) 
LOG2(INNER) LOG2(OUTER)  Time, ms
4           25           523
5           24           569
6           23           441
7           22           400
8           21           367
9           20           358
10          19           349
11          18           364
12          17           378
13          16           384
14          15           357
15          14           377
16          13           379
17          12           390
18          11           386
19          10           419
20          9            995
21          8            1,015
22          7            1,038
23          6            1,071
24          5            1,082
25          4            1,119