条件控制与条件传送详解

条件控制与条件传送详解

提要

CSAPP3e中文译本 3.6.5 用条件控制来实现条件分支 3.6.6 用条件传送来实现条件分支

CSAPP3e第三章前面主要是介绍了机器级代码的二进制形式和汇编形式、反汇编、x86汇编的基础指令、条件码及其访问方式等。

在介绍到汇编语言的条件分支时分了两小节(3.6.5,3.6.6)分别介绍实现条件分支的两种形式:

  1. 用控制的条件转移实现(结合有条件和无条件跳转)
  2. 用数据的条件转移实现

并对这两种方式的适用场景,哪种在哪些场景下效率更高进行了说明,以及一些可能会造成的错误。

笔者在这里要首先指出的是,如果使用C语言编程遇到条件分支(当时几乎是肯定会遇到啦^^),大可以直接按照我们熟悉的if-else模板编写代码即可,

if (test-expr){then-statement;
}
else{else-statement;
}

无论我们的C语言代码是按照哪种方式编写的,聪明的编译器会在保证安全的前提下自动优化条件分支的实现方式,将我们的C代码用最合适的分支方式编译为汇编代码。本文内容是为了从机器级代码汇编和现代CPU工作原理的层次,来帮助理解分支程序的性能,也反过来更好地理解现代计算机系统的工作原理

以下笔者以计算两个整型值的差的绝对值的程序为例,分析条件控制和条件传送的区别和关系。

条件控制

要实现算两个整型值的差的绝对值,我想大多数人的第一反应是以下程序:

int abs_diff(int x, int y)
{if(x < y)return y - x;elsereturn x - y;
}

先判断两个值哪个较大,然后用较大的减较小的,这个代码完全没有问题,这种条件分支的实现形式称为条件控制

然后我们来编译以下这个代码gcc -Og -S abs_diff.c -o abs_diff.s,注意这里的-Og参数是关闭编译器的自动优化,使得编译器忠实地编译我们的C代码。这里笔者把abs_diff.s文件的关键部分复制出来:

abs_diff:
.LFB0:.cfi_startproccmpl	%esi, %edijl	.L4movl	%edi, %eaxsubl	%esi, %eaxret
.L4:movl	%esi, %eaxsubl	%edi, %eaxret.cfi_endproc

笔者注:可以认为x保存在寄存器%edi中,y保存在寄存器%esi中。

可以看到,编译器确实忠实地按照我们编写的C代码结构进行了编译,比较两个寄存器中的参数值,然后返回较大的值减去较小的值的结果。将这种条件控制的条件分支实现形式用C语法来表达应该是这样的:

int goto_absdiff(int x, int y){if (x > y){goto x_ge_y;}return y - x;x_ge_y:return x - y;
}

当然,所有C语言老师都会要求大家不要使用goto,因为它会是的程序非常难以阅读和调试,还容易出错。我们这里使用goto是为了模拟汇编中的JMP类指令的行为。看到这里,想必读者应该能明白上面所谓的结合有条件和无条件跳转是什么意思了,在汇编语言中,需要结合有条件和无条件跳转,才能利用JMP类命令实现在两个分支(then-statementelse-statement)中必有一个被执行。

条件传送

上述函数的条件传送的实现形式如下:

int abs_diff(int x, int y){int result_0 = x - y;int result_1 = y - x;if (x < y){return result_1;}else{return result_0;}
}

可以看到,条件传送的分支实现方式是先将两种分支的值都算出来,然后再比较哪个参数较大,返回对应的结果。编译上述代码gcc -Og -S abs_diff_1.c -o abs_diff_1.s,得到的关键汇编代码如下:

abs_diff:
.LFB0:.cfi_startprocmovl	%edi, %eaxsubl	%esi, %eaxmovl	%esi, %edxsubl	%edi, %edxcmpl	%esi, %edijl	.L3
.L1:rep ret
.L3:movl	%edx, %eaxjmp	.L1.cfi_endproc

没有问题,编译器同样忠实地编译了我们的C代码结构。

那么,这两种条件分支的实现方式到底有什么区别呢?为什么说在保证随机输入的情况下,第二种的运行速度是比第一种要快的。

条件控制和条件分支的效率

我们放开编译器的优化选项,即让编译器自己去优化我们的C代码,来编译第一种实现方式条件控制

