Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >如何分析和提高(C/C++)程序的编译速度?

如何分析和提高(C/C++)程序的编译速度?

原创
作者头像
李肖遥
修改于 2020-07-03 06:27:39
修改于 2020-07-03 06:27:39
1.5K03
代码可运行
举报
运行总次数:3
代码可运行

一个别人的vs 2010 的程序, 编译, 加载数据, 运行, 需要个把小时。当改代码然后再运行的时候,又要个把小时才能编译看结果.这样岂不是很浪费时间, 怎么办?这样如何修改程序,怎么提高效率啊?

当我们遇到这样情况的时候,是不是不知所措呢?怎么防止遇到这样的情况呢,我们来分析一下程序加速的一些方法。

硬件、编译器造成的

使用好点的电脑无疑是一个操作上的最佳选择,其次,对于编译器也是可以编译选项优化的,例如在VS环境中,可以通过配置属性来实现,具体步骤如下,大家可以参考:https://blog.csdn.net/yizhou2010/article/details/52635288

代码编写风格

多使用自加、自减指令和复合赋值表达式

你觉得使用i++ ,i = i + 1,i += 1有区别吗?我们来测试一下C代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void asd() {}
int main() {
    int i=0;
    i++;
    asd();  //方便区分上下文
    i=i+1;
    asd();
    i+=1;
    return 0;
}

反汇编:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mov     [rbp+i], 0    //i的初始化
add     [rbp+i], 1    //i++;
call    _Z3asdv         ; asd(void)
add     [rbp+i], 1    //i=i+1;
call    _Z3asdv         ; asd(void)
add     [rbp+i], 1    //i+=1;

我们看到这个结果是一样的,但是在更加复杂的表达式中就会多生成几个指令了,而且用 i += 1 的,总是比写 i = i + 1的要稍微那么好看些。

除法换成乘法或者移位来表达

除法就是由乘法的过程逆推来的,依次减掉(如果x够减的)y^(2^31),y^(2^30),...y^8,y^4,y^2,y^1。减掉相应数量的y就在结果加上相应的数量,一般来说,更耗时间一些,用一个demo来测试一下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
auto time_start = std::chrono::system_clock::now();
int iCount = 100000;
double k ;
for (int i = 0; i < 1000000; i++)
{
     tmp = iCount / 2;
}
std::chrono::duration<double> time_spend = std::chrono::system_clock::now() - time_start;
double test1 = time_spend.count() * 1000;
cout<<"test1 cost "<<time_cost<<" ms"<<endl;

time_start = std::chrono::system_clock::now() ;
for (int i = 0; i < 1000000; i++)
{
     tmp = iCount * 0.5f;
}
time_spend = std::chrono::system_clock::now() - time_start;
test2 = time_spend.count() * 1000;
cout<<"test2 cost "<<time_cost<<" ms"<<endl;

time_start = std::chrono::system_clock::now() ;
for (int i = 0; i < 1000000; i++)
{
     tmp = iCount >>1;
}
time_spend = std::chrono::system_clock::now() - time_start;
test3 = time_spend.count() * 1000;
cout<<"test3 cost "<<time_cost<<" ms"<<endl;

我们输出结果会发现,移位和乘法比除法要省3-5倍时间,移位相对而言是最省时间的。

多用直接初始化,少用拷贝初始化

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
string s1 = "hiya";    // 拷贝初始化
string s2("hello");    // 直接初始化
string s3(10, 'c');    // 直接初始化

当我们使用拷贝初始化时,我们要求编译器将右侧运算对象拷贝到正在创建的对象中,如果需要的话还要进行类型转换,会浪费一定的资源时间,而直接初始化是要求编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数和拷贝构造函数。

我们来看看Primer中怎么说的

当用于类类型对象时,初始化的复制形式和直接形式有所不同:直接初始化直接调用与实参匹配的构造函数,复制初始化总是调用复制构造函数。复制初始化首先使用指定构造函数创建一个临时对象,然后用复制构造函数将那个临时对象复制到正在创建的对象”

