ARM嵌入式编译器-volatile关键字对编译器优化的影响

news2024/10/7 20:29:45

volatile限定符告知计算机,其他agent(而不是变量所在的程序)可以改变该变量的值。通常它被用于硬件地址以及在其他程序或同时运行的线程中共享数据。要求编译器不要对其描述的对象作优化处理,对它的读写都需要从内存中访问。

使用volatile关键字声明变量,可以让编译器不对该变量进行优化操作。如果在一些需要使用volatile关键字的地方而没有使用它时,那么编译器可能会优化对该变量的访问,并生成意外的代码或达不到预期的功能。

目录

1 volatile意味着什么

2 什么时候该使用volatile

3 不使用volatile可能出现的问题

4 强制使用特定指令来访问内存

 5 使用volatile和不使用volatile的汇编对比

6 总结

编译器会优化什么

作用

特点


1 volatile意味着什么

将变量声明为volatile,是在告诉编译器,该变量的值可能随时会被其他实体(比如操作系统或者硬件)改变。该声明可以保证编译器不会优化对该变量的使用,即使该变量未被使用或者未被更改。

2 什么时候该使用volatile

 对于可能在定义它们的作用域之外被修改的变量,使用volatile关键字。比如:

  • 会被多个子程序读写的全局变量。如果程序在某些计算中使用到了全局变量,由于需要经常对该变量进行读写操作,编译器为了加速读写操作,会生成代码将该变量的值加载到寄存器中,以执行该计算。如果相同的全局变量随后在另一个子程序中被使用,编译器可能会直接读取寄存器中的现有值,而不是去内存当中加载(寄存器中的是最新的值,还没有更新到内存中,而该子程序的本意是使用内存中的值),这可能导致计算错误。这种直接读取寄存器里的值的做法,是因为优化器认为该变量是non-volatile的,不能从外部被修改,而这种假设对于内存映射的外设是不正确的。 
  • 被用作定时(sleep或者timer)函数的延时变量。如果这种变量从未被使用过,编译器可能会将整个延时函数代码移除,除非将该变量声明为volatile。
  • 被中断服务子程序调用的变量。如下示例代码中,中断子程序async_interrupt在类中声明定义,但是它可能被硬件异步调用。被调用后会改变一个名为buffer_full的变量,如果不把buffer_full声明为volatile,check_stream函数可能会读不到buffer_full被async_interrupt更改后的值,导致程序错误。
    • class myclass
      {
          public:
          int check_stream();
          void async_interrupt();
          private:
          bool buffer_full;  // must be declared as volatile
      };
      int myclass::check_stream()
      {
          int count = 0;
          while (!buffer_full)
          {
              count++;
          }
          return count;
      }
      void myclass::async_interrupt()
      {
          buffer_full = !buffer_full;
      }

此外:

  •  当访问一些内存映射的外设时,必须使用volatile关键字声明。就算编译时采用 -O0(默认不优化),仍然不能保证每个变量都被当作volatile。
  • volatile 并不意味着内部线程通信或者同步(inter-thread communication or synchronization),要达到这个目的,需要使用原子性操作,而不是volatile。
  • 中断或者信号处理器必须使用原子性或者volatile sig_atomic_t 类型的变量,但不是任意类型的volatile变量,以确保多线程执行下的同步。
  • 在内联汇编代码前,也可以使用volatile修饰。

3 不使用volatile可能出现的问题

如果需要使用volatile的时候而没有使用,编译器就会认为该变量不会被当前作用域外的其他实体修改。因此,编译器可能会进行一些用户意想不到的优化操作,可能会导致:

  • 在轮询硬件时,代码可能会陷入循环。
  • 优化可能会导致删除实现故意定时延迟的代码。

4 强制使用特定指令来访问内存

