把变量赋值给寄存器_散装 vs 批发谁效率高?变量访问被ARM架构安排的明明白白...

作为过来人,我发现很多程序猿新手,在编写代码的时候,特别喜欢定义很多独立的全局变量,而不是把这些变量封装到一个结构体中,主要原因是图方便,但是要知道,这其实是一个不好的习惯,而且会降低整体代码的性能。

另一方面,最近有幸与大神「公众号:裸机思维」的傻孩子交流的时候,他聊到:“其实Cortex在架构层面就是更偏好面向对象的(哪怕你只是使用了结构体),其表现形式就是:「Cortex所有的寻址模式都是间接寻址」——换句话说「一定依赖一个寄存器作为基地址」

举例来说,同样是访问外设寄存器,过去在8位和16位机时代,人们喜欢给每一个寄存器都单独绑定地址——当作全局变量来访问,而现在Cortex在架构上更鼓励底层驱动以寄存器页(也就是结构体)为单位来定义寄存器,这也就是说,同一个外设的寄存器是借助拥有同一个基地址的结构体来访问的。”

以Cortex A9架构为前提,下面一口君详细给你解释为什么使用结构体效率会更高一些。

一、全局变量代码反汇编

1. 源文件

「gcd.s」

.text
.global _start
_start:
  ldr  sp,=0x70000000         /*get stack top pointer*/
  b  main

「main.c」

/*
 * main.c
 *
 *  Created on: 2020-12-12
 *      Author: pengdan
 */
int xx=0;
int yy=0;
int zz=0;

int main(void){
 xx=0x11;
 yy=0x22;
 zz=0x33;

 while(1);
    return 0;
}

「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=gcd
TARGETC=main
all:
 arm-none-linux-gnueabi-gcc -O1 -g -c -o $(TARGETC).o  $(TARGETC).c
 arm-none-linux-gnueabi-gcc -O1 -g -c -o $(TARGET).o $(TARGET).s
 arm-none-linux-gnueabi-gcc -O1 -g -S -o $(TARGETC).s  $(TARGETC).c
 arm-none-linux-gnueabi-ld $(TARGETC).o $(TARGET).o -Tmap.lds  -o  $(TARGET).elf 
 arm-none-linux-gnueabi-objcopy -O binary -S $(TARGET).elf $(TARGET).bin
 arm-none-linux-gnueabi-objdump -D $(TARGET).elf > $(TARGET).dis

clean:
 rm -rf *.o *.elf *.dis *.bin

【交叉编译工具,自行搜索安装】

2. 反汇编结果:

d7df7402e64d50a0267fe8bd77cb709e.png由上图可知,每存储1个int型全局变量需要「8个字节」

「literal pool (文字池)占用4个字节」

literal pool的本质就是ARM汇编语言代码节中的一块用来存放常量数据而非可执行代码的内存块。7e5a8dd2a8fae0cbcc1e4dccfaf7dd23.png

使用literal pool (文字池)的原因

当想要在一条指令中使用一个 4字节长度的常量数据(这个数据可以是内存地址,也可以是数字常量)的时候,由于ARM指令集是定长的(ARM指令4字节或Thumb指令2字节),所以就无法把这个4字节的常量数据编码在一条编译后的指令中。此时,ARM编译器(编译C源程序)/汇编器(编译汇编程序) 就会在代码节中分配一块内存,并把这个4字节的数据常量保存于此,之后,再使用一条指令把这个4 字节的数字常量加载到寄存器中参与运算。

在C源代码中,文字池的分配是由编译器在编译时自行安排的,在进行汇编程序设计时,开发者可以自己进行文字池的分配,如果开发者没有进行文字池的安排,那么汇编器就会代劳。

「bss段占用4个字节」55a3103461cb921e3e83bf883141dc57.png

每访问1次全局变量,总共需要3条指令,访问3次全局变量用了「12条指令」b4b8c904940c3a202557e25464247e98.png

14. 通过当前pc值40008018偏移32个字节,找到xx变量的链接地址40008038,然后取出其内容40008044存放在r3中,该值就是xx在bss段的地址15. 通过将立即数0x11即#17赋值给r216. 将r2的内容那个写入到r3对应的指向的内存,即xx标号对应的内存中

二、结构体代码反汇编

1. 修改main.c如下:

 /*
  2  * main.c                                                           
  3  *
  4  *  Created on: 2020-12-12
  5  *      Author: 一口Linux
  6  */
  7 struct
  8 {
  9     int xx;
 10     int yy;
 11     int zz;
 12 }peng;
 13 int main(void)
 14 {
 15     peng.xx=0x11;
 16     peng.yy=0x22;
 17     peng.zz=0x33;
 18 
 19     while(1);
 20     return 0;
 21 }

2. 反汇编代码如下:

76024ba55b5e43f5c3c7b3849db14a13.png由上图可知:

  1. 结构体变量peng位于bss段,地址是4000802c
  2. 访问结构体成员也需要利用pc找到结构体变量peng对应的文字池中地址40008028,然后间接找到结构体变量peng地址4000802c