gcc -S -O1 abs_diff.c -o abs_diff_opt.s,注意这里开启O1级别的编译器优化-O1。得到的abs_diff_opt.s的关键汇编代码如下:

abs_diff:
.LFB0:.cfi_startprocmovl	%esi, %edxsubl	%edi, %edxmovl	%edi, %eaxsubl	%esi, %eaxcmpl	%esi, %edicmovl	%edx, %eaxret.cfi_endproc

大家可以对比一下上面条件传送中得到的汇编代码,几乎是一样的。所以说,在编译器优化之后,这段汇编代码实际上执行的是条件传送的条件分支实现方式。也就是说,编译器认为,在保证安全的前提下,这段代码使用条件传送来实现比使用条件控制来实现要更加高效

原理

以下内容摘自CSAPP3e中文译本 Page 146

为了理解为什么基于条件数据传送的代码会比基于条件控制转移的代码性能要好,我们必须了解一些关于现代处理器如何运行的知识。正如我们在第4章和第5章中看到的,处理器通过流水线(pipelining)来获得高性能,在流水线中,一条指令的处理要经过一些列的阶段,每个阶段执行所需操作的一小部分(例如,从内存取指令,确定指令类型,从内存读数据,执行算术运算,向内存写数据,以及更新程序计数器)。这种方法通过重叠连续指令的步骤来获得高性能,例如,在取一条命令的同时,执行它前面一条指令的算术运算。要做到这一点,要求能够事先确定要执行的指令序列,这样才能保持流水线中充满了待执行的指令。当机器要到条件跳转(也称为“分支”)时,只有当分支条件求值完成后,才能决定分支往哪边走,处理器采用非常精密的分支预测逻辑来猜测每条跳转指令是否会执行。只要它的猜测还比较可靠(现代微处理器设计试图达到90%以上的成功率),指令流水线就会充满着指令。另一方面,错误预测一个跳转,要求处理器丢掉它为该跳转指令后所有指令已做的工作,然后再开始从正确位置其实的指令取填充流水线,正如我们会看到的,这样一个错误预测会招致很严重的处罚,浪费大约15~30个时钟周期,导致程序性能严重下降。

可以看到,当输入比较随机的情况下,CPU是很难在条件控制方式下精准地预测哪条分支会被执行的,而错误预测将付出高昂的代价(原书中有具体的错误预测代价计算方式,有兴趣可自查),这时,我们通过条件传送的实现方式,则会获得相对更优、更稳定的性能。

条件传送相当于把原本可能浪费在跳转的时间用在了计算另外一条分支上,所获得的性能提升取决于跳转所浪费的时间和计算另外一条分支的时间对比。不过从另一点来看,由于只有最后返回之前才进行条件的判断,条件传送更有利于流水线一直处于满的状态,运行时间更加稳定。

条件传送并不总是可行的

那有人可能就要问了,既然如此,我们把所有条件分支都实现为条件传送的方式岂不是最优,那还要条件控制的方式做什么呢?事实上恰恰相反,条件传送的可行情况是十分受限的,大部分情况下,编译器会将条件分支实现为条件控制的形式。比如下面这个C程序(同样来CSAPP):

long cread(long *xp){return (xp ? *xp : 0);
}

当指针结果为空时返回0,否则返回指针所指向的值。貌似很适合实现为条件传送:

cread:movq (%rdi), %raxtestq %rdi, %rdimovl $0, %edxcmov %rdx, %raxret

但实际上这个实现是非法的,因为即使当测试为假时,movq指令对xp的见解引用还是发生了,这将导致一个间接引用空指针的错误。所以,必须用条件控制分支方式来编译这段C代码。

使用条件传送指令,也不总是会提高代码的效率。因为毕竟要先计算出then-statemntelse-statement的结果,如果这些计算比较复杂,而最终有没有被执行,那很多计算就被白费了。因此,编译器必须考虑浪费的计算和由于分支预测错误所早晨改的性能处罚之间的相对性能。根据CSAPP原书的说明,只有当两个表达式都是很容易的计算时,例如都只是一条加法指令,编译器才会使用条件传送,而通常情况下,即使许多分支预测错误的开销会超过更复杂的计算,编译器还是会使用条件控制转移。笔者理解,编译器还是相对比较保守的。

__bulitin_expect

最后再说明一下,本文内容是为了从机器级代码汇编和现代CPU工作原理的层次,来帮助理解分支程序的性能,也反过来更好地理解现代计算机系统的工作原理。而在日常的代码编写中,按照正常的逻辑来编写程序即可,即使有优化的需求,现代编译器都会帮你进行优化。

