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

版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。

本文链接:https://www.cnblogs.com/lihuidashen/p/12937085.html

微信链接:https://mp.weixin.qq.com/s/MFOaa-Dw1iNMXuXPfXjLBA

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

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

硬件、编译器造成的

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

代码编写风格

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

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

void asd() {}
int main() {
    int i=0;
    i++;
    asd();  //方便区分上下文
    i=i+1;
    asd();
    i+=1;
    return 0;
}

反汇编:

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来测试一下

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 "<" 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 "<" 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 "<" ms"<;

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

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

csharp;gutter:true; string s1 = "hiya"; // 拷贝初始化 string s2("hello"); // 直接初始化 string s3(10, 'c'); // 直接初始化</p> <pre><code> 当我们使用拷贝初始化时,我们要求编译器将右侧运算对象拷贝到正在创建的对象中,如果需要的话还要进行类型转换,会浪费一定的资源时间,而直接初始化是要求编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数和拷贝构造函数。 我们来看看Primer中怎么说的 > 当用于类类型对象时,初始化的复制形式和直接形式有所不同:直接初始化直接调用与实参匹配的构造函数,复制初始化总是调用复制构造函数。复制初始化首先使用指定构造函数创建一个临时对象,然后用复制构造函数将那个临时对象复制到正在创建的对象" 还有一段说到: > 通常直接初始化和复制初始化仅在低级别优化上存在差异,然而,对于不支持复制的类型,或者使用非explicit构造函数的时候,它们有本质区别: ;gutter:true;
ifstream file1("filename")://ok:direct initialization
ifstream file2 = "filename";//error:copy constructor is private

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

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

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

代码冗余度

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

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

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

for (i=0;i<100;i++)
{
    for (j=0;j<5;j++)
    {
       for (j=0;j<5;j++)
        {
            /*处理代码*/
        }
    }
}

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

for (i=0;i<100;i++)
{
    for (j=0;j<5;j++)
    {
            /*处理代码*/
    }
}

改为:

for (j=0;j<5;j++)
{
    for (i=0;i<100;i++)
    {
            /*处理代码*/
    }
}

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

if (condition)
{
    for (i = 0;i < n;i++)
    {
        /*处理代码*/
    }
}
else
{
    for (i = 0;i < n;i++)
    {
        /*处理代码*/
    }
}

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

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 )就可以编译通过

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

csharp;gutter:true;
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也不需要重编译。

未完待续

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

推荐阅读

(点击标题可跳转阅读)

【编程之美】超时重传,滑动窗口,可靠性传输原理

【编程之美】论嵌入式架构的重要性

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

Original: https://www.cnblogs.com/lihuidashen/p/12937085.html
Author: 技术让梦想更伟大
Title: 如何分析和提高(C/C++)程序的编译速度?

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/536477/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • C++11 static_assert

    1。assert是动态断言,运行期检查,影响性能,故debug版本检查,release关闭。 2。C++11中引入了static_assert这个关键字,用来做编译期间的断言,因此…

    C++ 2023年5月29日
    048
  • Delphi XE8,C++ Builder XE8,RAD Studio XE8 官方 ISO 文件下载,附激活工具

    RAD Studio XE8 v22.0.19027.8951 官方ISO下载(6.72G):http://altd.embarcadero.com/download/radstu…

    C++ 2023年5月29日
    076
  • [C++]assert的加强版——Ensure的简易实现

    Ensure用法如: ENSURE(0 断言失败时,会打印: Failed: 0 概括来说,Ensure至少包括以下特性: 1) 不区分debug和release,始终生效。 2)…

    C++ 2023年5月29日
    041
  • C++函数模板template(模板函数)

    函数模板不是一个实在的函数,编译器不能为其生成可执行代码。定义函数模板后只是一个对函数功能框架的描述,当它具体执行时,将根据传递的实际参数决定其功能。 面向对象的继承和多态机制有效…

    C++ 2023年5月29日
    054
  • [C++] 浅拷贝和深拷贝

    浅拷贝只是简单的值拷贝; 深拷贝需要重新分配空间。 系统默认的拷贝构造函数属于浅拷贝。 输出结果为: HelloHelloWorldWorld 为什么修改对象 m 的值,对象 n …

    C++ 2023年5月29日
    064
  • C++ 相关库

    C++ 相关库 说明 RapidJSONhttps://github.com/Tencent/rapidjson Original: https://www.cnblogs.com…

    C++ 2023年5月29日
    062
  • C++11 静态断言 static_assert

    我们知道,C++现有的标准中就有assert、#error两个方法是用来检查错误的,除此而外还有一些第三方的静态断言实现。 assert是运行期断言,它用来发现运行期间的错误,不能…

    C++ 2023年5月29日
    046
  • C++内存管理

    [ 导语] 内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能,更大的自由,C++菜鸟的收获则是一遍一遍的检查代码和对C++的痛恨,但内…

    C++ 2023年5月29日
    048
  • error: Microsoft Visual C++ 14.0 is required问题最佳解决方法

    对于程序员来说,经常pip安装自己所需要的包,大部分的包基本都能安装,但是总会遇到包安装不了的问题,预研学习的动力第一步就被安装包给扼杀了。其中最受困扰的就是这个问题:error:…

    C++ 2023年5月29日
    053
  • 用C++实现半透明按钮控件(PNG,GDI+)

    使用MFC实现上面的按钮半透明效果能看到父窗口中的内容,上面是效果图(一个是带背景图片的、另一个是不带的)。 控件继承自CWnd类(彩色的部分是窗口的背景图片、按钮是PNG图片,第…

    C++ 2023年5月29日
    080
  • 2.设计模式-七大原则(C++)

    任何一个设计模式都离不开七大原则中的任一原则.所以七大原则非常重要,只要我们明白了七大原则,就可以知道如何在项目中使用什么设计原则了. 设计模式的七大原则如下所示: 1.单一职责原…

    C++ 2023年5月29日
    045
  • [C++] new和delete运算符使用方法

    new 和 delete 是C++语言中的两个运算符,配套使用。 new:用于分配内存,与C语言中的 malloc 相同,分配在堆内存 delete:用于释放内存,与C语言中的 f…

    C++ 2023年5月29日
    052
  • C++多线程库的常用模板类 std::lock_guard

    格式:类名 + 头文件 + 用例 + 解释说明 解释说明: C++标准库为互斥量提供了一个RAII语法的模板类 std::lock_guard,在构造时对互斥量上锁,并在析构的时进…

    C++ 2023年5月29日
    062
  • 老牌C/C++ IDE——DEV C++有新的大版本了

    序: 这个软件也是有相当历史了,可以追溯到win98时代,1998年就已经发布第一版本了,其中4.9.9.2版本是最经典的,网上镜像应该是最多的,这个版本才10M大小,后面版本都是…

    C++ 2023年5月29日
    055
  • [c++] 拷贝构造函数

    拷贝构造函数就是进行对象拷贝复制的函数。 拷贝构造函数也是一种构造函数。它用同类型的对象来初始化新创建的对象。其唯一的形参是const类型&,此函数也由系统自动调用。 拷贝…

    C++ 2023年5月29日
    049
  • VC++之自定义消息

    用户可以自定义消息,在应用程序中主动发出,这种消息一般用于应用程序的某一部分内部处理。 实例说明: 当用户按键盘上的光标上移键时,程序发送用户自定义消息,在对应的消息响应函数中弹出…

    C++ 2023年5月29日
    061
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球