还有一段说到:

通常直接初始化和复制初始化仅在低级别优化上存在差异,然而,对于不支持复制的类型,或者使用非explicit构造函数的时候,它们有本质区别: ifstream file1("filename")://ok:direct initialization ifstream file2 = "filename";//error:copy constructor is private

局部变量、静态局部变量、全局变量与静态全局变量

  • 局部变量是存在于堆栈中的,对其空间的分配仅仅是修改一次esp寄存器的内容即可;
  • 静态局部变量是定义在函数内部的,静态局部变量定义时前面要加static关键字来标识,静态局部变量所在的函数在多调用多次时,只有第一次才经历变量定义和初始化;
  • 当一个文件或者数据反复使用时,应该存储在全局变量中,避免重复加载使用;
  • 静态全局变量是静态存储方式,静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。

静态变量是低效的,当一块数据被反复读写,其数据会留在CPU的一级缓存(Cache)中

代码冗余度

避免大的循环,循环中避免判断语句

在写程序过程中,最影响代码运行速度的往往都是循环语句,我记得当时在写matlab的时候,处理大数据,都是禁止用循环的,特别是多层嵌套的循环语句。

其次,尽量将循环嵌套控制在 3 层以内,有研究数据表明,当循环嵌套超过 3 层,程序员对循环的理解能力会极大地降低。同时,这样程序的执行效率也会很低。因此,如果代码循环嵌套超过 3 层,建议重新设计循环或将循环内的代码改写成一个子函数。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
for (i=0;i<100;i++)
{
    for (j=0;j<5;j++)
    {
       for (j=0;j<5;j++)
        {
            /*处理代码*/
        }
    }
}

多重 for 循环中,如果有可能,应当尽量将最长的循环放在最内层,最短的循环放在最外层,以减少 CPU 跨切循环层的次数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
for (i=0;i<100;i++)
{
    for (j=0;j<5;j++)
    {
            /*处理代码*/
    }
}

改为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
for (j=0;j<5;j++)
{
    for (i=0;i<100;i++)
    {
            /*处理代码*/
    }
}

逻辑判断不要在循环中使用,当 for 循环的次数很大时,执行多余的判断不仅会消耗系统的资源,而且会打断循环“流水线”作业,使得编译器不能对循环进行优化处理,降低程序的执行效率

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if (condition)
{
    for (i = 0;i < n;i++)
    {
        /*处理代码*/
    }
}
else
{
    for (i = 0;i < n;i++)
    {
        /*处理代码*/
    }
}

尽量避免递归,递归就是不停的调用自身,所以非常消耗资源,甚至造成堆栈溢出和程序崩溃等等问题!

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int Func(int n)
{
if(n < 2)
return 1;
else
return n*Func(n-1);
}

因此,掌握循环优化的各种实用技术是提高程序效率的利器,也是一个高水平程序必须具备的基本功。

尽量不使用继承和多重继承

多重继承增加了类的继承层次的复杂性,调试难度增加当然风险也增加了,而且使用父类指针指向子类对象变成了一件复杂的事情,得用到C++中提供的dynamic_cast来执行强制转换。但是dynamic_cast是在运行期间而非编译期间进行转换的,因此会会带来一些轻微的性能损失,建议类型转换尽量采用c++内置的类型转换函数,而不要强行转换

少用模板,因为模板是编译期技术,大量采用模板也会增加编译时间

在c++primer3中,有一句话:

在多个文件之间编译相同的函数模板定义增加了不必要的编译时间 简单点说,对于一个zhidaovector的函数,比如size(),如果在不同的cpp中出现,在这些文件编译的时候都要把vector::size()编译一遍。然后在链接的时候把重复的函数去掉,很显然增加了编译时间。模版函数需要在编译的时候实例化zhidao,所以呢,不把模版的实现代码放到头文件中的话(在头文件中实例化),那么每个使用到这个模版的cpp的都要把这个模版重新实例化一遍,所以增加了编内译时间

