Dance with Compiler - EP3 ARM64 汇编传参约定以及 restrict 汇编分析

news2024/11/15 3:44:16

在 ARM64 架构(也称为 AArch64)中,函数调用约定定义了寄存器如何用于传递参数和返回值。这些约定有助于实现高效的函数调用和返回。在 ARM64 的汇编中,寄存器传参遵循以下约定:

参数传递寄存器
x0 - x7: 这 8 个寄存器用于传递函数的前 8 个参数(对于整数类型的参数)。如果函数有更多的参数,这些额外的参数会通过栈传递。

  • x0 用于第一个参数
  • x1 用于第二个参数
  • x2 用于第三个参数
  • x3 用于第四个参数
  • x4 用于第五个参数
  • x5 用于第六个参数
  • x6 用于第七个参数
  • x7 用于第八个参数

v0 - v7: 对于浮点数和 SIMD(单指令多数据)参数,这 8 个寄存器用于传递前 8 个浮点数参数(每个寄存器可以存储一个 64 位的浮点数,或者一组 128 位的 SIMD 数据)。

返回值
x0: 用于返回整数类型的结果(例如,int、long、pointer 等)。
v0: 用于返回浮点数或 SIMD 类型的结果(例如,float、double、__m128 等)。

阅读汇编代码时需要注意上述约定。

学完后实习一下:https://godbolt.org/z/zWjbo9183

仅仅是用了 restrict,性能可以提升非常大:

Compare
source:here

C++代码:


struct AvgState {
  uint64_t numerator{0};
  uint64_t denominator{0};

  double divide() { return numerator / denominator; }
};


void addBatch(size_t batch_size, AggregateDataPtr __restrict state, Column *args) __attribute__((noinline)) {
    for (size_t i = 0; i < batch_size; ++i) {
     data(state).numerator += args[0].data()[i];
        ++(data(state).denominator);

    }
}

对应汇编代码:

IAggregate<AvgAggregator>::addBatch(unsigned long, char*, Column*) [clone .isra.0]:
        cbz     x0, .L7                 <= x0 是第一个参数,batch_size,cbz(compare and branch on zero),batch_size 是 0 的话直接返回
        ldr     x2, [x2]                <= x2 是第三个参数 args,args[0] 地址等于 x2, args[0].data() 地址也等于 x2, args, args[0].data()[0]地址也等于 x2
        ldp     x3, x6, [x1]            <= x1 指向了 state,state 结构包含了两个地址相连的成员 numerator 和 denominator,所以这个指令获得了他们的地址:x3 = state.numerator.  x6 = state.denominator
        add     x5, x2, x0, lsl 3     <= args[0].data()[0] + (batch_size << 3) 得到 args[0].data() 最后一个元素的地址,用于控制循环结束。也就是说,这里是通过 address guard 的方式来控制循环结束
.L9:
        ldr     x4, [x2], 8           <= 将 x2 里的内容载入 x4,同时将 x2 加上 8(专门针对 for 循环场景设计的指令)。一条指令实现了两个能力:args[0].data()[i] 取值,i++
        add     x3, x3, x4         <= x3 = state.numerator,x4 = args[0].data()[i] ,这条指令计算 state.numerator + args[0].data()[i] 
        cmp     x5, x2           <= 判断循环是否结束(x2 的地址是否抵达了上面计算的边界)
        bne     .L9                <= 如果还没有到边界,则跳到 L9 继续循环
        add     x0, x0, x6      <= 我们可以发现,循环里没有执行过 ++(data(state).denominator); 操作。这里一把梭哈,(data(state).denominator) = (data(state).denominator) + batch_size  减少了很多指令执行。
        stp     x3, x0, [x1].      <= 把 state.numerator, state.denominator 的最新值写会到。state 结构的内存里,完成全部计算
.L7:
        ret        

这些汇编代码非常简洁,从性能角度,最最重要的一点是循环中只有一处 ldr 操作,其余都是寄存器里的算术运算。我们知道,在性能领域,访问内存往往是瓶颈所在。

但是,如果 state 上没有加 __restrict,则是完全另一幅光景:

IAggregate<AvgAggregator>::addBatchWithoutOpt(unsigned long, char*, Column*) [clone .isra.0]:
        cbz     x0, .L1  <= x0 指向 batch_size
        ldr     x5, [x2] <= x5 指向 args[0].data()[0]
        ldp     x3, x2, [x1] <= x1 指向 state,x3 = state.numerator.  x2 = state.denominator
        add     x4, x0, x2  <= x4 =  batch_size + state.denominator,也就是用 state.denominator 终值做循环结束条件
        sub     x5, x5, x2, lsl 3 <= x5  指向 args[0].data() 最后一个元素的地址,用于控制循环结束