与定义成3个全局变量相比,优点:

  1. 结构体的所有成员在literal pool 中共用同一个地址;而每一个全局变量在literal pool 中都有一个地址,「节省了8个字节」
  2. 访问结构体其他成员的时候,不需要再次装载基地址,只需要2条指令即可实现赋值;访问3个成员,总共需要「7条指令」「节省了5条指令」

「彩!」

所以对于需要大量访问结构体成员的功能函数,所有访问结构体成员的操作只需要加载一次基地址即可。

使用结构体就可以大大的节省指令周期,而节省指令周期对于提高cpu的运行效率自然不言而喻。

「所以,重要问题说3遍」

「尽量使用结构体」「尽量使用结构体」「尽量使用结构体」

三、继续优化

那么指令还能不能更少一点呢?答案是可以的, 修改Makefile如下:

TARGET=gcd                                                                                
TARGETC=main
all:
     arm-none-linux-gnueabi-gcc -Os   -lto -g -c -o $(TARGETC).o  $(TARGETC).c
     arm-none-linux-gnueabi-gcc -Os  -lto -g -c -o $(TARGET).o $(TARGET).s
     arm-none-linux-gnueabi-gcc -Os  -lto -g -S -o $(TARGETC).s  $(TARGETC).c
     arm-none-linux-gnueabi-ld   $(TARGETC).o    $(TARGET).o -Tmap.lds  -o  $(TARGET).elf
     arm-none-linux-gnueabi-objcopy -O binary -S $(TARGET).elf $(TARGET).bin
     arm-none-linux-gnueabi-objdump -D $(TARGET).elf > $(TARGET).dis
clean:
     rm -rf *.o *.elf *.dis *.bin

仍然用第二章的main.c文件

e20e388b1b6733f2c0dfe1b689858e63.png
执行结果

可以看到代码已经被优化到5条。

14. 把peng的地址40008024装载到r3中
15. r0写入立即数0x11
16. r1写入立即数0x22
17. r0写入立即数0x33
18. 通过stm指令将r0、r1、r2的值顺序写入到40008024内存中

「彩!彩!彩!彩!」

文中用到的汇编知识可以参考ARM系列文章

《从0学arm合集》

982a635b832c716b93529c2214c1f4ca.png

推荐阅读

【1】嵌入式工程师到底要不要学习ARM汇编指令?必读【2】7. 从0学ARM-汇编伪指令、lds详解【3】IP协议入门必读【4】【从0学ARM】你不了解的ARM处理异常之道【5】4. 从0开始学ARM-ARM汇编指令其实很简单【6】【典藏】大佬们都在用的结构体进阶小技巧【7】[粉丝问答6]子进程进程的父进程关系【8】C和汇编如何互相调用?嵌入式工程师必须掌握

进群,请加一口君个人微信,带你嵌入式入门进阶。

aebb92aed2e2afb0ab1be97d37fb041f.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/442469.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

威海二职工业机器人专业_工业机器人专业就业前景-山东省好的中专学校

济南电子机械工程学校,高考升学班,圆您大学梦!职教高考说明:山东省自2012年起,普通高校考试招生分春季高考与夏李高考。2020年春季高考开级为职教高考,重点面向中等职业学校学生,考试科目由知技能两部分组成。“知识”…

ls mac 显示最近修改日期_如何在Mac上按日期排序文件 | MOS86

许多Mac用户按名称和类型对文件进行排序,但是按日期排序档案最有用的方法之一就是。Mac Finder可以为文件,文档,应用程序和文件夹提供各种不同的基于日期的排序选项,并且它们通常在Finder列表视图中最为有效。在Mac OS中使用基于日…

python 正则表达式 断言 不定长表达式_MyEssay 之 Python正则表达式 —— 四种断言扩展的理解...

我们经常用正则表达式来检测一个字符串中包含某个子串,要表示一个字符串中不包含单个的某字符或某些字符也很容易,用[^...]形式就可以了。但是要表示一个字符串中不包含某个子串(由字符序列构成)的时候,用[^...]这种形式就不行了,…

python爬虫请求头是什么意思_python爬虫请求头的使用

这篇文章我们来讲一下在网站建设中,python爬虫请求头的使用。本文对大家进行网站开发设计工作或者学习都有一定帮助,下面让我们进入正文。爬虫请求头网页获取:通过urlopen来进行获取requset.urlopen(url,data,timeout)第一个参数url即为URL&a…

复化科特斯公式matlab_matlab实现复化NewtonCotes公式求积分的程序应用和代码

命令窗口输入被积函数2120ttedt。输入应为:。Step2:执行函数。输入形式为mymulNewtonCotes(ft,a,b,m,n);其中ft—被积函数,此体重,已经在第一步赋值;a—积分下限,本题中为0;b—积分…

单元格自适应宽度_最详细的Excel模块Openpyxl教程(二)-单元格操作详解

在以前的推文中,我们介绍了操作Excel的模块openpyxl的入门知识,相关推文可以从本公众号的底部相关菜单获取。接下来的推文我们来学习一下openpyxl这个python模块中的其他知识,本次推文我们来学习一下单元格(cell)操作的…

