浅析代码优化——编译器优化原理

什么是预编译和编译器优化?如何进行代码优化? 在C语言编程中,预编译和编译器优化是构建和优化程序的两个重要步骤。它们的主要任务是将源代码转化为可执行程序,并在尽可能短的时间内实现最佳性能。下面我们将详细解释这两个概念以及如何进行代码优化。 阅读详情

https://www.cnblogs.com/yanlingyin/archive/2012/02/04/programoptimization.html

开篇

相信有过编码经验的人都知道,程序的正常运行,只是最基本的要求。更多的,还要考虑程序的性能,运行效率,组织结构,和重用性等等。

今天将简单的讨论一下如何优化程序性能。

要写出高效的程序,可能多数初学者想到的是在程序中用合适的算法和数据结构。这确实是一中提高程序性能的主要方法。

而这里要讨论的是另一种方法,也是很多人都忽略但确实很重要的方法。也是我们这篇文章的主题:

如何编写出编译器能有效优化的源代码。

编译器优化的局限性

 没有万能的东西,编译器也一样。现代编译器都会对源代码进行优化,以提高程序的性能。比如linux下的GCC编译器就能控制优化的等级,优化等级高,对应的程序性能好。对于给定的代码,编译器并不能保证能得到最好的性能,它也有局限性。所以才需要程序员能写出编译器易于理解和优化的代码。

编译器简单的说是讲程序语言代码翻译为机器码,既然是翻译,就不能改变程序想要表达的意思。所以编译器对程序总是小心的使用安全的优化。也就是说:优化后的版本和未优化的版本有一样的行为。

下面简单说一下编译器的局限性

1、存储器别名引用

存储器别名引用就是两个不同的指针可能指向存储器中的同一个位置。

例子说明:看如下的代码

两个程序似乎有相同的行为。都是将存储在*yp处的值两次加到*xp处存储的位置。这时,twiddle2的效率会更高一些。(可以认为第二个是第一个的简单优化)

第一个函数需要6次存储器引用(读*xp两次,写*xp两次,读*yp两次)而第二个函数只需3次存储器引用(读*yp两次,写*xp一次)

当然上面讨论的是在*xp和*yp指向不同位置的基础上。

现在考虑*xp和*yp指向同一位置的情况:

函数twiddle1的结果是*xp的值翻四倍,twiddle2得到的是3倍。编译器并不知道twiddle1会如何被引用,即不知道*xp,*yp是否指向存储器的同一位置,如果指向同一位置,编译器就不能把函数1优化为函数2的形式。因为他们有不同的行为。

为了编译后程序行为不被改变,也就是所谓的安全优化,编译器只能假设*xp 、*yp 会指向相同的位置。也就是说编译器不会把函数1优化为函数2的形式。这造成了一个妨碍优化的因素。

2、函数副作用

用例子说明问题,看下面简单的代码:

简单的看两个函数能产生相同的结果。同样的,可以暂时认为func2是func1的优化版本。

但是func2 调用了f()一次,而func1调用了f()两次。如果他们调用的函数f()修改了全局变量,结果就会有所不同

考虑如下f()代码:

int f()

{

  return counter++;

}

这个函数修改了全局变量counter,函数调用的次数会改变程序的行为。也就是说:这个函数有副作用。

大多数编译器在代码优化的时候不会试图判断一个函数是否有副作用。为了安全的优化,编译器会认为所有函数都有副作用。

这也成为妨碍编译器优化的另一因素。

对于函数会有副作用的情况,在写代码的时候就要“帮助”编译器做出判断。和上面的代码对应的,如果函数f()没有副作用,在写程序的时候就把代码写成func2的形式。因为编译器是不会把func1()优化为func2()形式的。

消除循环的低效率

请看下面的代码:

