我用C++编写了这个简单的循环(Visual 2019在发布模式下),它经历了100万个32位整数并分配给前一个:
for ( int i = 1; i < Iters; i++ )
ints32[i - 1] = ints32[i];这里有一个循环,除了使用+=之外,这个循环是相同的
for ( int i = 1; i < Iters; i++ )
ints32[i - 1] += ints32[i];我正在测量这两个功能,运行了20次,放弃了5次最慢和5次最快的运行,平均其余的。(在每次测量之前,我都会用随机整数填充数组。)我发现只有赋值的循环大约需要460微秒,但是加法的循环需要320微秒。这些度量在多个运行中是一致的(它们略有不同,但添加速度总是更快),即使在更改测量这两个函数的顺序时也是如此。
为什么带加法的循环比没有加法的循环要快?如果有什么变化的话,我认为这会让它花费更长的时间。
下面是反汇编,您可以看到这些函数是等价的,只不过加法循环做了更多的工作(并将eax设置为ints[i - 1]而不是ints[i])。
for ( int i = 1; i < Iters; i++ )
00541670 B8 1C 87 54 00 mov eax,54871Ch
ints32[i - 1] = ints32[i];
00541675 8B 08 mov ecx,dword ptr [eax]
00541677 89 48 FC mov dword ptr [eax-4],ecx
0054167A 83 C0 04 add eax,4
0054167D 3D 18 90 91 00 cmp eax,offset floats32 (0919018h)
00541682 7C F1 jl IntAssignment32+5h (0541675h)
}
00541684 C3 ret
for ( int i = 1; i < Iters; i++ )
00541700 B8 18 87 54 00 mov eax,offset ints32 (0548718h)
ints32[i - 1] += ints32[i];
00541705 8B 08 mov ecx,dword ptr [eax]
00541707 03 48 04 add ecx,dword ptr [eax+4]
0054170A 89 08 mov dword ptr [eax],ecx
0054170C 83 C0 04 add eax,4
0054170F 3D 14 90 91 00 cmp eax,919014h
00541714 7C EF jl IntAddition32+5h (0541705h)
}
00541716 C3 ret ( int数组是volatile,因为我不希望编译器对其进行优化或矢量化,而且它产生的反汇编确实是我想要测量的。)
编辑:我注意到,当我改变程序看似无关的东西时,分配版本变得更快,然后又变慢了。我怀疑这可能与函数代码的对齐有关,也许吧?
我正在使用默认的VisualStudio2019 Win32发行版编译器选项(从项目属性复制此选项):
/permissive- /ifcOutput "Release\" /GS /GL /analyze- /W3 /Gy /Zc:wchar_t /Zi /Gm- /O2 /sdl /Fd"Release\vc142.pdb" /Zc:inline /fp:precise /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /Gd /Oy- /Oi /MD /FC /Fa"Release\" /EHsc /nologo /Fo"Release\" /Fp"Release\profile.pch" /diagnostics:column 编译器版本: 2019版本16.11.15
以下是完整的代码:
#include <iostream>
#include <chrono>
#include <random>
#include <algorithm>
const int ValueRange = 100000000;
std::default_random_engine generator;
std::uniform_int_distribution< int > distribution( 1, ValueRange - 1 );
const int Iters = 1000000; // nanoseconds -> milliseconds
volatile int ints32[Iters];
void InitArrayInt32()
{
for ( int i = 0; i < Iters; i++ )
ints32[i] = distribution( generator );
}
const int SampleCount = 20;
const int KeepSampleCount = SampleCount - 2 * (SampleCount / 4);
float ProfileFunction( void(*setup)(), void(*func)() )
{
uint64_t times[SampleCount];
for ( int i = 0; i < SampleCount; i++ )
{
setup();
auto startTime = std::chrono::steady_clock::now();
func();
auto endTime = std::chrono::steady_clock::now();
times[i] = std::chrono::duration_cast<std::chrono::microseconds>( endTime - startTime ).count();
}
std::sort( times, times + SampleCount );
uint64_t total = 0;
for ( int i = SampleCount / 4; i < SampleCount - SampleCount / 4; i++ )
total += times[i];
return total * (1.0f / KeepSampleCount);
}
void IntAssignment32()
{
for ( int i = 1; i < Iters; i++ )
ints32[i - 1] = ints32[i];
}
void IntAddition32()
{
for ( int i = 1; i < Iters; i++ )
ints32[i - 1] += ints32[i];
}
int main()
{
float assignment = ProfileFunction( InitArrayInt32, IntAssignment32 );
float addition = ProfileFunction( InitArrayInt32, IntAddition32 );
printf( "assignment: %g\n", assignment );
printf( "addition: %g\n", addition );
return 0;
}发布于 2022-07-03 23:47:06
编辑:我注意到当我改变程序中看似无关的东西时,分配版本变得更快,然后又变慢了。我怀疑这可能与函数代码的对齐有关,也许吧?
其中一个比另一个更快的原因是运气。由代码和数据的对齐引起的速度差异比生成的代码中的细微差异有更大的影响。
即使您测量了多次,您测量的总是相同的对齐(在某种程度上,如果您有地址空间随机化)。这消除了来自调度程序和中断的噪声,但不消除来自对齐更改的噪声。
但是,当您更改代码中的某些内容时,您将更改对齐方式,例如,将数组移动4字节。将代码移动1字节。这对速度的影响实际上比gcc的-O0和-O2之间的差异更大。关于这种效果的论文很多,还有更多的。
https://stackoverflow.com/questions/72850251
复制相似问题