使用volatile关键字声明变量,并不能保证使用特定的机器指令来访问它。比如Cortex®-R7 and Cortex-R8的AXI外设端口是一个64-bit的外设寄存器。这个寄存器必须使用STM(多寄存写入)来写入,而不是STRD或者一对STR指令。不能保证编译器在响应相关变量或指针类型上的volatile修饰时选择该寄存器所需的访问方法。因此还可以使用volatile关键字来修饰内联汇编代码。

__asm__ volatile("stm %1,{%Q0,%R0}" : : "r"(val), "r"(ptr));
__asm__ volatile("ldm %1,{%Q0,%R0}" : "=r"(val) : "r"(ptr));

 5 使用volatile和不使用volatile的汇编对比

有如下两段代码:

test1:

int buffer_full;
int read_stream(void)
{
    int count = 0;
    while (!buffer_full)
    {
        count++;
    }
    return count;
}

test2: 

volatile int buffer_full;
int read_stream(void)
{
    int count = 0;
    while (!buffer_full)
    {
        count++;
    }
    return count;
}

 test1和test2唯一的区别就是buffer_full变量是否被声明为volatile。buffer_full是一个结束while循环的flag,当buffer_full==true时,停止计数,跳出循环并返回count。我们先看编译结果,再进行分析:

使用 armclang --target=arm-arm-none-eabi -march=armv8-a -Os -S 命令对这两段程序进行编译:

test1:

1 read_stream:
2         movw    r0, :lower16:buffer_full
3         movt    r0, :upper16:buffer_full
4         ldr     r1, [r0]
5         mvn     r0, #0
6 .LBB0_1:
7         add     r0, r0, #1
8         cmp     r1, #0
9         beq     .LBB0_1     ; infinite loop
10         bx      lr

test2:

1 read_stream:
2         movw    r1, :lower16:buffer_full
3         mvn     r0, #0
4         movt    r1, :upper16:buffer_full
5 .LBB1_1:
6         ldr     r2, [r1]     ; buffer_full
7         add     r0, r0, #1
8         cmp     r2, #0
9         beq     .LBB1_1
10         bx      lr

 先看test1的汇编程序,第6行到第9行为while循环语句,寄存器r1里的值为buffer_full,进入.LBB0_1的循环后,通过第8行的cmp指令不断比较r1是否为0,以此判断是否要跳出循环。由于test1没有使用volatile关键字声明变量buffer_full,所以在.LBB0_1的循环内一直读取的是寄存器r1的值,如果在轮询的过程中,有其他子程序改变了buffer_full的值,test1也不会跳出循环,因为它并不知道buffer_full已经被更新了。

而在test2程序中,寄存器r1中存储的是buffer_full的地址,r2中存储的是它的值。由于使用了volatile声明,编译器认为该变量是易变的,有被其他子程序更改的风险,所有在第5行到第9行的循环中,每次读取buffer_full的值都是使用 ldr r2, [r1] ,直接加载内存当中的值,而不是如test1中的,读取寄存器的值。所以当外部程序更改了buffer_full的值,test2就能立马读取到真实的buffer_full,从而跳出循环。

6 总结

智能的(进行优化的)编译器可能会把变量的值临时储存在寄存器上,便于下次读取,以节约时间,这个过程被称为高速缓存。但是有一些agent在内存上改变了变量的值,寄存器上的还是旧数据,这样就出错了。如果被volatile 关键字修饰,编译器不会进行高速缓存,直接去内存中读取该变量的数据。


编译器会优化什么

  • 将内存变量缓存到寄存器中。
  • 调整指令顺序,充分利用CPU指令流水线,进行指令重新排序读写指令。

作用


告诉编译器该变量值是不稳定的,可能被更改,需要去内存中读取该值而不是读取寄存器中的备份

  • 多个线程都要用到的某个变量,而且变量的值会被改变
  • 中断服务子程序中访问到的非自动变量
  • 并行设备硬件寄存器的变量(如状态寄存器)