看for循环里,里面的判断条件i<vec_length(v) (不用去管这个函数是干什么用的)我们知道,for循环每次都要判断 i 的值是否满足条件,按照上面的代码也就意味着每次循环都要执行函数vec_length(v)。如果这个函数的值不会因为循环而改变,那么把这个求值过程放在循环外面,只执行一次函数就把值保存起来,会有更好的效果。

改进后的代码如下:

这是一个常见的代码优化例子,称为代码移动。适用于要执行多次(如在循环里)但计算结果不变的情况。而这类优化是编译器不能达到的。

编译器会非常小心,为了安全,它认为所有调用的函数都有副作用。

消除不必要的存储器引用

考虑下面的代码:

看被圈起来的部分,OPER表示某种操作,for循环的目的是将数组data[]中的所有值依次执行某种操作并把最后的值存入*dest。

观察循环内的代码,下面列出每次循环对存储器的操作:

1、读*dest

2、读data[i]

3、(通过计算)写*dest

熟悉汇编的可以参看以上代码的汇编形式:

 

对于第 i 次循环,第 i 次读的*dest的值刚好就是第 i-1 次写入*dest的值。

由此可见,每次对于*dest的写操作是多余的。因为下一次又会对他写入覆盖前面的值,而我们需要的只是最后一次写入。

考虑到这里,可以用一个临时变量来记录*dest 的值,这样就不用每次循环都重复对*dest的读写工作。在循环结束后讲临时变量的值写入*dest即可。

 如上所说,引入临时变量x的代码如下

对应的汇编代码:

小记

 本文先讨论了编译器在代码优化方面的局限性,如简单的存储器别名引用和函数副作用。为了安全的优化,编译器总是考虑最糟的情况,他会认为所有的存储器引用都会有别名引用,所有的函数都会有副作用。而这两点成了限制编译器优化能力的很大因素。所以我们就有责任编写出编译器易于优化的代码。当确定了存储器没有别名引用时,或者当确定函数没有副作用时,适当的修改代码,能协助编译器编写出性能好的程序。

C++中的编译器优化技巧实现原理 1. 背景介绍 1.1 为什么需要编译器优化 编译器优化是编译器在将源代码转换为目标代码时进行的一系列改进,以提高生成代码的性能。这些优化可以提高程序的运行速度、减少程序的内存占用、降低程序的能耗等。编译器优化在现代软件开发中具有重要意义,因为它可以帮助开发者在不修改源代码的情况下,实现程序性能 阅读详情

相关推荐

Ferry工单平台部署

ferry工单平台部署

运维求生之路 1897

《编译器设计》第九章——优化简介

代码优化的目标是在编译时发现有关程序运行时行为的信息,并利用该信息来改进编译器生成的代码。改进可能有许多种形式。优化最常见的目标是提高编译后代码的运行速度。但对于某些应用程序来说,编译后代码的长度要比其执行速度更重要。例如,考虑某个将烧录到只读存储器的应用程序,其代码长度会影响整个系统的成本。优化的其他目标包括降低执行的能耗、提高代码对实时事件的响应、降低对内存的总访问量、优化寄存器的使用等。有两个关于优化的比较经典的例子,后面补上。

yelvens的博客 7912

IEEE Access投稿全流程解析:从初次提交到最终接收的完整指南

本文详细解析了IEEE Access期刊从初次投稿到最终接收的全流程,包括ORCID注册、论文投稿七步法、回稿再投流程及最终版提交等关键步骤。特别强调了4-6周快速同行评审的特点,为研究者提供了一份实用的投稿指南,帮助高效完成IEEE Access期刊投稿。

weixin_29273849的博客 913

【编译原理优化介绍】

编译器优化是指在编译过程中,通过对源代码进行一系列变换和调整,生成更加高效的目标代码的过程。这些优化技术不仅可以提高程序的运行速度,还可以减少程序的内存占用,甚至在某些情况下可以降低程序的能耗。优化可以在编译的不同阶段进行,包括源代码到中间表示(IR)的转换、中间表示的优化、以及中间表示到机器代码的转换。中间表示(Intermediate Representation,IR)是介于源代码和目标代码之间的一种代码表示形式。它是一种抽象的、独立于具体硬件的中间层,便于进行各种编译器优化和代码生成。

