将性能从size_t转换为double

Tim*_*Tim 28 c performance casting

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];
}
Run Code Online (Sandbox Code Playgroud)

使用配置文件分析代码我得到以下(样本,百分比,代码):

 58228 32.1263 :  sum += (double)i * frame->histo[i];
116760 64.4204 :  sumsquared += (double)(i * i) * frame->histo[i];
Run Code Online (Sandbox Code Playgroud)

或者,第一行占用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];
}
Run Code Online (Sandbox Code Playgroud)

它跑得快〜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
Run Code Online (Sandbox Code Playgroud)

UPDATE2:

我通过分析器(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)

令人惊讶的是,int是否比uint_fast32_t

UPDATE4:

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

对于测试0-2,相关代码是

    for(loop_t i = 0; i < thismaxval; i++)
        sumsquared += (double)(i * i) * histo[i];
Run Code Online (Sandbox Code Playgroud)

使用sumsquareddouble,和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];
Run Code Online (Sandbox Code Playgroud)

isumsquared类型uint_fast64_tloop_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).

Kos*_*801 4

对于您原来的问题:

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

对于附加问题:

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

一般来说,如果不是真正必要的话,请尽可能避免可见和隐藏的强制转换。例如,尝试找出您的环境(gcc)上“size_t”后面隐藏的真实数据类型并将其用作循环变量。在您的示例中,uint 的平方不能是浮点数据类型,因此此处使用 double 是没有意义的。坚持使用整数类型以获得最佳性能。