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