如何分析和提高(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++智能指针

    前言 在现代 c + + 编程中,标准库包含 智能指针,这些指针用于帮助确保程序不会出现内存和资源泄漏,并具有异常安全。 标准库智能指针分类 auto_ptr, shared_pt…

    C++ 2023年5月29日
    053
  • c++调用tflite实战

    一,概述 深度学习模型在移动端的应用越来越多,tensorflow lite就是专门为tensorflow模型在移动端上线推断设计的框架。tensorflow 官方提供了不少cv的…

    C++ 2023年5月29日
    045
  • c和c++开发工具之clion和vs

    个人体验结果 如果是CMake或者要跨平台的话,建议使用CLion 像我在看书写练习题的话,Clion使用cmake编译c/c++源码更简单上手使用。 如果项目不大,两者都可以。如…

    C++ 2023年5月29日
    0110
  • 【C++服务端技术】消息队列

    ThreadWorkUnit.h #pragma once #include #include #include "SafeQueue.h" namespace…

    C++ 2023年5月29日
    044
  • c++以代理的方式来实现接口化编程

    假如你项目代码本身已经实现了很多的实体类,但并未采用接口,可以考虑以这种方式来实现接口化编程 struct ITest { virtual void Test()=0; }; cl…

    C++ 2023年5月29日
    062
  • C++11 右值引用和转移语义

    新特性的目的 右值引用 (Rvalue Referene) 是 C++ 新标准 (C++11, 11 代表 2011 年 ) 中引入的新特性 , 它实现了转移语义 (Move Se…

    C++ 2023年5月29日
    072
  • [图形图像]C++实现的软件光栅器

    第1张: 从坐到右,从上到下。无纹理、无镜面光、无深度缓冲,依次是: 线框。 纯色。 即每个三角形一个颜色。 flat着色(相比上次,修正后的效果好多了)。也是每个三角形一个颜色,…

    C++ 2023年5月29日
    059
  • LLVM Constant Value to c++ value

    auto llval = b.getInt32(-1); std::cout << llval->getZExtValue() << "\n…

    C++ 2023年5月29日
    068
  • C++菜鸟经验:如何有效地避免各种不期而遇的Bug

    本文展示了笔者在编写 C++程序中遇到的问题和解决方案。文中附有大量有用的代码,这些代码往往都可以不加修改的添加进你自己的函数包中。你可能不能在其他的书上找到这些写法,因为这些都是…

    C++ 2023年5月29日
    038
  • C++多线程库的常用函数 std::lock()

    格式:函数名 + 头文件 + 用例 + 解释说明 1 函数名: 2 std::lock() 3 4 头文件: 5 #include 6 7 用例: 8 std::mutex ma,…

    C++ 2023年5月29日
    054
  • c++ 数字类型转string

    include using namespace std; std::to_string(x) 几乎所有的数字类型都可以转 桂棹兮兰桨,击空明兮溯流光。 Original: http…

    C++ 2023年5月29日
    034
  • C++类中静态数据成员MAP如何初始化

    cpp;gutter:true; conv_xxx.hpp</p> <p>class convolution { …</p> <pre…

    C++ 2023年5月29日
    051
  • c++ effective总结(一)

    条款一:视c++为一个语言联邦 c++可以认为是由C,Object-Oriented C++(面向对象),Template C++(模板),STL(c++标准模板库)四种次语言组成…

    C++ 2023年5月29日
    075
  • 【C/C++】输出文件的相对路径

    FILE 在有些编译器上会输出带路径的文件名,删除路径可使用函数 strrchr #include #include #define __FILENAME__ (strrchr(_…

    C++ 2023年5月29日
    056
  • C++11 并发指南六(atomic 类型详解三 std::atomic (续))

    总地来说,C++11 标准库中的 std::atomic 针对整形(integral)和指针类型的特化版本新增了一些算术运算和逻辑运算操作。具体如下: 下面我们来简单介绍以上的 s…

    C++ 2023年5月29日
    035
  • 腾讯研发类笔试面试试题(C++方向)(转)

    原文转自 https://www.cnblogs.com/freebird92/p/9595244.html 1、C和C++的特点与区别? 答: (1)C语言特点:1.作为一种面向…

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