【问题标题】:Small vs. identical types of loop variables in C/C++ for performanceC/C++ 中的小型与相同类型的循环变量以提高性能
【发布时间】:2021-02-22 15:37:59
【问题描述】:

假设我有一个较大的表单嵌套循环

long long i, j, k, i_end, j_end;
...
for (i = 0; i < i_end; i++) {
  j_bgn = get_j_bgn(i);
  for (j = j_bgn; j < j_end; j++) {
    ...
  }
}

有一些大的i_endj_end,比如i_end = j_end = 10000000000。如果我知道j_bgn 总是很小,甚至可能总是01,那么为此使用较小的类型是否有益于性能,例如signed char j_bgn?或者,每次我们开始一个新的j 循环时,由于隐式转换为long long,这是否会带来经常性成本?

我想这有一个很小的影响,但我想知道这样做的“正确”/迂腐方式:要么 1)保持所有循环变量的类型相同(并使用可以冷却的最小类型所需的最大整数),或 2)独立选择每个循环变量的类型,使其尽可能小。

编辑

从我看到的 cmets/answers 我需要提供更多信息:

  • 我有时想要,有时不想使用这些变量(例如j)进行索引。为什么这很重要(只要我确保使用足够大的类型来覆盖我的可用内存)?
  • 在我的实际代码中,我使用size_t(或ssize_t)之类的东西,例如jj_end。在现代硬件上,这是 64 位。

我认为使用小于 32 位的类型是不值得的,但是对于 j_bgn 使用 32 位类型而不是也使用 64 位类型可能仍然有益(因为我确实需要 @987654338 @ 和j_end)?

【问题讨论】:

  • 您期望使用较小的字体有什么好处?
  • 一种安全的方法是为您的计数器使用与最终值类型相同的类型。它使代码保持清晰,并避免可能导致比较不正确的简单错误。
  • 我不会费心使用小于自然 int 的类型。它不会产生任何性能改进。这还取决于您的循环是否将这些变量用于其他用途(例如索引数组 - 在这种情况下应该考虑 size_t
  • @EugeneSh。小于硬件的“本机”甚至可能导致某些处理器(例如 cortex-M)的一些性能损失
  • 一个int 应该代表机器架构的字长(尽管现在通常是半字长:64 位架构上的 32 位),并且有与其他数据类型一样好或更好的性能。 shortchar 不会比 int 快。如果您担心性能,请考虑使用快速的最小尺寸类型,例如 std::int_fast32_t

标签: c++ c loops types integer


【解决方案1】:

这听起来像是在&lt;cstdint&gt;(用于 C++)或&lt;stdint.h&gt;(用于 C)中定义的“快速”数据类型的实际用例。

您可以使用int_fast8_tint_fast16_tint_fast32_tint_fast64_t 或它们的无符号吊坠,以获得至少8、16、32 或64 字节大的最快整数类型。

我想如果你想真正迂腐,你应该选择这些,让编译器选择最快的选项。

【讨论】:

  • 不,对于循环,您应该使用原生大小的整数(当然如果可能的话)godbolt.org/z/hqYsz1
  • @P__JsupportswomeninPoland 看看这些类型的文档,理论上答案似乎是正确的,但编译器似乎没有正确实现它..
  • @EugeneSh。不,它以正确的方式进行。这种类型必须表现为 n 位。 int8_t 是最快的一个,它表现为 8 位整数。实际上,这些类型背后的想法是一个谜。对于我的这个答案,它是一个“谷歌搜索”的答案,没有对该主题有实际的理解。
  • @P__JsupportswomeninPoland “这种类型必须表现为 n 位。而 int8_t 是最快的,表现为 8 位整数。” - 参考不同意。它声明 “最快的有符号整数类型,其宽度分别为 至少 8、16、32 和 64 位” 仔细解释为什么它应该完全正确 像 int 类型?
  • @churill 完全按照我写的那样引用我。它的行为必须与 n 位整数完全相同。如果您不理解 n 的意思是 8,16 .... 与 (u)int_fastn_t 中的相同。 不是你写的任何整数
【解决方案2】:

如果整数比寄存器的宽度更宽或更小,许多平台需要一些额外的操作。 (不过,大多数 64 位平台可以像处理 64 位一样高效地处理 32 位整数。)

示例(使用空的 asm 语句来停止循环优化):

void lfoo(long long int loops)
{
    for(long long int i = 0; i < loops; i++) asm("");
}

void foo(int loops)
{
    for(int i = 0; i < loops; i++) asm("");
}

void bar(short int loops)
{
    for(short int i = 0; i < loops; i++) asm("");
}

void zoo(char loops)
{
    for(char i = 0; i < loops; i++) asm("");
}

以及生成的旧 32 位 ARM Cortex 处理器的代码,没有 ARMv6 符号扩展指令,这使得 short 的糟糕程度稍低(Godbolt compiler explorer, gcc8.2 默认选项,-O3 没有 -march=-mcpu=cortex-...

lfoo:
        cmp     r0, #1
        sbcs    r3, r1, #0
        bxlt    lr
        mov     r2, #0
        mov     r3, #0
.L3:
        adds    r2, r2, #1
        adc     r3, r3, #0        @@ long long takes 2 registers, obviously bad
        cmp     r1, r3
        cmpeq   r0, r2            @@ and also to compare
        bne     .L3
        bx      lr

foo:
        cmp     r0, #0
        bxle    lr                @ return if loops==0 (predicate condition)
        mov     r3, #0            @ i = 0
.L8:                              @ do {
        add     r3, r3, #1          @ i++  (32-bit)
        cmp     r0, r3             
        bne     .L8               @ } while(loops != i);
        bx      lr                @ return

bar:
        cmp     r0, #0
        bxle    lr
        mov     r2, #0
.L12:                            @ do {
        add     r2, r2, #1          @ i++ (32-bit)
        lsl     r3, r2, #16         @ i <<= 16
        asr     r3, r3, #16         @ i >>= 16  (sign extend i from 16 to 32)
        cmp     r0, r3
        bgt     .L12             @ }while(loops > i)
        bx      lr
                @@ gcc -mcpu=cortex-a15 for example uses
                @@  sxth    r2, r3

zoo:
        cmp     r0, #0
        bxeq    lr
        mov     r3, #0
.L16:
        add     r3, r3, #1
        and     r2, r3, #255     @ truncation to unsigned char is cheap
        cmp     r0, r2           @ but not free
        bhi     .L16
        bx      lr

如您所见,最有效的是 32 位整数,因为它们的大小与处理器寄存器相同(函数 foo)。

【讨论】:

  • 我没有接受过阅读汇编的培训。 .LXY 下面的foobar 等是上面函数的一部分吗?否则foobarzoo 似乎都一样,只有lfoo 更贵。
  • @jmd_dk 如果你不知道汇编程序,你怎么能说它们是相同的。这些被称为标签,它们是功能的一部分。最有趣的部分在标签下方
  • 嗯,我可以逐个字符比较文本,看看它们完全一样......
  • @jmd_dk:我对 asm 发表了评论,以显示 bar 和 zoo 哪里更贵。是的,.Lnum 自动编号标签是分支目标 inside 函数;这就是循环的实现方式。
  • 指出size_t 对于循环计数器来说总是相当合理的,例如。 32 位 CPU 上的 32 位。在 x86-64 上,它通常比大多数循环所需的要宽,并且 REX 前缀会花费一些额外的代码大小。 (并且 64 位 div 在 Intel CPU 上比 32 位要慢得多,因此 32 位 int 是自然大小。)
猜你喜欢
  • 2010-11-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-04
  • 1970-01-01
  • 2015-04-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多