武帝为此的博客 2453

KITTI数据集下载及解析

KITTI数据集下载及解析 版本 更新时间 更新内容 作者 1 V 1.0 xxx 完成主体内容 W. Xiao 2 文章目录KITTI Dataset1 简介1.1 数据采集平台1.2 坐标系2 数据解析2.1 image文件2.2 velodyne文件2.3 calib文件2.4 label文件3 KITTI可视...

u013086672的博客 5万+

【项目立项管理、项目整合管理】30天通过软考高项-第二天

①项目或阶段的概述;②范围目标、范围的评估标准,证明达到完⼯标准的证据;③质量目标、项目和产品质量的评估标准、相关核实信息和实际里程碑交付日期以及偏差原因;④成本目标,包括可接受的成本区间、实际成本,产⽣任何偏差的原因等;⑤最终产品、服务或成果的确认信息的总结;⑥进度计划目标,包括成果是否实现项目预期效益:如果在项目结束时未能实现效益,则指出效益实现程度并预计未来实现情况;

weixin_42520723的博客 999

编译器工具链(三)——编译优化

GCC和LLVM编译器提供了各种各样的优化和检测技术。编译器中的优化可以提高性能或减少代码体积,同时各种插装技术可以帮助了解系统的内部结构。本文将介绍一些常用的编译器优化。

weixin_54911557的博客 2160

三步实现Java分布式追踪:像“开灯”一样简单

摘要: 本文通过三步实现Java分布式追踪: 引入依赖:配置Spring Cloud Sleuth+Zipkin,自动埋点无需代码侵入; 启动服务:REST接口自动生成Trace/Span ID,日志可验证; 可视化分析:通过Zipkin UI查看调用链耗时和拓扑关系。 亮点:AOP自动埋点、零业务代码修改、支持多服务链路追踪,助力快速定位性能瓶颈。适用于微服务诊断,生产环境可调整采样率平衡性能。(150字)

墨夶的博客 2710

volatile与编译器代码优化浅析

转载自:http://blog.chinaunix.net/uid-24641004-id-3395896.html Volatile有什么用? 最近写ARM9的裸板C语言代码,比如说我想表示到GPBCON的地址的内容,我们往往会这样写: #define GPBCON (*(volatile unsigned int*)0x56000010)  

不舍驽马 623

Python无法拒绝的表白界面(完整代码)

Python制作一个无法拒绝的表白界面的完整代码。

AIGC 5万+

视觉Transformer实战 | Data-efficient image Transformer(DeiT)详解与实现

在计算机视觉领域,Vision Transformer (ViT) 已经证明了纯 Transformer 架构在图像分类任务上的强大能力。然而,ViT 通常需要在大规模数据集上预训练才能达到最佳性能,DeiT (Data-efficient image Transformer) 通过引入一系列训练策略和优化,使得 Transformer 模型能够在相对较小的数据集上取得优异表现。本节将详细介绍 DeiT 的技术原理,并使用 PyTorch 从零开始实现 DeiT 模型。

盼小辉丶的博客 1339

GCC优化浅析

一、前言 GCC(GNU Compiler Collection)是一个免费的编译软件,能够支持Ada、C、C++、Fortran、Java和Objective-C多种语言,并且适用于多个平台包括x86、ARM、MIPS、PowerPC等等。GCC的功能是相当强大的,本文主要集中在GCC的代码优化方面进行探讨,努力将优化算法和具体实现讲述清楚完全。本文规划如下:第二部分是GCC的简单介绍,包

u014529578的专栏 1916

基于STM32、HAL库的IP5356M_DBZ_CA快充协议芯片简介及驱动程序设计