.L3:
        ldr     x0, [x5, x2, lsl 3] <= args[0].data()[0] + x2 << 3,也就是访问  args[0].data()[i]
        add     x2, x2, 1. <= 循环加1, state.denominator++
        add     x3, x3, x0 <= args[0].data()[i]  + state.numerator -> state.numerator
        stp     x3, x2, [x1].          <= 将 x3, x2 的内容写回 x1 地址。即更新 state 在内存中的值
        cmp     x2, x4    <= 判断是否已经循环结束
        bne     .L3
.L1:
        ret

在上面的循环里,除了 ldr 访存,还多了一个 stp 写内存操作。二者巨大的性能差异也是因为这条指令而起。

为什么没有 __restrict 后性能差异如此巨大呢?我们来分析下函数签名背后蕴含的可能:

void addBatch(size_t batch_size, AggregateDataPtr __restrict state, Column *args) __attribute__((noinline)) {

state 没有使用 restrict 时,编译器必须假设 strict 可能指向了 args。如果 state 指向了 args,那么我们看循环里的两条语句可能发生什么情况:

for  (size_t i = 0; i < batch_size; ++i) {
     data(state).numerator += args[0].data()[i];   <= numerator 被更新,意味着 args[0].data 指针本身,args[0].data 里的元素,都可能被更新。编译器无法判断这是否可能,必须做最坏打算。并且,如果真的是这样,编译器还必须假设这是用于有意为之,它必须保证用户能得到符合预期的结果。
     ++(data(state).denominator); <= 这一步也是和上面一样,denominator 被更新,也意味着有一段内存被更新了,这段内存是什么?不知道,不能做任何假设。
     // 所以,到这里的时候,编译器必须把对 state 的更新写回到内存,只有这样,下一次循环才能得到“符合预期“的行为。
}

更凶猛的后果

上面是用 -O2 编译的,如果使用 -O3,还可以看到更凶猛的结果。下面分别展示了 x86 上 -O3 编译和 arm64 的 -O3 编译结果:

X86:

IAggregate<AvgAggregator>::addBatchWithoutOpt(unsigned long, char*, Column*) [clone .constprop.0]:
        mov     rax, QWORD PTR [rdi+8]
        mov     r9, rsi
        mov     rdx, QWORD PTR [rdi]
        mov     rcx, QWORD PTR [r9]
        mov     r8, rax
        lea     rsi, [rax+1048576]
        neg     r8
        lea     rcx, [rcx+r8*8]
.L2:
        add     rdx, QWORD PTR [rcx+rax*8]
        add     rax, 1
        mov     QWORD PTR [rdi], rdx
        mov     QWORD PTR [rdi+8], rax
        cmp     rax, rsi
        jne     .L2
        ret
IAggregate<AvgAggregator>::addBatch(unsigned long, char*, Column*) [clone .constprop.0]:
        mov     r8, rsi
        mov     rcx, QWORD PTR [rdi+8]
        mov     rsi, QWORD PTR [rdi]
        pxor    xmm0, xmm0
        mov     rax, QWORD PTR [r8]
        lea     rdx, [rax+8388608]
.L6:
        movdqu  xmm2, XMMWORD PTR [rax]
        add     rax, 16
        paddq   xmm0, xmm2
        cmp     rdx, rax
        jne     .L6
        movdqa  xmm1, xmm0
        add     rcx, 1048576
        psrldq  xmm1, 8
        mov     QWORD PTR [rdi+8], rcx
        paddq   xmm0, xmm1
        movq    rax, xmm0
        add     rax, rsi
        mov     QWORD PTR [rdi], rax
        ret

ARM64:

IAggregate<AvgAggregator>::addBatchWithoutOpt(unsigned long, char*, Column*) [clone .constprop.0]:
        ldr     x4, [x1]
        ldr     x1, [x0, 8]
        ldr     x2, [x0]
        add     x5, x1, 1048576
        sub     x4, x4, x1, lsl 3
.L2:
        ldr     x3, [x4, x1, lsl 3]
        add     x1, x1, 1
        add     x2, x2, x3
        stp     x2, x1, [x0]
        cmp     x1, x5
        bne     .L2
        ret
IAggregate<AvgAggregator>::addBatch(unsigned long, char*, Column*) [clone .constprop.0]:
        ldr     x1, [x1]
        ldp     x4, x3, [x0]
        add     x2, x1, 8388608
        movi    v0.4s, 0
.L6:
        ldr     q1, [x1], 16
        add     v0.2d, v0.2d, v1.2d
        cmp     x2, x1
        bne     .L6
        addp    d0, v0.2d
        add     x1, x3, 1048576
        str     x1, [x0, 8]
        fmov    x1, d0
        add     x1, x1, x4
        str     x1, [x0]
        ret

可以看到,此时用到了 SIMD 指令:
movi v0.4s, 0, add v0.2d, v0.2d, v1.2d, addp d0, v0.2d, fmov x1, d0,一定程度上可以加速执行。

不过也需要注意,上面 2d 表示一条指令只能同时处理两个 64 位整数,也许快不了太多。得256、512 bit 的 SIMD 才能更显神威。

BTW,这篇文章讲 SIMD 以及汇编指令讲得挺不错:https://no5-aaron-wu.github.io/2022/06/14/SIMD-3-NeonAssembly/

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

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

相关文章

Golang | Leetcode Golang题解之第396题旋转函数

题目&#xff1a; 题解&#xff1a; func maxRotateFunction(nums []int) int {numSum : 0for _, v : range nums {numSum v}f : 0for i, num : range nums {f i * num}ans : ffor i : len(nums) - 1; i > 0; i-- {f numSum - len(nums)*nums[i]ans max(ans, f)}return…

超声波气象站

超声波气象站的设计优势包括&#xff1a; 非接触式测量&#xff1a;超声波气象站利用超声波技术进行测量&#xff0c;可以在不接触被测物体的情况下进行精确的测量。这意味着测量过程不会受到外界干扰或影响&#xff0c;提高了测量的准确性和可靠性。 多参数测量&#xff1a;超…

LVGL 控件之标签(lv_label)

目录 一、概述二、标签1、设置文本2、长模式3、文本着色4、文本选择5、内置图标6、事件7、API 函数 一、概述 标签部件由三个部分组成&#xff1a;主体背景、滚动条和所选文本&#xff1a; LV_PART_MAIN&#xff08;主体背景&#xff09;使用所有典型的背景属性和文本属性。 …

windows手工杀毒-寻找可疑进程之网络连接

上篇回顾&#xff1a;windows手工杀毒-寻找可疑进程之句柄 上篇我们简单介绍了如何通过句柄发现可疑进程&#xff0c;主要有两个方向&#xff0c;一个是通过命名句柄的名称&#xff0c;利用全局唯一的句柄名反向标识进程&#xff0c;另一个就是通过句柄查看进程占有的资…

二、线性结构及算法

文章目录 一、稀疏数组1.1 实际需求1.2 基本介绍1.3 应用实例 二、队列2.1 引入2.2 基本介绍2.3 数组模拟队列 三、链表3.1 链表介绍3.2 单链表的应用实例3.3 单链表面试题3.4 双向链表应用实例3.5 单向环形链表 四、栈4.1 基本介绍4.2 数组模拟栈4.3 链表模拟栈4.4 栈实现综合…

PMP--一、二、三模--分类--14.敏捷--技巧--宣言

文章目录 技巧一模14.敏捷--宣言--重视实在的--工作的软件大于流程和文档19、 [单选] 一个关键干系人坚持团队用正式的文档广泛地记录软件代码。产品负责人解释说&#xff0c;虽然一定数量的文档是必要的&#xff0c;但团队成员最好把时间花在开发软件上&#xff0c;因为这对客…

Java 每日一刊(第2期):搭建开发环境

文章目录 JVM、JRE、JDKJVM&#xff08;Java Virtual Machine&#xff0c;Java 虚拟机&#xff09;JRE&#xff08;Java Runtime Environment&#xff0c;Java 运行时环境&#xff09;JDK&#xff08;Java Development Kit&#xff0c;Java 开发工具包&#xff09;JVM、JRE、JD…

1765asp.net古镇旅游网站VS开发sqlserver数据库web结构c#编程web网页设计

博主介绍&#xff1a;专注于Java .net php phython 小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不然下次找不到哟 我的博客空间发布了1000毕设题目 方便大家学习使用 感兴趣的可以…

Python | Leetcode Python题解之第395题至少有K个重复字符的最长子串

题目&#xff1a; 题解&#xff1a; class Solution:def longestSubstring(self, s1: str, k: int) -> int:if k 1: return len(s1)n len(s1)res 0for c in range(1, len(set(s1)) 1):# 滑窗中字母种类个数恰好为 cfreq Counter()l cnt tcnt 0 for r, ch in enu…

安防监控/视频汇聚平台EasyCVR无法启动并报错“error while loading shared libraries”,如何解决?

安防监控/视频汇聚平台EasyCVR视频管理系统以其强大的拓展性、灵活的部署方式、高性能的视频能力和智能化的分析能力&#xff0c;为各行各业的视频监控需求提供了优秀的解决方案。通过简单的配置和操作&#xff0c;用户可以轻松地进行远程视频监控、存储和查看&#xff0c;满足…

AI基础 L10 Adversarial Search I 对抗性搜索

Multiagent Environments In multiagent environments, each agent must: — Consider everyone else’s actions — Coordinate in order to act coherently 多个智能体&#xff08;agent&#xff09;相互作用&#xff0c;每个智能体都具有自己的目标和行动策略。在多智能体环…

C++ | Leetcode C++题解之第396题旋转图像

题目&#xff1a; 题解&#xff1a; class Solution { public:int maxRotateFunction(vector<int>& nums) {int f 0, n nums.size();int numSum accumulate(nums.begin(), nums.end(), 0);for (int i 0; i < n; i) {f i * nums[i];}int res f;for (int i …

【经纬度坐标系、墨卡托投影坐标系和屏幕坐标系转换详解】

地图坐标系转换详解 1. 引言2. 坐标系定义2.1 经纬度坐标系2.2 墨卡托投影坐标系3.3 屏幕坐标系 2. 坐标系间的转换2.1 经纬度坐标系到墨卡托投影坐标系2.2 墨卡托投影坐标系到经纬度坐标系2.3 墨卡托投影坐标系到屏幕坐标系2.4 屏幕坐标系到墨卡托投影坐标系2.5 经纬度坐标系到…

mfc140u.dll文件错误的相关修复方法,4种方法修复mfc140u.dll

当面对基于Microsoft Visual C开发的应用程序出现启动或运行失败时&#xff0c;mfc140u.dll文件错误往往是罪魁祸首之一。这个动态链接库&#xff08;DLL&#xff09;文件对于许多Windows软件来说是必不可少的&#xff0c;因为它包含了重要的编程代码和数据。如果发现此文件损坏…

数据结构(7.1)——查找的基本概念

基本概念 查找——在数据结构集合中寻找满足某种条件的数据元素的过程称为查找 查找表&#xff08;查找结构&#xff09;——用于查找的数据集合称为查找表&#xff0c;它由同一类型的数据元素(或记录)组成 关键字——数据元素中唯一标识该元素的某个数据项的值&#xff0c;…

【C++二分查找 贪心】1488. 避免洪水泛滥

本文涉及的基础知识点 C二分查找 贪心&#xff1a;决策包容性 LeetCode1488. 避免洪水泛滥 你的国家有无数个湖泊&#xff0c;所有湖泊一开始都是空的。当第 n 个湖泊下雨前是空的&#xff0c;那么它就会装满水。如果第 n 个湖泊下雨前是 满的 &#xff0c;这个湖泊会发生 洪…

【Canvas与艺术】菊花孔雀螺旋

【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>菊花孔雀螺旋</title><style type"text/css">…

HTML 揭秘:HTML 编码快速入门

HTML 揭秘&#xff1a;HTML 编码快速入门 一 . 前端知识介绍二 . HTML 介绍三 . HTML 快速入门四 . HTML 编辑器 - VSCode4.1 插件安装4.2 修改主题配色4.3 修改快捷键4.4 设置自动保存4.5 创建 HTML 文件4.5 书写 HTML 代码4.6 常见快捷键 五 . 基础标签5.1 字体标签5.1.1 col…

SpringCloud之配置中心git示例

SpringCloud之配置中心git示例 随着线上项目变的日益庞大&#xff0c;每个项目都散落着各种配置文件&#xff0c;如果采用分布式的开发模式&#xff0c;需要的配置文件随着 服务增加而不断增多。 某一个基础服务信息变更&#xff0c;都会引起一系列的更新和重启&#xff0c;…

探索有向图与无向图中深度优先搜索(DFS)的边类型——3×3 网格分析

探索有向图与无向图中深度优先搜索(DFS)的边类型——33 网格分析 一、基本概念二、有向图中的 DFS 边类型分析三、有向图 DFS 的 C 代码实现在图的深度优先搜索(DFS)过程中,边的分类对于理解算法的执行流程及其复杂性至关重要。在有向图和无向图中,DFS 过程中遇到的边可以…