关于班次,标志和速度的问题

“找到一切的理由,您将了解很多”


也许我的普通读者(嗯,可能不是他们不是)记得在我的帖子中,我感到困惑的是,unsigned属性用于描述外部设备的寄存器。 在评论中,建议这样做是为了避免在轮班期间出现不确定的行为,我同意。 正如我最近发现的,使用此属性还有另一个原因,它不仅可以应用于寄存器,而且可以应用于普通变量。

所以,我们开始了。

对于初学者,铁的小介绍
作为目标平台,我们将考虑一个不带电池的8位MK(这是隐藏可危名称AVR的可悲尝试),它具有以下硬件实现的命令:

lsl / lsr逻辑左/右移位,低/高位被清除;
rol / ror循环左移/右移转移(移位9位);
当向右算术移位时,将保存最高有效(有符号)位(我们注意,从原则上讲,向左执行这种类型的移位通常是不可能的)。

所有这些命令都是在字节操作数上执行的,并且是执行所有其他可能移位的基础。 例如,按以下顺序实现向右移1位符号的字移位(2个字节的rh,rl):

asr rh; ror rl;

像往常一样,请考虑在godbolt.org上获得一个简单的代码示例以及带有AVR命令系统的MK的相应汇编代码。 (意味着启用了优化,并且变量位于r24寄存器中)

int8_t byte; byte = byte << 1; 

 clr r25 sbrc r24,7 com r25 lsl r24 rol r25 

看到该行动需要五个团队?

注意:如果评论中的某人告诉您如何将该片段(及后续片段)分为2列,我将不胜感激。

从汇编代码中可以看出,字节变量在前三个命令中扩展为整数(16位)类型,而在后两个命令中,双字节数字实际上已移位-至少可以说这有点奇怪。

向右移动没有更好

 byte = byte >> 1; clr r25 sbrc r24,7 com r25 asr r25 ror r24 

-同样的五支球队。 同时,很明显,实际上,要执行最后一个操作,您只需要一个命令

 sr r24 

而且第一次操作就没有了。 我已经反复指出,编译器当前正在创建的汇编代码并不比程序员差(尽管这是一个ARM命令系统),尤其是如果您对他有所帮助的话,突然之间就变得如此糟糕。 但是请尝试帮助编译器创建正确的代码,这可能是在shift操作中混合类型并尝试

 byte = byte >> (int8_t) 1; 

-“完全”一词没有帮助,但可以选择

  byte=(uint8_t) byte >> 1; 

给出更好的结果

 ldi r25,lo8(0) asr r25 ror r24 

-三支队伍,因为扩充至整个队伍现在只占一支队伍-最好,虽然不是完美的,但

 byte=(uint8_t) byte << 1; 

-三支队伍。 好吧,为了不编写额外的类型转换,我们使变量本身为无符号

 uint8_t byteu; 

和BINGO-汇编代码完全符合我们的期望

 byteu = byteu << 1; lsr r24 

立即显示正确的变量类型或直接将其带入运算的外观看起来有什么不同,这很奇怪,但是事实证明存在差异。

进一步的研究表明,汇编代码考虑了将结果分配给变量的类型,因为

 byteu = byte << 1; 

工作正常并产生最少的代码,并且该选项

 byte = byteu << 1; 

没有三支球队是无法做到的。

我想问一下在评论中知道的那些人,这种行为肯定是用语言的标准来描述的,但是我将再次自豪地宣布“楚科奇不是读者”,我将继续讲故事。

因此,这种技术无助于向右移动-像以前一样,有3支队伍(好吧。对于标志牌,这不是5支队伍),我无法以任何方式改善结果。
但是无论如何,我们看到带有无符号数字的换档操作比对手更快。 因此,如果我们不打算将数字的高阶位视为符号(对于寄存器,通常是这种情况),那么我们肯定需要添加unsigned属性,这将在以后进行。

事实证明,一般来说,移位是非常有趣的,让我们开始向左移位时增加位置数,然后看一下结果:<< 1占用1个时钟周期,<< 2-2,<< 3-3,4-2出乎意料,编译器应用了棘手的优化

 swap r24 andi r24,lo8(-16) 

s wap命令在一个字节中交换两个半字节。 此外,基于上一次的优化<< << 5-3,<< 6-4,<< 7-3再次出乎意料,还有另一个优化

 ror r24 clr r24 ror r24 

使用传输位,<< 8-0的小节,因为它的结果是0,所以进一步查找毫无意义。

顺便说一下,这对您来说是一项有趣的任务-您可以在最短的时间内执行一次操作

 uint16_t byteu; byteu = byteu << 4; 

将0x1234转换为0x2340。 显而易见的解决方案是执行几次命令两次

 lsl rl rol rh 

导致4 * 2 = 8项测量,我很快想出了一个选择

 swap rl ; 1243 swap rh ; 2143 andi rh,0xf0 ; 2043 mov tmp,rl andi tmp,0x0f or rh,tmp ; 2343 andi rl,0xf0 ; 2340 

这需要7个措施和一个中间寄存器。 因此,编译器生成的代码由6个命令组成,没有中间寄存器-很酷,是的。

我将此代码隐藏在“破坏者”下-尝试自己找到解决方案。
提示:在MK命令集中,存在EXCLUSIVE OR命令或TOTAL AMOUNT TWO 2

就是这样,这段美妙的代码
 swap rl ; 1243 swap rh ; 2143 andi rh,0xf0 ; 2043 eor rh,rl ; 6343 andi r2l,0xf0 ; 6340 eor rh,rl ; 2340 


我只是从这个片段中获得了审美上的愉悦。

通常,对于16位数字,当向左移动时,有符号和无符号数字的代码之间的差异会消失,这很奇怪。

让我们返回字节,开始向右移动。 我们记得,对于一个有符号的字节,我们有5个时钟周期,对于一个无符号的字节-3,并且这个时间不能减少。 或全部相同,您可以-是的,但是可以,但这是一种非常奇怪的方式(启用了优化功能的GCC-“这是一个非常奇怪的地方”),即

 byteu = (byteu >> 1) & 0x7F; 

这将为符号的两个变体恰好生成一个命令。 适合和选择

  byteu = (byteu & 0xFE) >> 1; 

但只有一个无符号的数字,带有一个带符号的数字,一切都会变得更加令人沮丧-7种度量,因此我们将继续探索第一种选择。

我不能说我了解发生了什么,因为很明显,在这样的移位之后,通过这样的常数进行逻辑乘运算(&)毫无意义(而且没有意义),但是&运算的存在会影响移位本身的代码。 “你看到了地鼠-不-我没看见,但他是。”

移位2等等,表明还清符号位很重要,但是最初数字是无符号的,通常会获得一些垃圾,“但是它起作用”,这是唯一可以说的。

不过,可以肯定地说,将寄存器和内存的内容解释为无符号数字可以使您更快地执行许多操作(例如,移位或扩展值)并生成更紧凑的代码,因此强烈建议将其用于除非另有要求(熟悉数字的解释),否则无需编写MK程序。

Source: https://habr.com/ru/post/zh-CN436956/


All Articles