将性能从size_t转换为double

时间:2012-05-15 07:49:23

标签: 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];
}

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

 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倍。

现在我想知道:

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

    我用

    编译了上面的代码
    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%
    4. 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 iuint_fast64_t isumsquared
      4. isumsquared += (i * i) * histo[i]; 11%(int iuint_fast64_t isumsquared
      5. 令人惊讶的是,intuint_fast32_t快?

        UPDATE4:

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

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

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

        sumsquared为双倍,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
        ----------------------------------
        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
        

        或:

        casting performance

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

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

2 个答案:

答案 0 :(得分:4)

原始问题:

  1. 代码很慢,因为它涉及从整数转换为 浮点数据类型。这就是为什么当你也使用它时很容易加快的原因 sum-variables的整数数据类型,因为它不需要 再进行浮动转换。
  2. 差异是几个结果 因素。例如,它取决于平台的效率 执行int-&gt;浮点转换。此外,这种转换 也可能搞乱程序中的处理器内部优化 流量和预测引擎,缓存,......以及内部 并行化处理器的功能可以产生巨大的影响 这样的计算。
  3. 有关其他问题:

    • “令人惊讶的是int比uint_fast32_t更快”?什么是 平台上的sizeof(size_t)和sizeof(int)?我可以猜到的是,两者都是 可能是64位,因此投射到32位不仅可以给你 计算错误,但也包括不同大小的铸造 罚。

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

答案 1 :(得分:1)

在x86上,uint64_t到浮点的转换速度较慢,因为只有转换int64_tint32_tint16_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位值读回。如果转换是长依赖链(不是在这种情况下)的一部分,那么这可能非常糟糕。