编码依赖性

声明与实现分离,删除不必要的#include

  • 使用include时,只需要include这个接口头文件就好
  • 并不是所有的文件都需要包含头文件 iostream,定义了输出函数引用就好
  • ostream头文件也不要,替换为 iosfwd, 为什么,参数和返回类型只要前向声明(forward declared )就可以编译通过

尽量减少参数传递,多用引用来传递参数。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
bool func1(string s1,  string s2)
bool func2(string *s1, string *s2)
bool func3(string &s1, string &s2)

指针和引用都不会创建新的对象,函数func2和func3不需要调用析构和构造函数,函数func1使用值传递在参数传递和函数返回时,需要调用string的构造函数和析构函数两次。

适当的采用PIMPL模式

很实用的一种基础模式,通过一个私有的成员指针,将指针所指向的类的内部实现数据进行隐藏。将实现放到CPP里,主要作用在于编译分离,其实是增加了编码量以及初次编译时长,增量编译才体现作用。例如:指针的大小为(64位)或32(8位),X发生变化,指针大小却不会改变,文件c.h也不需要重编译。

未完待续

方法还有很多,比如使用多线程,多任务并行编译,分布式编译,预编译等等,另外,在编译大型项目时,分布式编译更优,往往能够大幅度提升性能。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
关注微信公众号『技术让梦想更伟大』,后台回复“m”查看更多内容,回复“加群”加入技术交流群。
长按前往图中包含的公众号关注
长按前往图中包含的公众号关注

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
R语言亚组分析1行代码实现!
本来找了好久没找到可以实现这个功能的R包,都打算自己写个包了,没想到这几天找到了!
医学和生信笔记
2023/08/30
1.4K1
R语言亚组分析1行代码实现!
Publish做亚组分析有问题吗?
Publish包有一个subgroupAnalysis函数也可以实现亚组分析。我在之前的推文中说这个函数有一些问题,所以不推荐使用。
医学和生信笔记
2023/10/10
5370
Publish做亚组分析有问题吗?
R语言生存分析:Cox回归
上次介绍了生存分析中的寿命表、K-M曲线、logrank检验、最佳切点的寻找等,本次主要介绍Cox回归。
医学和生信笔记
2023/02/14
1.8K1
R语言生存分析:Cox回归
「R」管道统计分析——rstatix使用指南
rstatix 包提供了一个与「tidyverse」设计哲学一致的简单且直观的管道友好框架用于执行基本的统计检验, 包括 t 检验、Wilcoxon 检验、ANOVA、Kruskal-Wallis 以及相关分析。
王诗翔呀
2020/07/03
3K0
「R」管道统计分析——rstatix使用指南
我承认tidyverse已经脱离了R语言的范畴
最近知乎热议:R和Python谁更优雅的问题,或者谁更适合数据分析的问题,各种讨论,非常值得一看:
邓飞
2022/05/19
6920
我承认tidyverse已经脱离了R语言的范畴
R语言方差分析总结
这篇文章涵盖了孙振球,徐勇勇《医学统计学》第4版中关于方差分析的章节,包括:多样本均数比较的方差分析/多因素实验资料的方差分析/重复测量设计资料的方差分析/协方差分析。
医学和生信笔记
2022/11/15
2.8K0
R语言方差分析总结
R语言倾向性评分:回归和分层
倾向性评分有4种应用,前面介绍了倾向性评分匹配及matchIt和cobalt包的使用:R语言倾向性评分:匹配
医学和生信笔记
2023/02/14
1.5K0
R语言倾向性评分:回归和分层
R语言生存曲线的可视化(超详细)
关于Cox模型诊断和汇总在之前的推文中已经进行过详细的讲解:R语言生存分析:Cox回归
医学和生信笔记
2023/02/14
3K0
R语言生存曲线的可视化(超详细)
真的!森林图(Forest Plot)全部绘制技巧都在这了
今天这篇推文,小编就带大家了解一下森林图(Forest Plot) 的绘制方法,主要内容如下:
郭好奇同学
2021/07/30
35.6K1
真的!森林图(Forest Plot)全部绘制技巧都在这了
p for trend/ p for interaction/ per 1 sd 的R语言实现
本篇主要介绍P for trend、p for interaction、per 1 sd的R语言实现,关于每一项的具体含义,可参考文中给出的链接,或者自己搜索学习。
医学和生信笔记
2023/02/14
1.4K0
R语言有序logistic回归-因变量为等级资料
“医学和生信笔记,专注R语言在临床医学中的使用、R语言数据分析和可视化。主要分享R语言做医学统计学、临床研究设计、meta分析、网络药理学、临床预测模型、机器学习、生物信息学等。
医学和生信笔记
2022/11/15
2K0
R语言有序logistic回归-因变量为等级资料
R语言多项逻辑回归-因变量是无序多分类
因变量是无序多分类资料(>2)时,可使用多分类逻辑回归(multinomial logistic regression)。
医学和生信笔记
2022/11/15
1K0
R语言多项逻辑回归-因变量是无序多分类
R语言方差分析的注意事项
R语言做方差分析很简单,就是一个函数aov(),包括但不限于单因素方差分析、多因素方差分析、协方差分析、重复测量方差分析等,都是这个函数。
医学和生信笔记
2022/11/15
1.5K0
R语言方差分析的注意事项
R自定义构建函数绘制相关性条形图
构建好绘图函数后让我们来进行可视化的操作,由于原始数据较多在此我们筛选一部分数据进行可视化操作
R语言数据分析指南
2022/09/21
4700
R自定义构建函数绘制相关性条形图
R语言多个样本均数的多重比较
对于多个样本均数的多重比较,比较常用的是LSD-t,SNK,Dunnett,Tukey等,这些方法在之前的推文中介绍过。
医学和生信笔记
2022/11/15
1.2K0
R语言多个样本均数的多重比较
R语言因子分析
因子分析可以看做是主成分分析的进一步扩展,主成分分析重点在综合原始变量的信息,而因子分析重在解释原始变量之间的关系。
医学和生信笔记
2023/02/14
5580
R语言因子分析
临床预测模型概述6-统计模型实操-单/多因素Logistic回归
既往推文已经介绍过了logistic,cox,lasso回归(https://mp.weixin.qq.com/s/pXRZ1rYUr3lwH5OlDeB0_Q),接下来将重点进行代码的实操。
凑齐六个字吧
2024/08/07
2140
临床预测模型概述6-统计模型实操-单/多因素Logistic回归
R语言生存分析的实现
生存分析是临床常用统计方法,一旦和时间扯上关系,分析就变得复杂多了,此时不再是单一的因变量,还需要考虑时间给因变量和自变量带来的各种影响。
医学和生信笔记
2023/02/14
1.4K0
R语言生存分析的实现
R语言计算AUC(ROC曲线)的注意事项
并详细介绍了如何手动计算真阳性率/假阳性率,以及怎样计算多个,并把点连接成线,变成ROC曲线:ROC曲线纯手工绘制
医学和生信笔记
2023/02/14
1.6K0
R语言计算AUC(ROC曲线)的注意事项
R语言读CSV、txt文件方式以及read.table read.csv 和readr(大数据读取包)
首先准备测试数据*(mtcars) 分别为CSV.    TXT read.table 默认形式读取CSV(×)与TXT(效果理想) ① > test<-read.table("C:/Users/ad
学到老
2018/03/19
8.4K0
R语言读CSV、txt文件方式以及read.table read.csv 和readr(大数据读取包)
推荐阅读
相关推荐
R语言亚组分析1行代码实现!
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验