将性能从size_t转换为double

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

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

在后处理中,我生成直方图thismaxval frame->histo thismaxval ,其为uint32_t并且具有thismaxval = 2 ^ 16个元素,基本上我统计了所有强度值。 使用这个直方图,我计算总和和平方和:

 double sum=0, sumsquared=0; size_t thismaxval = 1 << 16; for(size_t i = 0; i 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 histo[i]; isumsquared += (i * i) * frame->histo[i]; } 

它的运行速度提高了约10倍。 但是,这种性能影响也因平台而异。 在工作站上,Core i7 CPU 950 @ 3.07GHz的代码速度提高了10倍。 在我的Macbook8,1上,它具有2.7 GHz(2620M)的英特尔酷睿i7 Sandy Bridge,代码速度仅提高了2倍。

现在我想知道:

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

更新:

我编译了上面的代码

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

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%(with 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 iuint_fast64_t isumsquared
  4. isumsquared += (i * i) * histo[i]; 11%(使用int iuint_fast64_t isumsquared

令人惊讶的是, intuint_fast32_t快?

UPDATE4:

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

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

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

使用sumsquared为double,并将loop_t size_tuint_fast32_tint用于测试0,1和2。

对于测试3–5,代码是

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

isumsquared类型为uint_fast64_tloop_t再次为size_tuint_fast32_tint为测试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
 ----------------------------------
测试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)。

对于您的原始问题:

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

对于其他问题:

  • “令人惊讶的是int比uint_fast32_t更快”? 您平台上的sizeof(size_t)和sizeof(int)是多少? 我可以猜测的是,两者都可能是64位,因此对32位的投射不仅可以给你计算错误,还包括不同大小的投射惩罚。

一般情况下,如果不是真的有必要,尽量避免使用可见和隐藏的强制转换。 例如,尝试找出环境(gcc)中“size_t”背后隐藏的真实数据类型,并将其用于循环变量。 在你的例子中,uint的平方不能是float数据类型,所以在这里使用double是没有意义的。 坚持使用整数类型以获得最佳性能。

在x86上, uint64_t到浮点的转换速度较慢,因为只有转换int64_tint32_tint16_tint16_t和32位模式int64_t只能使用x87指令转换,而不能使用SSE转换。

uint64_t转换为浮点时,GCC 4.2.1首先将该值转换为int64_t ,如果为负值则补充2 64 。 (当使用x87时,在Windows和* BSD上,或者如果您更改了精度控制,请注意转换忽略精度控制,但添加会考虑它。)

uint32_t首先扩展为int64_t

在具有某些64位function的处理器上以32位模式转换64位整数时,存储到转发转发问题可能会导致停顿。 64位整数写为两个32位值,并作为一个64位值读回。 如果转换是长依赖链(不是在这种情况下)的一部分,这可能非常糟糕。