把变量赋值给寄存器_散装 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,一经查实,立即删除!

相关文章

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

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

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

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

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

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

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

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

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

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

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

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

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未安装则无任何信息返回,若已安装则会返回已安装的版本信息,可通过--…

mysql中rownum分页_Oracle数据库中rownum分页

测试人员在项目测试中发现查询结果列表分页的时候,有的数据在好几页中重复显示,有的则一次都不显示,经过分析sql,原来问题出在测试人员在项目测试中发现查询结果列表分页的时候,有的数据在好几页中重复显示&#xff0c…

python买东西_Python实战之ATM+购物车

学python一个月以来的第一个工程级别项目!!!!!1.首先分析项目需求:模拟实现一个ATM 购物商城程序额度 15000或自定义---->注册功能实现购物商城,买东西加入 购物车,调用信用卡接口结账----->购物车,支付可以提现,手续费5%----->提现…

multi source replication mysql_MySQL 5.7多源复制(Multi-Source Replication)

MySQL5.7.6以后开始支持多源复制Multi-Source Replication,可以将多个主的库同步到一台slave上,从而增加从的利用率,出节省了机器,也可用于备份。首先设置master_info_repository和relay_log_info_repository两个参数,这存储同步信…

列模式 文本编辑器_UltraEdit 24.2 文本编辑器免费版

UltraEdit是由IDM官方最新出品的文本编辑器版本,它是一款性价比全球一流的文本、十六进制,HTML、PHP、Java 和 JavaScript程序文本编辑器,被认为是世界上最好的文本编辑器。UltraEdit可以满足您的所有需求,例如代码突出显示&#…

在linux中加固mysql_mysql在linux下的安装

安装环境:系统是 centos6.51、下载下载地址:http://dev.mysql.com/downloads/mysql/5.6.html#downloads下载版本:我这里选择的5.6.33,通用版,linux下64位也可以直接复制64位的下载地址,通过命令下载&#x…

mysql 多条记录选择一套_2020-11-09-Mysql(练习题第一套)

遗留问题 select语句中的别名问题 group by 中的别名问题# sql-表 FACT_DAY_SC-字段 DEPT_ID varchar(20) 电厂TJSJ_DATE DATE 日期FSL number 日发电量create table FACT_DAY_SC(DEPT_ID varchar(20),TJSJ_DATE DATE,FSL LONG);insert into FACT_DAY_SC values (10001,2020-12…

wamp的mysql触发器教程_wamp里的mysql怎么做出这个

这个表怎么做这个是phpmyadmin,他是一个独立的程序,并不是wamp独有的,你也可以独立下载phpmyadmin来使用。首先,phpmyadmin你可以把他看作是一个php写的mysql管理工具。前端页面实际上就是HTML,和一般的网页没有什么区…

wx轮播图接口学习用json格式_零基础学小程序008----列表和轮播图的实现,小程序解析json数据...

点击下面网址进入系列教程上一节带领大家实现领简单的计算器,这节来带领大家学习小程序列表功能。本节知识点1,定义本地json文件2,本地文件引入3,小程序列表渲染实现4,解析本地json(为解析网络json做准备)学习之前先来…

云服务器怎么装mysql_云服务器(windows环境)安装mysql图文教程

1、首先进入的是安装引导界面2、然后进入的是类型选择界面,这里有3个类型:Typical(典型)、Complete(完全)、Custom(自定义)。这里建议选择“完全”(Complete)安装,这样可以自定义选择MySQL的安装目录,然后点“Next”下一步&#x…

阿里mysql 二进制_Mysql binlog 之阿里canal

1、What is Canal?canal [kənl],中文翻译为 水道/管道/沟渠/运河,主要用途是用于 MySQL 数据库增量日志数据的订阅、消费和解析,是阿里巴巴开发并开源的,采用Java语言开发;历史背景是早期阿里巴巴因为杭州…

java 基本类型 引用类型_Java中的基本类型和引用类型变量的区别

基本类型:基本类型自然不用说了,它的值就是一个数字,一个字符或一个布尔值。引用类型:是一个对象类型,值是什么呢?它的值是指向内存空间的引用,就是地址,所指向的内存中保存着变量所…

mysql 学生成绩等级_JSP+SSM+Mysql实现的学生成绩管理系统

项目简介本系统是基于JSPSSMMysql实现的学生成绩管理系统。主要实现的功能有教师管理、学生管理、课程管理、学生成绩管理。难度等级:中等技术栈编辑器Eclipse Version: 2020-03 (4.15.0)前端技术基础:htmlcssJavaScript框架:JQueryH-ui后端技…