为了下列功能..。
uint16_t swap(const uint16_t value)
{
return value << 8 | value >> 8;
}...why用ARM gcc 6.3.0和-O2生产下列组件?
swap(unsigned short):
lsr r3, r0, #8
orr r0, r3, r0, lsl #8
lsl r0, r0, #16 # shift left
lsr r0, r0, #16 # shift right
bx lr编译器似乎正在使用两班制来屏蔽不需要的字节,而不是使用逻辑和。编译器可以使用and r0, r0, #4294901760吗?
发布于 2017-12-12 20:58:45
旧的ARM程序集不能轻易地创建常量。相反,它们被加载到文字池中,然后通过内存加载读取。你建议的这个and只能接受,我相信一个8位的文字和移位。您的0xFFFF0000需要16位才能执行1条指令.
因此,我们可以从内存中加载并执行and (慢速),使用2条指令来创建值和1 to和(更长),或者只需便宜地移动两次并称其为“好”。
编译器选择了转换,老实说,它足够快。
现在让我们来看看现实:
担心一次换班,除非这是百分之百的瓶颈,否则就是浪费时间。即使编译器不是最优的,您也几乎永远不会感觉到它。担心代码中的“热”循环,而不是像这样的微操作。好奇地看着这个真是太棒了。担心这个精确的代码在你的应用程序中的性能,没有那么多。
编辑:
这里的其他人已经注意到,新版本的ARM规范允许更有效地完成这类事情。这表明,在这个级别上,重要的是指定芯片,或者至少是我们正在处理的确切的ARM规范。我认为古代的手臂是因为你的输出缺乏“更新”的指令而产生的。如果我们跟踪编译器错误,那么这个假设可能不成立,并且了解规范甚至更重要。对于这样的交换,在以后的版本中确实有更简单的说明来处理这个问题。
编辑2
可以做的一件事,可能使这更快是使它内联。在这种情况下,编译器可以交织这些操作和其他工作。根据CPU的不同,这可能是这里吞吐量的两倍,因为很多ARM CPU都有两个整数指令管道。把指令展开得足够充分,这样就不会有危险了,然后就会消失。这必须与it的使用进行权衡,但在重要的情况下,您可以看到更好的东西。
发布于 2017-12-13 01:30:10
这里有一个遗漏的优化,但是and并不是缺少的部分。生成一个16位常数并不便宜。对于一个循环来说,是的,在循环之外生成一个常量并在循环中只使用and是一种胜利。(TODO:在数组上循环调用swap,并查看我们得到的代码类型。)
对于无序的CPU来说,在关键路径上使用多个指令来构建常量也是值得的,那么关键路径上只有一个AND而不是两个轮班。但这可能是罕见的,而不是gcc选择的。
AFAICT (通过查看编译器输出中的简单函数),ARM调用约定保证输入寄存器中不存在高垃圾,并且不允许在返回值中留下高垃圾。也就是说,在输入时,它可以假定r0的上16位都为零,但必须在返回时将其保留为零。因此,value << 8的左移位是一个问题,但value >> 8不是问题(它不必担心将垃圾转移到低位16)。
(请注意,x86调用约定不是这样的:返回值允许有很高的垃圾。(可能是因为调用者可以简单地使用16位或8位部分寄存器)。输入值也是如此,除了作为x86-64系统V ABI的一个无文件部分:clang取决于输入值被符号/零扩展到32位。GCC在打电话的时候提供了这个服务,但并没有假定为被叫者。
ARMv6有说明,哪个字节交换寄存器的两个16位的一半.如果上面的16位已经是零,它们就不需要被重新归零,所以gcc -march=armv6应该将函数编译成rev16。但实际上,它会释放出一个uxth来提取和零扩展下半个单词。(即与and与0x0000FFFF完全相同,但不需要大的常量)。我认为这完全是错过了优化;大概gcc的旋转习语,或者它对使用rev16的内部定义,没有包含足够的信息来让它实现上半部分的零。
swap: @@ gcc6.3 -O3 -march=armv6 -marm
rev16 r0, r0
uxth r0, r0 @ not needed
bx lr对于ARM预v6,一个较短的序列是可能的。只有当我们把它拿到我们想要的asm的时候,GCC才能找到它:
// better on pre-v6, worse on ARMv6 (defeats rev16 optimization)
uint16_t swap_prev6(const uint16_t value)
{
uint32_t high = value;
high <<= 24; // knock off the high bits
high >>= 16; // and place the low8 where we want it
uint8_t low = value >> 8;
return high | low;
//return value << 8 | value >> 8;
}
swap_prev6: @ gcc6.3 -O3 -marm. (Or armv7 -mthumb for thumb2)
lsl r3, r0, #24
lsr r3, r3, #16
orr r0, r3, r0, lsr #8
bx lr但这与gcc的旋转习语识别相去甚远,因此,即使在简单版本编译为rev16 / uxth时,它也会编译成相同的代码。
发布于 2017-12-16 23:08:21
ARM是一台RISC机器(高级RISC机器),因此,所有的指令都是相同大小的编码,上限为32位。
指令中的即时值被分配给一定数量的位,而AND指令根本没有分配给即时值来表示任何16位值的加密位。
这就是编译器转而使用两个移位指令的原因。
但是,如果您的目标CPU是ARMv6 (ARM11)或更高,编译器会利用新的REV16指令,然后用UXTH指令掩盖低16位,这是不必要的和愚蠢的,但是没有传统的方法来说服编译器不要这样做。
如果你认为GCC内在的__builtin_bswap16会为你服务的话,那你就大错特错了。
uint16_t swap(const uint16_t value)
{
return __builtin_bswap16(value);
}上面的函数生成与原始C代码完全相同的机器代码。
即使使用内联程序集也于事无补
uint16_t swap(const uint16_t value)
{
uint16_t result;
__asm__ __volatile__ ("rev16 %[out], %[in]" : [out] "=r" (result) : [in] "r" (value));
return result;
}再说一次,完全一样。只要使用GCC,就无法摆脱烦人的UXTH;从上下文中看不出上面的16位都是零,因此,UXTH是不必要的。
在程序集中编写整个函数;这是唯一的选项。
https://stackoverflow.com/questions/47780720
复制相似问题