在位移操作中使用 size_t 进行计数是否合适?

Zac*_*iaz 2 c bit-manipulation size-t bit-shift bitwise-operators

这个看似微不足道的小问题,我最近突然想到,但在谷歌上搜索了很多之后,我什至无法找到关于这个主题的意见。仅提及循环和对象大小。

我知道人们喜欢例子,所以这里是首先引起问题的人:

uint64_t deltaSwap( const uint64_t b, const size_t delta, uint64_t mask )
{
    return b ^ mask ^ ( mask &=  b ^ b >> delta ) << delta;
}
Run Code Online (Sandbox Code Playgroud)

一段时间以来,我一直在尝试对此进行优化,我很清楚这不是编写正确代码的方法,尽管到目前为止它给了我最好的结果,至少在 GCC 中,然后我想到了。如果你真的想学究,不应该是 delta 类型size_t吗?

我从来没有真正理解什么时候使用size_t,所以我从来没有真正理解过,但是如果我知道,这不是正确的用法吗?

更新:这是它的作用的简短解释,虽然不是它是如何做到的,因为我不确定如何解释它:

这是一个标准的 delta 交换,这不是一个新的理想,并且代码运行良好,这与代码无关(但既然你问了),我真正做的就是试验它,以实现最好的性能,以及你在这里看到的版本,是我迄今为止最好的结果。

代码的目的是交换两位或更多位,如果您希望交换第一位和最后一位,则可以这样做:

deltaSwap(b, 63, 0x0000000000000001);
Run Code Online (Sandbox Code Playgroud)

或者如果您希望颠倒位的顺序:

deltaSwap(b, 32, 0x00000000ffffffff);
deltaSwap(b, 16, 0x0000ffff0000ffff);
deltaSwap(b,  8, 0x00ff00ff00ff00ff);
deltaSwap(b,  4, 0x0f0f0f0f0f0f0f0f);
deltaSwap(b,  2, 0x3333333333333333);
deltaSwap(b,  1, 0x5555555555555555);
Run Code Online (Sandbox Code Playgroud)

尽管对于这个特定的任务,deltaswap 可能不是最好的方法。

更新2:只是为了完成,这是我能想到的最正确的在线程序(已经得到了我的答案),并且编译器显然对其进行了完美的优化。

uint64_t deltaSwap( const uint64_t b, const uint_fast8_t delta, const uint64_t mask )
{
    return b ^ ( mask & ( b ^ b >> delta ) ) ^ ( mask & ( b ^ b >> delta ) ) << delta;
}
Run Code Online (Sandbox Code Playgroud)

我会缩短变量名称以使其全部适应我的 ocd 大脑(显然也是这个站点)强加的 80 个字符,但对于你们所有人,我愿意受苦。

Mar*_*lli 5

如果你真的想学究气,不delta应该是那种类型size_t吗?

不,如果你真的想学究,delta应该只是一个无符号整数类型,至少能够保存从0到的值范围sizeof(uint64_t) * CHAR_BIT。在你的情况下,这是[0, 63]. 没有真正的必要size_t

我从来没有真正理解什么时候使用size_t,所以我从来没有真正理解过,但是如果我知道,这不是正确的用法吗?

就代码的正确性而言,还可以。就优化而言,它没有多大意义。size_t用于保存大小,因为它是一种保证能够保存最大可能大小的对象的类型。它当然不能保证比普通unsigned或任何其他无符号整数类型更快(请参阅答案底部)。

另一个需要注意的重要事项是:

 b ^ mask ^ ( mask &=  b ^ b >> delta ) << delta
Run Code Online (Sandbox Code Playgroud)

根据 C 标准是未定义的行为,因为您在使用变量的值的同时还在同一语句中对其应用了副作用(请参阅此处的第 6.5 段第 2 点,第 76 页)。

做你想做的正确方法是:

mask &= b ^ (b >> delta);
return b ^ (mask ^ (mask << delta));
Run Code Online (Sandbox Code Playgroud)

在任何情况下,在使用移位运算符时都要格外小心,因为它们优先于其他位运算符。使用额外的括号或将表达式拆分为多行不会影响性能并提高可读性。一个体面的编译器将毫无问题地优化上述内容。


现在,回到您提出这个问题的真正原因:优化。

优化代码的正确方法是让编译器选择您需要的变量的最佳大小。为此,您只需要一个字节,并且可以使用uint_fast8_tfrom stdint.h,它是最快的(实现定义的)无符号整数类型,宽度至少为 8 位。编译器将为其目的选择最快的宽度。

综上所述,优化代码的正确方法是:

uint64_t deltaSwap( const uint64_t b, const uint_fast8_t delta, uint64_t mask )
{
    mask &= b ^ (b >> delta);
    return b ^ (mask ^ (mask << delta));
}
Run Code Online (Sandbox Code Playgroud)

根据您在做什么,将函数声明为inline __attribute__ ((always_inline))好像 GCC 还没有为您内联代码也可能是有意义的,尽管编译器通常更擅长确定何时内联代码以及何时不内联。您的函数很可能已经被内联了。

更重要的一件事:使用正确的优化标志通常比手动调整代码更重要。例如,对于上面的代码,您可能希望使用-Ofast -march=native, 甚至其他标志进行编译,具体取决于您在哪里使用该函数(例如,-ftree-vectorize如果在循环中使用)。

除了上述之外:asm()假设公式已经简化到其核心,基准测试、切换到带有语句的手动调整的汇编以及查看周围的代码是进一步优化上述内容的唯一方法。