特点

  • 易变的
  • 不可优化,告诉编译器不要对volatile声明的变量进行各种优化保证程序员写在代码中的指令一定会被执行
  • volatile int a;a = 1; 如果未声明为volatile两条代码会合并成一条。
  • 顺序执行的(原子性):保证volatile变量间的顺序性,不会被编译器进行乱序优化
  • 能否和const一起用:可以,const是只读,volatile是去内存中读取
  • 指针可以是volatile
  • 可以修饰函数参数
     

参考文章:

Effect of the volatile keyword on compiler optimizationhttps://developer.arm.com/documentation/100748/0620/Writing-Optimized-Code/Effect-of-the-volatile-keyword-on-compiler-optimization?lang=en#a48-effect-of-the-volatile-keyword-on-compiler-optimization__c-code-for-nonvolatile-and-volatile-buffer-loops

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

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

相关文章

文献阅读:LLaMA: Open and Efficient Foundation Language Models

文献阅读:LLaMA: Open and Efficient Foundation Language Models 1. 文章简介2. 模型训练 1. 训练数据2. 模型结构3. 模型训练 1. Optimizer2. 效率优化 3. 效果评估 1. 经典任务下效果 1. Commen Sense Reasoning2. Closed-book Question Answering3. Reading Co…

数据分析03——矩阵常用计算方法和函数