当然,如果你非常确定哪一条分支大概率会被执行,你也可以通过\_\_builtin_except来告诉编译器。使用\_\_bulitin_expect这个宏来告诉编译器这个if更有可能会选择哪一个分支,从而让编译器生成出跳转可能比较小的汇编代码。

Ref:

https://blog.csdn.net/qq_33113661/article/details/90750145?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163081410616780366559662%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=163081410616780366559662&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-1-90750145.pc_search_insert_download&utm_term=%E6%9D%A1%E4%BB%B6%E6%8E%A7%E5%88%B6%EF%BC%8C%E6%9D%A1%E4%BB%B6%E4%BC%A0%E9%80%81%E4%B8%8E__builtin_expect&spm=1018.2226.3001.4187

CSAPP3e

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

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

相关文章

联合体(union)的使用方法及其本质

联合体&#xff08;union&#xff09;的使用方法及其本质 转自&#xff1a;https://blog.csdn.net/huqinwei987/article/details/23597091 有些基础知识快淡忘了&#xff0c;所以有必要复习一遍&#xff0c;在不借助课本死知识的前提下做些推理判断&#xff0c;温故知新。 1…

linux设备驱动之串口移植,Linux设备驱动之UART驱动结构

一、对于串口驱动Linux系统中UART驱动属于终端设备驱动&#xff0c;应该说是实现串口驱动和终端驱动来实现串口终端设备的驱动。要了解串口终端的驱动在Linux系统的结构就先要了解终端设备驱动在Linux系统中的结构体系&#xff0c;一方面自己了解的不够&#xff0c;另一发面关于…

linux python复制安装,复制一个Python全部环境到另一个环境,python另一个,导出此环境下安装的包...

复制一个Python全部环境到另一个环境&#xff0c;python另一个,导出此环境下安装的包导出此环境下安装的包的版本信息清单pipfreeze>requirements.txt联网&#xff0c;下载清单中的包到all-packet文件夹[[email protected] ~]# pip download -d ./all-packet -r requirement…

NVIDIA英伟达的Multi-GPU多卡通信框架NCCL

NVIDIA英伟达的Multi-GPU多卡通信框架NCCL 笔者注&#xff1a;NCCL 开源项目地址&#xff1a;https://github.com/NVIDIA/nccl 转自&#xff1a;https://www.zhihu.com/question/63219175/answer/206697974 NCCL是Nvidia Collective multi-GPU Communication Library的简称&…

C语言n个坐标点间的最大距离,c语言已知两点坐标,求另一点到穿过这两点的直线最短距离。...

c语言已知两点坐标&#xff0c;求另一点到穿过这两点的直线最短距离。以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;c语言已知两点坐标&#xff0c;求另一点到穿过这两点的直线最短距离。#…

[分布式训练] 单机多卡的正确打开方式:理论基础

[分布式训练] 单机多卡的正确打开方式&#xff1a;理论基础 转自&#xff1a;https://fyubang.com/2019/07/08/distributed-training/ 瓦砾由于最近bert-large用的比较多&#xff0c;踩了很多分布式训练的坑&#xff0c;加上在TensorFlow和PyTorch之间更换&#xff0c;算是熟…

s3c2416开发板 linux,S3C2416移植内核Linux3.1的wm9713声卡过程

移植内核的声卡驱动。原因没有声卡驱动&#xff0c;WM9713声卡驱动移植(原来的内核有UDA1341声卡驱动&#xff0c;我们再次基础上直接修改)1、直接复制内核得到三个文件:s3c2416_wm9713.c , wm9713.c , s3c2416_ac97.c.linux-3.1\sound\soc\codecs\Wm9713.c---->wm9713.c;li…

Linux查看文件内容命令:cat, tail, head, more, less

Linux查看文件内容命令&#xff1a;cat, tail, head, more, less cat 直接显示整个文件。 cat直接显示全部文件内容&#xff0c;没有换页等交互。 cat filenamemore more命令&#xff0c;功能类似 cat &#xff0c;cat命令是整个文件的内容从上到下显示在屏幕上。 more会…

linux查看队列 msg,linux第10天 msg消息队列

