28

TL;DR:为什么乘法/转换数据size_t速度很慢,为什么每个平台会有所不同?

我遇到了一些我不完全理解的性能问题。上下文是一个相机帧采集器,其中以几个 100 Hz 的速率读取和后处理 128x128 uint16_t 图像。

在后处理中,我生成了一个直方图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];

或者,第一行占用 32% 的 CPU 时间,第二行占用 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 倍。

现在我想知道:

  1. 为什么原始代码如此缓慢且容易加速?
  2. 为什么每个平台的差异如此之大?

更新:

我编译了上面的代码

g++ -O3  -Wall cast_test.cc -o cast_test

更新2:

我通过分析器(Mac 上的Instruments ,如Shark)运行优化的代码,发现了两件事:

1) 在某些情况下,循环本身需要相当长的时间。thismaxval是类型size_t

  1. for(size_t i = 0; i < thismaxval; i++)占用我总运行时间的 17%
  2. for(uint_fast32_t i = 0; i < thismaxval; i++)占 3.5%
  3. for(int i = 0; i < thismaxval; i++)没有出现在分析器中,我认为它小于 0.1%

2) 数据类型和转换事项如下:

  1. sumsquared += (double)(i * i) * histo[i];15%(含size_t i
  2. sumsquared += (double)(i * i) * histo[i];36%(含uint_fast32_t i
  3. isumsquared += (i * i) * histo[i];13%(带uint_fast32_t i, uint_fast64_t isumsquared
  4. isumsquared += (i * i) * histo[i];11%(带int i, uint_fast64_t isumsquared

令人惊讶的是,intuint_fast32_t?

更新4:

我在一台机器上用不同的数据类型和不同的编译器运行了更多测试。结果如下。

对于 testd 0 -- 2 相关代码是

    for(loop_t i = 0; i < thismaxval; i++)
        sumsquared += (double)(i * i) * histo[i];

对于测试 0、1 和 2,使用sumsquareddouble 和loop_t size_t, uint_fast32_tand 。int

对于测试 3--5,代码是

    for(loop_t i = 0; i < thismaxval; i++)
        isumsquared += (i * i) * histo[i];

isumsquared类型为and uint_fast64_tloop_t用于测试3、4size_t和5。uint_fast32_tint

我使用的编译器是 gcc 4.2.1、gcc 4.4.7、gcc 4.6.3 和 gcc 4.7.0。时间是代码总 cpu 时间的百分比,所以它们显示的是相对性能,而不是绝对的(尽管运行时间在 21 秒时相当稳定)。cpu 时间是两行的,因为我不太确定分析器是否正确分离了这两行代码。

海湾合作委员会:4.2.1 4.4.7 4.6.3 4.7.0
----------------------------------
测试0:21.85 25.15 22.05 21.85
测试1:21.9 25.05 22 22
测试2:26.35 25.1 21.95 19.2
测试 3:7.15 8.35 18.55 19.95
测试4:11.1 8.45 7.35 7.1
测试 5:7.1 7.8 6.9 7.05

或者:

铸造性能

基于此,无论我使用什么整数类型,转换似乎都很昂贵。

此外,gcc 4.6 和 4.7 似乎无法正确优化循环 3(size_t 和 uint_fast64_t)。

4

2 回答 2

4

对于您的原始问题:

  1. 代码很慢,因为它涉及从整数到浮点数据类型的转换。这就是为什么当您也为总和变量使用整数数据类型时它很容易加速,因为它不再需要浮点转换。
  2. 差异是几个因素的结果。例如,它取决于平台执行 int->float 转换的效率。此外,这种转换还可能会扰乱程序流和预测引擎、缓存等处理器内部的优化,而且处理器的内部并行化特性也会对此类计算产生巨大影响。

对于其他问题:

  • “令人惊讶的是 int 比 uint_fast32_t 快”?你平台上的 sizeof(size_t) 和 sizeof(int) 是多少?我可以做出的一个猜测是,两者都可能是 64 位,因此转换为 32 位不仅会给您带来计算错误,而且还包括不同大小的转换惩罚。

一般来说,如果不是真的需要,尽量避免可见和隐藏的演员表。例如,尝试找出在您的环境 (gcc) 中隐藏在“size_t”后面的真实数据类型,并将该数据类型用于循环变量。在您的示例中,uint 的平方不能是 float 数据类型,因此在这里使用 double 是没有意义的。坚持使用整数类型以实现最大性能。

于 2012-05-16T13:47:36.183 回答
1

On x86, the conversion of uint64_t to floating point is slower because there are only instructions to convert int64_t, int32_t and int16_t. int16_t and in 32-bit mode int64_t can only be converted using x87 instructions, not SSE.

When converting uint64_t to floating point, GCC 4.2.1 first converts the value as if it were an int64_t and then adds 264 if it was negative to compensate. (When using the x87, on Windows and *BSD or if you changed the precision control, beware that the conversion ignores precision control but the addition respects it.)

An uint32_t is first extended to int64_t.

When converting 64-bit integers in 32-bit mode on processors with certain 64-bit capabilities, a store-to-load forwarding issue may cause stalls. The 64-bit integer is written as two 32-bit values and read back as one 64-bit value. This can be very bad if the conversion is part of a long dependency chain (not in this case).

于 2012-05-17T19:59:39.037 回答