IP5356M_BATTERY_LEVEL_25 = 1, /* 电池电量25% */IP5356M_CHARGE_STATE_CHARGE_DONE = 4, /* 充电完成 */IP5356M_CHARGE_STATE_FAST = 2, /* 快速充电 */IP5356M_CHARGE_STATE_NONE = 0, /* 无充电 */IP5356M_CHARGE_STATE_PRE = 1, /* 预充电 *//* 延时1秒 */

corlin6688的博客 1415

浅析Javascript运行原理

Javascript是一种广泛应用于网页前端开发的脚本语言。本篇博客将深入探讨Javascript的运行原理,包括Javascript引擎、解释器和编译器、执行上下文、作用域和作用域链、事件循环、内存管理和异步编程等方面。

Jerry_zpon的博客 1726

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

volatile与编译器优化

volatile与编译器代码优化浅析 2012-11-03 16:05:17 分类: 嵌入式 Volatile有什么用? 最近写ARM9的裸板C语言代码,比如说我想表示到GPBCON的地址的内容,我们往往会这样写: 1.  #define GPBCON (*(volatile unsigned int*)0x56000010)   这里的volatile关键字就是要告诉编译器,这个

anbaixiu的博客 1060

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

JVM编译浅析

1.编译器与解释器的区别与优劣 编译器:将完整的用户代码经过一系列处理翻译成完整的目标语言。 解释器:直接利用用户提供的输入执行源程序中的操作。因此可以逐个语句的执行源程序。 编译器产生的机器语言目标程序通常比解释器快很多,代码的优化效果也比解释器好。但是利用解释器逐句执行的特点,解释器更容易进行错误诊断,与用户的交互效果也比编译器好。 2.一个编译器的结构 一个编译器可以分为前端和后端: 前端...

JustKian的博客 291

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

Java编译期优化与运行期优化技术浅析

Java语言的“编译期”是一段不确定的过程,因为它可能指的是前端编译器把java文件转变成class字节码文件的过程,也可能指的是虚拟机后端运行期间编译器(JIT)把字节码转变成机器码的过程。 下面讨论的编译期优化指的是javac编译器将java文件转化为字节码的过程,而运行期间优化指的是JIT编译器所做的优化。 编译期优化 虚拟机设计团队把对性能的优化集中到了后端的即时编译器(

wutao610783814的博客 250

LLVM浅析

llvm浅析

lingjunjie的博客 946

C语言关键字浅析-restrict

### C语言关键字浅析系列 ### ### ISO/ANSI C 关键字 ### restrict是C语言中的一个类型限定词,如果在编程的时候不是很常用C语言指针方面的语法,可能对这个关键字就不是很熟悉 字面上讲,restrict意为“严格的”,我们可以想象为“严格地限定” 其实restrict的出现是为了解决一种叫指针混淆(Pointer aliasing)的问题,并且只用于指针的语法...

Johan_Joe_King的博客 985

C程序编译过程浅析

前几天看了《程序员的自我修养——链接、装载与库》中的第二章“编译和链接”,主要根据其中的内容简单总结一下C程序编译的过程吧。 我现在一般都是用gcc,所以自然以GCC编译hellworld为例,简单总结如下。   hello.c源代码如下: #include int main() { printf(“Hello, world.\n”); return 0; }

李布衣 502

C程序编译过程浅析(转)

转载:http://blog.youkuaiyun.com/koudaidai/article/details/8092647 通常我们使用gcc来生成可执行程序,命令为:gcc hello.c,默认生成可执行文件a.out 其实编译(包括链接)的命令:gcc hello.c 可分解为如下4个大的步骤: 预处理(Preprocessing) 编译(Compilation) 汇编(Assembly) 链接(...

weixin_34289744的博客 72
上一篇: 你有没有想到,这样的观点挖掘引擎?
下一篇: vs2013下配置googletest环境
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值