cat /proc/sys/kernel/msgmax最大消息长度限制cat /proc/sys/kernel/msgmnb消息队列总的字节数cat /proc/sys/kernel/msgmni消息条目数消息队列综合案例//server#include #include #include #include #include #include #include #include #define ERR_EXIT(m)do{perror(m);}wh…

Linux中 C++ main函数参数argc和argv含义及用法

Linux中 C main函数参数argc和argv含义及用法 简介 argc 是 argument count的缩写&#xff0c;表示传入main函数的参数个数&#xff1b; argv 是 argument vector的缩写&#xff0c;表示传入main函数的参数序列或指针&#xff0c;并且第一个参数argv[0]一定是程序的名称&…

c语言六位抢答器课程设计,51单片机八路抢答器课程设计

;说明&#xff1a;本人的这个设计改进后解决了前一个版本中1号抢答优先的问题&#xff0c;并增加了锦囊的设置&#xff0c;当参赛选手在回答问题时要求使用锦囊&#xff0c;则主持人按下抢答开始键&#xff0c;计时重新开始。;八路抢答器电路请看下图是用ps仿真的&#xff0c;已…

ELF文件详解—初步认识

ELF文件详解—初步认识 转自&#xff1a;https://blog.csdn.net/daide2012/article/details/73065204 一、 引言 在讲解ELF文件格式之前&#xff0c;我们来回顾一下&#xff0c;一个用C语言编写的高级语言程序是从编写到打包、再到编译执行的基本过程&#xff0c;我们知道在C…

埃及分数问题c语言,埃及分数问题(转)

今日&#xff0c;小雨和小明来到网络中心&#xff0c;继续与刘老师讨论“数的认识”问题。刘老师说&#xff1a;“还有一种‘埃及分数’需要认识。这是一类分裂分数的思维题&#xff0c;对思维能力的训练很有价值。”小明说&#xff1a;“有意思&#xff0c;愿洗耳恭听。”刘老…

linux常用命令--开发调试篇

前言 Linux常用命令中有一些命令可以在开发或调试过程中起到很好的帮助作用&#xff0c;有些可以帮助了解或优化我们的程序&#xff0c;有些可以帮我们定位疑难问题。本文将简单介绍一下这些命令。 转自&#xff1a;https://www.yanbinghu.com/2018/09/26/61877.html 示例程序…

简单有趣的c语言小程序,一个有趣的小程序

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼源码:#include #include #include #include #include HINSTANCE g_hInstance 0;LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPreInstance,LPSTR lpCmdLine,int nSh…

linux下ora 01110,ORA-01003ORA-01110

Oracle 9i数据库登录时&#xff0c;提示ORA-01003&ORA-01110&#xff0c;大概意思是数据文件存储介质损坏。startup nomount,正常&#xff1b;alter database mount,也正常&#xff1b;alter database open,提示如下&#xff1a;alter database open*ERROR 位于第 1 行:ORA…

x11转发:通过ssh远程使用GUI程序

x11转发&#xff1a;通过ssh远程使用GUI程序 我们常常使用ssh服务远程操控服务器&#xff0c;大多数操作我们都可以通过命令行命令来实现。 ssh远程无法查看GUI程序 现在&#xff0c;笔者在x11-test目录下放入一张图片test.jpg&#xff0c;并通过opnencv-python写一个简单的…

操作系统引导详细过程

操作系统引导详细过程 转自&#xff1a;https://blog.csdn.net/lijie45655/article/details/89366372 就直观而言&#xff0c;我们所见到计算机启动的过程是&#xff1a;按下电脑开机键&#xff0c;系统在黑色的屏幕下打印出一些英文语句、然后进入进度条状态&#xff0c;最后…

android 自定义透明 等待 dialog,Android自定义Dialog内部透明、外部遮罩效果

Android自定义Dialog内部透明、外部遮罩效果发布时间&#xff1a;2020-09-09 03:01:41来源&#xff1a;脚本之家阅读&#xff1a;117作者&#xff1a;zst1303939801本文实例为大家分享了Android自定义Dialog遮罩效果的具体代码&#xff0c;供大家参考&#xff0c;具体内容如下图…

对比损失的PyTorch实现详解

对比损失的PyTorch实现详解 本文以SiT代码中对比损失的实现为例作介绍。 论文&#xff1a;https://arxiv.org/abs/2104.03602 代码&#xff1a;https://github.com/Sara-Ahmed/SiT 对比损失简介 作为一种经典的自监督损失&#xff0c;对比损失就是对一张原图像做不同的图像…