散装与批发谁更有效率?ARM体系结构明确地安排了变量访问。原因是图方便,但要知道这其实是个坏习惯,会降低整体代码的性能。另一方面,最近有幸和大神的傻孩子“公众号:BareMetalThinking”交流时,他讲到:“其实Cortex在架构层面更偏向于面向对象(即使你只使用结构体),其他的表达形式是:“Cortex的所有寻址方式都是间接寻址”——换句话说“必须依赖一个寄存器作为基址”。比如同样访问外设寄存器,在过去在8位和16位计算机时代,人们喜欢将地址单独绑定到每个寄存器——作为全局变量访问,而现在Cortex鼓励底层架构驱动以寄存器页为单位定义寄存器(即is,structures),也就是说同一个外设的寄存器是通过同一个基地址的结构体来访问的。”基于CortexA9架构,我会详细给大家解释为什么使用一个结构体效率更高。一、全局变量代号反汇编1.源文件「gcd.s」.text.global_start_start:ldrsp,=0x70000000/*getstacktoppointer*/bmainbmain/**main.c**Createdon:2020-12-12*Author:pengdan*/intxx=0;intyy=0;intzz=0;intmain(void){xx=0x11;yy=0x22;zz=0x33;while(1);return0;}「map.lds」OUTPUT_FORMAT("elf32-littlearm","elf32-littlearm","elf32-littlearm")/*OUTPUT_FORMAT("elf32-arm","elf32-arm","elf32-arm")*/OUTPUT_ARCH(arm)ENTRY(_start)SECTIONS{.=0x40008000;.=ALIGN(4);.text:{gcd.o(.text)*(.text)}.=ALIGN(4);.rodata:{*(.rodata)}.=ALIGN(4);.data:{*(.data)}.=ALIGN(4);.bss:{*(.bss)}}「Makefile」TARGET=gcdTARGETC=mainall:arm-none-linux-gnueabi-gcc-O1-g-c-o$(TARGETC).o$(TARGETC).carm-none-linux-gnueabi-gcc-O1-g-c-o$(TARGET).o$(TARGET).sarm-none-linux-gnueabi-gcc-O1-g-S-o$(TARGETC).s$(目标).carm-none-linux-gnueabi-ld$(TARGETC).o$(TARGET).o-Tmap.lds-o$(TARGET).elfarm-none-linux-gnueabi-objcopy-Obinary-S$(目标).elf$(目标).binarm-none-linux-gnueabi-objdump-D$(TARGET).elf>$(TARGET).disclean:rm-rf*.o*.elf*.dis*.bin【交叉编译工具,自行搜索安装】二、反汇编结果:图片从上图可以看出,每个int型全局变量需要“8个字节”,而“文字池(literalpool)占用4个字节”。文字池的本质是一段ARM汇编语言代码段,用来存放常量数据使用文字池(literalpool)而不是可执行代码的内存块的原因是当你想使用一个4字节的常量数据时(这个数据可以是一个内存地址或者一个数值常量)在一条指令中,由于ARM指令集是定长的(ARM指令4字节或者Thumb指令2字节),不可能把这个4字节的常量数据编码成编译指令。此时,ARM编译器(编译C源程序)/汇编器(compileassembler)会在代码段分配一块内存,将这4个字节的数据常量保存在这里,然后用一条指令对这4个字节数值常量被加载到一个寄存器中参与运算。在C源代码中,文字池的分配是编译器在编译时安排的。开发者在设计汇编程序时,可以自行分配文字池。如果开发者没有安排文字池,那么汇编器会帮你安排。“bss段占4个字节。”每次访问一个全局变量,一共需要3条指令,“12条指令”用来访问3个全局变量。14、通过从当前pc值40008018开始移动32字节,找到xx变量的链接地址40008038,然后将其内容40008044取出,存入r3,即xx在bss段的地址。17为r216赋值。将r2的内容写入r3对应的内存,即xx标签对应的内存。二、结构代码反汇编1、修改main.c如下:/*2*main.c3*4*Createdon:2020-12-125*Author:AportofLinux6*/7struct8{9intxx;10intyy;11intzz;12}peng;13intmain(void)14{15peng.xx=0x11;16peng.yy=0x22;17peng.zz=0x33;1819while(1);20return0;21}2.反汇编代码如下:从上图我们可以看到结构体变量peng位于bss段,地址为4000802c。访问结构体成员,同样需要使用pc找到结构变量peng对应的文字池中地址为40008028,然后间接找到结构变量peng地址4000802c与定义三个全局变量相比,优点:结构的所有成员在文字池中共享相同的地址;而每个全局变量在文字池中各有一个地址,“节省8个字节”。访问结构体其他成员时,无需再次加载基地址,仅需2条指令即可实现赋值;访问3个成员总共需要“7条指令”,“节省5条指令”,所以对于那些需要大量访问结构体成员的函数函数,所有访问结构体成员的操作只需要加载基地址一次。使用该结构可以大大节省指令周期,节省指令周期对于提高cpu的运行效率是不言而喻的。“所以,把重要的问题说3遍”“尝试使用结构”“尝试使用结构”“尝试使用结构”3.继续优化。指令能少点吗?答案是肯定的,修改Makefile如下:-gcc-Os-lto-g-c-o$(目标).o$(目标).sarm-none-linux-gnueabi-gcc-Os-lto-g-S-o$(目标).s$(目标).carm-none-linux-gnueabi-ld$(TARGETC).o$(TARGET).o-Tmap.lds-o$(TARGET).elfarm-none-linux-gnueabi-objcopy-Obinary-S$(TARGET).elf$(目标).binarm-none-linux-gnueabi-objdump-D$(TARGET).elf>$(TARGET).disclean:rm-rf*.o*.elf*.dis*.bin仍然和main.c文件一起执行在第2章可以看到代码已经优化到5篇了。14.将peng40008024的地址加载到r3中。15.将r0写入立即数0x1116。R1将立即数写入0x2217。记忆中,本文转载自微信公众号“一口Linux”,可通过以下二维码关注。转载本文请联系易口Linux公众号。
