很好的问号!
没有。它并不快。
至少对于我在 Intel 处理器上使用 clang 和在 ARM64 处理器上使用 gcc 进行的基准测试来说不是这样。
确实,? 会导致条件分支或条件指令。尽管如此,? 方法的编译代码最终比使用 - 和 & 的代码快三分之一,从而避免了条件执行。
请参阅下面用于测试的代码。还包括该最内层语句的编译指令。我还测试了您列出的 Mark 之外的另一种替代方案:
crc = (crc >> 1) ^ (crc & 1 ? 0xa001 : 0);
那个仍然使用条件,但将其隔离为与移位的 CRC 异或的内容。有趣的是,这在 Intel 上编译为与减和方法完全相同的代码,没有条件执行。在 ARM 上,它编译为不同的第三组指令。对于 Intel 和 ARM,它仍然比第一种方法慢。
最重要的是,常见的条件执行方法比试图避免条件执行的尝试快 30% 到 35%。
为什么,你可能会问?据我所知,对于 ARM,您平均而言使用条件语句执行的指令更少。条件方法可以利用一条指令平均只执行一半时间的事实。这显然超过了不可预测的分支机构的成本。现代处理器似乎在处理不可预测的分支方面做得很好。对于英特尔来说,无论哪种方式都是 6 条指令,而且在一半的时间里似乎没有任何好处。
这仅适用于两种架构、技术和相关的编译器。您的里程可能会有所不同。
// Test the speed of alternative bit-wise CRC implementations. To do this, we
// simply propagate a CRC assuming zeros for the data with an initial value of
// all ones. A down-shifting (reflected) 16-bit CRC is used. All times are for
// one billion iterations, with each iteration cycling eight zero bits.
#include <stddef.h>
// Implementation using a conditional expression. This compiles to use a
// conditional move instruction on Intel or a conditional branch on ARM, and an
// unrolled inner loop on both. There are six instructions per iteration on
// Intel, 2.5 on average on ARM. This compiles to the fastest code on both
// architectures.
//
// Intel i7 (clang 11.0.3 -O3): 6.6 seconds
// movl %eax, %ecx
// shrl %ecx
// movl %ecx, %edx
// xorl $40961, %edx ## imm = 0xA001
// testb $1, %al
// cmovel %ecx, %edx
//
// ARM64 (gcc 6.3.0 -O3): 12.0 seconds
// tbnz x0, 0, .L4
// lsr w0, w0, 1
// .L5:
//---------
// .L4:
// eor w0, w2, w0, lsr 1 ## w2 = 0xa001
// b .L5
unsigned crc16_cond(unsigned crc, size_t len) {
while (len--)
for (unsigned k = 0; k < 8; k++)
crc = crc & 1 ? (crc >> 1) ^ 0xa001 : crc >> 1;
return crc;
}
// Different implementation using a conditional expression as before, but here
// just to select zero or the polynomial. This compiles to use no conditionals
// and an unrolled inner loop. There are six instructions per iteration on
// Intel, three on ARM. This ends up compiling to the exact same instructions
// as the minus implementation on Intel.
//
// Intel i7 (clang 11.0.3 -O3): 8.9 seconds
// movl %eax, %ecx
// shrl %ecx
// andl $1, %eax
// negl %eax
// andl $40961, %eax ## imm = 0xA001
// xorl %ecx, %eax
//
// ARM64 (gcc 6.3.0 -O3): 15.5 seconds
// tst x0, 1
// csel w3, w1, wzr, ne ## w1 = 0xa001
// eor w0, w3, w0, lsr 1
unsigned crc16_cond2(unsigned crc, size_t len) {
while (len--)
for (unsigned k = 0; k < 8; k++)
crc = (crc >> 1) ^ (crc & 1 ? 0xa001 : 0);
return crc;
}
// Implementation using a minus and an and to select zero or the polynomial.
// This compiles as written in an unrolled inner loop. There are six
// instructions per iteration on Intel and three on ARM.
//
// Intel i7 (clang 11.0.3 -O3): 8.9 seconds
// movl %eax, %ecx
// shrl %ecx
// andl $1, %eax
// negl %eax
// andl $40961, %eax ## imm = 0xA001
// xorl %ecx, %eax
//
// ARM64 (gcc 6.3.0 -O3): 16.0 seconds
// sbfx x2, x0, 0, 1
// and w2, w2, w1 ## w1 = 0xa001
// eor w0, w2, w0, lsr 1
unsigned crc16_minus(unsigned crc, size_t len) {
while (len--)
for (unsigned k = 0; k < 8; k++)
crc = (crc >> 1) ^ ((0 - (crc & 1)) & 0xa001);
return crc;
}
#include <stdio.h>
int main(void) {
unsigned crc = crc16_cond2(0xffff, 1000000000);
printf("%04x\n", crc);
return 0;
}