TL; DR :为什么size_t
中的乘法/转换数据会变慢,为什么每个平台会有所不同?
我遇到了一些我不太了解的性能问题。上下文是相机帧抓取器,其中读取128x128 uint16_t图像并以几百Hz的速率进行后处理。
在后处理中,我生成一个直方图frame->histo
,其uint32_t
并且thismaxval
= 2 ^ 16个元素,基本上我统计了所有强度值。使用这个直方图,我计算总和和平方和:
double sum=0, sumsquared=0;
size_t thismaxval = 1 << 16;
for(size_t i = 0; i < thismaxval; i++) {
sum += (double)i * frame->histo[i];
sumsquared += (double)(i * i) * frame->histo[i];
}
使用配置文件分析代码我得到以下内容(样本,百分比,代码):
58228 32.1263 : sum += (double)i * frame->histo[i];
116760 64.4204 : sumsquared += (double)(i * i) * frame->histo[i];
或者,第一行占用CPU时间的32%,第二行占64%。
我做了一些基准测试,似乎是数据类型/转换有问题。当我将代码更改为
时uint_fast64_t isum=0, isumsquared=0;
for(uint_fast32_t i = 0; i < thismaxval; i++) {
isum += i * frame->histo[i];
isumsquared += (i * i) * frame->histo[i];
}
它运行速度快了~10倍。但是,每个平台的性能影响也各不相同。在工作站上,Core i7 CPU 950 @ 3.07GHz的代码速度提高了10倍。在我的Macbook8,1上,它有一个Intel Core i7 Sandy Bridge 2.7 GHz(2620M),代码只快了2倍。
现在我想知道:
更新
我用
编译了上面的代码g++ -O3 -Wall cast_test.cc -o cast_test
UPDATE2:
我通过分析器(Mac上的Instruments)运行优化代码,例如Shark),并发现了两件事:
1)在某些情况下,循环本身需要相当长的时间。 thismaxval
的类型为size_t
。
for(size_t i = 0; i < thismaxval; i++)
占我总运行时间的17%for(uint_fast32_t i = 0; i < thismaxval; i++)
需要3.5%for(int i = 0; i < thismaxval; i++)
未显示在探查器中,我认为它低于0.1%2)数据类型和转换内容如下:
sumsquared += (double)(i * i) * histo[i];
15%(size_t i
)sumsquared += (double)(i * i) * histo[i];
36%(uint_fast32_t i
)isumsquared += (i * i) * histo[i];
13%(uint_fast32_t i
,uint_fast64_t isumsquared
)isumsquared += (i * i) * histo[i];
11%(int i
,uint_fast64_t isumsquared
)令人惊讶的是,int
比uint_fast32_t
快?
UPDATE4:
我在一台机器上运行了不同数据类型和不同编译器的更多测试。结果如下。
对于测试0-2,相关代码是
for(loop_t i = 0; i < thismaxval; i++)
sumsquared += (double)(i * i) * histo[i];
sumsquared
为双倍,loop_t
size_t
,uint_fast32_t
和int
为测试0,1和2。
对于测试3--5,代码是
for(loop_t i = 0; i < thismaxval; i++)
isumsquared += (i * i) * histo[i];
isumsquared
类型为uint_fast64_t
,loop_t
类型为size_t
,uint_fast32_t
和int
代表测试3,4和5。
我使用的编译器是gcc 4.2.1,gcc 4.4.7,gcc 4.6.3和gcc 4.7.0。时间是代码总cpu时间的百分比,因此它们显示相对性能,而不是绝对性(尽管运行时在21s时非常稳定)。两个行的cpu时间都是,因为我不太确定探查器是否正确地分隔了两行代码。
gcc: 4.2.1 4.4.7 4.6.3 4.7.0 ---------------------------------- test 0: 21.85 25.15 22.05 21.85 test 1: 21.9 25.05 22 22 test 2: 26.35 25.1 21.95 19.2 test 3: 7.15 8.35 18.55 19.95 test 4: 11.1 8.45 7.35 7.1 test 5: 7.1 7.8 6.9 7.05
或:
基于此,无论我使用什么整数类型,似乎铸造都很昂贵。
此外,似乎gcc 4.6和4.7无法正确优化循环3(size_t和uint_fast64_t)。
答案 0 :(得分:4)
原始问题:
有关其他问题:
一般情况下,如果不是真的有必要,尽量避免使用可见和隐藏的强制转换。例如,尝试找出环境(gcc)中“size_t”背后隐藏的真实数据类型,并将其用于循环变量。 在你的例子中,uint的平方不能是float数据类型,所以在这里使用double是没有意义的。坚持使用整数类型以获得最佳性能。
答案 1 :(得分:1)
在x86上,uint64_t
到浮点的转换速度较慢,因为只有转换int64_t
,int32_t
和int16_t
的说明。 int16_t
和32位模式int64_t
只能使用x87指令进行转换,而不能使用SSE进行转换。
将uint64_t
转换为浮点时,GCC 4.2.1首先将值转换为int64_t
,然后如果为负值则补充2 64 。 (当使用x87时,在Windows和* BSD上,或者如果您更改了精度控制,请注意转换忽略精度控制,但添加会考虑它。)
uint32_t
首先扩展为int64_t
。
在具有某些64位功能的处理器上以32位模式转换64位整数时,存储到转发转发问题可能会导致停顿。 64位整数写为两个32位值,并作为一个64位值读回。如果转换是长依赖链(不是在这种情况下)的一部分,那么这可能非常糟糕。