动态参数 maven_使用Jenkins Git参数实现分支标签动态选择

1.1 为什么要使用GIT参数?我们为什么要使用 git参数呢? 每个项目代码库都会有不同的分支,(如果你没有用多分支流水线的情况下)对于普通的流水线项目我们可以 让一条流水线来支持多个分支的发布,其实有时候你会发现每个分支的集成步…

走线和交互式布线_画PCB时,一些非常好的布线技巧

画PCB时,一些非常好的布线技巧布线是PCB设计过程中技巧最细、限定最高的,即使布了十几年布线的工程师也往往觉得自己不会布线,因为看到了形形色色的问题,知道了这根线布了出去就会导致什么恶果,所以,就变的…

相机裁剪旋转_怎么旋转视频画面角度

在用手机或相机拍摄视频时,有时候画面会出现镜像、画面倒转的情况,影响观看体验,那么视频怎么旋转才能将画面变为正常播放?有很多方法可以做到,以下我为你挑选了最简单的工具,详细给你说说:在线…

新代数控系统参数说明书_台湾新代宏程序编程书

点击右上角关注公众号,每天更新其实说起来宏就是用公式来加工零件的,比如说椭圆,如果没有宏的话,我们要逐点算出曲线上的点,然后慢慢来用直线逼近,如果是个光洁度要求很高的工件的话,那么需要计算很多的点&…

树莓派要mysql的密码_树莓派raspberry Pi 3B+系统中安装mysql过程中不提示输入密码,安装完后如何设置密码...

树莓派raspberry Pi 3B安装mysql未提示输入密码,安装后修改mysql密码默认密码使用mysql -uroot -p 命令连接mysql时,报错piraspberrypi:/ $ mysql -uroot -pEnter password:ERROR1698 (28000): Access denied for user rootlocalhost按照网上的说法&…

mysql中error 1786_mysql错误处理之ERROR1786(HY000)_MySQL

ERROR 1786 (HY000)【环境描述】msyql5.6.14【报错信息】执行create table ... select的时候遇到报错:db1 [test] [23:01:58]> create tablelgmnr_bak select * from lgmnr;ERROR 1786 (HY000): CREATE TABLE ... SELECTis forbidden when GLOBAL.ENFORCE_GTID_C…

深入mysql语言_MySQL对数据操作的一些深入语法

其他数据操作数据的操作也叫作crud:C:createR:readU:updateD:delete插入数据蠕虫复制就是在已有的数据的基础之上,将原来的数据进行复制,插入到相对应的表中!语法规则:in…

MySQL查询多表定义实体类_自己设计一个 JAVA + MyBatis 解析实体类多表通用查询

先来处理一下查询的字段和用到的表吧//虽然我们使用的时候是实体,但最终还是要解析成sql的,那么我们需要想好解析成sql 时候所用到的东东,提前准备好class TableEntity {String tableName; //表的真正名称肯定是要的String nickName; //表名的…

mysql table catalog_hibernate 注解@table( catalog=)作用

在阿里云RDS新建库和用户,并分配权限,而且用客户端访问连接成功。但启动应用,并修改连接用户和库地址,报错(信息如下)!字面意思是用户没有SELECT的权限,但客户端验证不是权限问题。经多步测试发现应用一直在…

mysql 导出数据库中的某张数据表_mysql 导出数据库中的某张数据表

{"moduleinfo":{"card_count":[{"count_phone":1,"count":1}],"search_count":[{"count_phone":4,"count":4}]},"card":[{"des":"阿里云数据库专家保驾护航,为用户…

pycharm 开发app_windows及mac下开发Excel, python+xlwings开发环境配置

背景:孩子他妈天天excel办公,看她每天大量重复性的工作,她是时候学习python来解放自己了,虽然我不用python ~ ~网络搜索 xlwings 是python下非常强大的处理Excel的库开发IDE选择Pycharm或者vs code配置开发环境,折腾了…

mysql 优化表_mysql里sql优化和表结构优化

开启慢查询日志 计入sqlshow variables like slow_query_log;//慢查询查看状态 OFF未开启 ON开启show variables like %log%;//没有使用索引的sql计入慢查询日志中set global log_queries_nor_using_indexes on;//开启log_queries_nor_using_indexes 为ON 记录未使用索引的查询…

sql server查询历史进程_学习笔记 | SequoiaDB SQL查询语句执行过程

本篇笔记将为大家介绍 SequoiaDB 巨杉数据库查询 SQL 语句的执行过程,以及查询语句执行过程中实例层、协调节点、编码节点、数据节点各自承担的功能。应用程序或用户想要从数据库查询需要的数据,首先通过 API 或 client 端连接数据库,将查询 …

服务器主机linux安装mysql_linux服务器上安装mysql

mysql版本:mysql-5.6.44-linux-glibc2.12-x86_64.tarlinux操作系统和版本信息:1、检查linux服务器上是否已安全mysql[rootlocalhost ~]# rpm -qa|grep -i mysql未安装则无任何信息返回,若已安装则会返回已安装的版本信息,可通过--…