0、前言: 数组:计算机领域的概念矩阵:数学领域的概念对于Numpy而言,矩阵是数组的分支 1、创建矩阵: 字符串创建矩阵:mat1 np.matrix(‘1 2;3 4’)列表形式创建矩阵:mat2 np.matrix([[5, 6],…

MySQL基础(八)聚合函数

上一章讲到了 SQL 单行函数。实际上 SQL 函数还有一类,叫做聚合(或聚集、分组)函数,它是对一组数据进行汇总的函数,输入的是一组数据的集合,输出的是单个值。 1. 聚合函数介绍 什么是聚合函数 聚合函数作…

深度学习目标检测项目实战(五)—基于mobilenetv2和resnet的图像背景抠图及其界面封装

深度学习目标检测项目实战(五)—基于mobilenetv2和resnet的图像背景抠图及其界面封装 该项目很有意思,也是比较前沿,项目主要参考了开源代码: https://github.com/PeterL1n/BackgroundMattingV2 环境搭建 kornia0.4.1 tensorboard2.3.0 to…

图像修复_criminis算法及改进算法学习小结

摘要 对图像修复专题学习情况的一个总结,学习内容包括: (1)综述文献的阅读及对图像修复的理解。 (2)criminis算法的仿真情况。 (3)criminis算法的改进算法的仿真 一、 前言 1&…

【leetcode】138.复制带随机指针的链表

《力扣》138.复制带随机指针的链表 给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设…

[羊城杯 2020]a_piece_of_java

首先jd-gui进行反编译 简单查看发现有用的类就两个一个是 MainContrller.class和InfoInvocationHandler.class public class MainController {GetMapping({"/index"})public String index(CookieValue(value "data", required false) String cookieDa…

【C++】类和对象(初阶认识)#上篇#

目录 对面向过程和面向对象的初步认识 类的引入 封装 和 类的访问限定符 所以祖师爷在类中还引入了访问权限 用类定义变量 类的理解和对象的实例化 sizeof 计算类对象的大小 类对象的成员函数在公共代码区 this 指针 对面向过程和面向对象的初步认识 什么,是…

分片集群-搭建

分片集群 高数据量和吞吐量的数据库应用会对单机的性能造成较大压力,大的查询量会将单机的CPU耗尽,大的数据量对单机的存储压力较大,最终会耗尽系统的内存而将压力转移到磁盘IO上。 为了解决这些问题,有两个基本的方法: 垂直扩展和水平扩展。 垂直扩展:增加更多的…

阿里云服务器地域和可用区怎么选择合适?

阿里云服务器地域和可用区怎么选择?地域是指云服务器所在物理数据中心的位置,地域选择就近选择,访客距离地域所在城市越近网络延迟越低,速度就越快;可用区是指同一个地域下,网络和电力相互独立的区域&#…

ROHM常见的电冰箱控制电路图,轻松了解冰箱工作原理

​冰箱是我们日常生活中使用频率非常高的电器,它可以将食物和饮料保存在低温下,以延长它们的保质期。冰箱的工作原理主要基于制冷循环和温度控制。 先看电路图: 1. 单门直冷式电冰箱重锤式控制电路 电路的基本组成:采用重锤式启…

win10安装pytorch全网最好用的教程[2023.5.7更新]

目录 0.关于pytorch a. 什么是 PyTorch ? b. 为何选择 PyTorch ? 1.安装pytorch 1.1确定关联性 1.2下载最新版本的pytorch 1.3.pytorch历史版本下载 1.4 避坑 1.4.1、猜测 1.4.2、验证 1.4.3、解决方案 1.5、检验 0.关于pytorch a. 什么是…

前端学习----webpack入门学习

webpack是前端项目工程化的具体解决方案 主要功能:它提供了友好的前端模块化开发支持,以及 代码压缩(将空格和注释全部消除,变成一行代码)、 处理浏览器端JavaScript的兼容性(只管写高级语法,自动向下兼容。例如ie8不支持let 语法…

【Shell】一天快速入门

1 Shell解析器 Linux提供的shell解析器有6种: /bin/sh /bin/bash /sbin/nologin /bin/csh /bin/dash /bin/tashsh是bash的软连接。 2 Shell脚本 2.1 hello.sh脚本 #!/bin/bash echo hello world!#! 告诉系统这个脚本需要什么解释器来执行; 脚本以#!…

NCNN----Monodepthv2单目深度估计 小米手机部署

题目要求:学习了解单目深度估计模型MonoDepthv2,基于NCNN推理框架部署到小米手机 MonoDepthv2 论文:Digging Into Self-Supervised Monocular Depth Estimation MonoDepthv2 源码:Monodepth2 GitHub 分析: 1&#xff0…

Illustrator如何进行任务自动化之实例演示?

文章目录 0.引言1.动作的录制2.对文件播放动作3.批处理 0.引言 因科研等多场景需要进行绘图处理,笔者对Illustrator进行了学习,本文通过《Illustrator CC2018基础与实战》及其配套素材结合网上相关资料进行学习笔记总结,本文对任务自动化进行…

Cadence技巧总结学习(DRC、Annotate)持续更新~

Cadence技巧总结学习持续更新~ 你还可以再哪里看到这篇文章:知乎 1. 画叉 对于芯片上不用的引脚信号画上号,如下: 按大写X就可以了,或是双脚引脚,在跳出的界面中,Is No Connect上✔。 2. 画线快捷键&#…

【关于C++中----智能指针】

文章目录 一、概念引入二、内存泄漏2.1 内存泄漏概念及其危害2.2 内存泄漏分类2.3 避免内存泄漏 三、智能指针的原理3.1 RAII3.2 像指针一样 四、三类智能指针4.1 auto_ptr4.2 unique_ptr4.3 shared_ptr 五、循环引用和weak_ptr 一、概念引入 在我们日常写代码过程中&#xff…

ARM学习(21)STM32 外设Can的认识与驱动编写

笔者来聊聊can的认识以及can接收数据的驱动编写 1、STM32 Can 外设的认识 Can的特性就不多说了,主要来聊聊can的一些标识符以及收发状态。can有一套收发机制,发送和接收都有硬件缓存,叫邮箱,通过下面的图可以看出,下面…

ESP32学习笔记 -- ESP32-S3 使用外部 PSRAM

ESP32-S3是乐鑫科技推出的一款专为物联网而生的SOC芯片,该芯片同时支持WIFI和BLE蓝牙功能,集成了高性能的Xtensa 32位LX7双核处理器,最高主频可达240MHz。 根据乐鑫官方资料介绍,该芯片内置512KB SRAM片上内存,并且具有45个可编程GPIO管脚和丰富的通信接口。为了更好地扩…