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

本文链接:https://www.cnblogs.com/lihuidashen/p/12935470.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 "<<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倍时间,移位相对而言是最省时间的。

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

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 层,建议重新设计循环或将循环内的代码改写成一个子函数。

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

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

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/C++)程序的编译速度?的更多相关文章

  1. 提高Android和iOS调试编译速度

    http://www.cnblogs.com/findumars/p/7841252.html 提高Android和iOS调试编译速度   如果您使用Delphi开发App,就会遇到:Android和 ...

  2. 【转】实践最有效的提高Android Studio运行、编译速度方案

    原文:https://blog.csdn.net/xwh_1230/article/details/60961723 实践最有效的提高Android Studio运行.编译速度方案 最有效提升Andr ...

  3. 提高微信小程序的应用速度

    一.是什么 小程序启动会常常遇到如下图场景: 这是因为,小程序首次启动前,微信会在小程序启动前为小程序准备好通用的运行环境,如运行中的线程和一些基础库的初始化 然后才开始进入启动状态,展示一个固定的启 ...

  4. 提高Android Studio运行、编译速度方案

    1.安装完成后启动卡死 刚刚打开studio就卡在gradle building的界面再也不动了(去连接墙外的网下载),那么这个时候我们就需要把这个联网下载操作屏蔽掉,找到studio安装目录,找到i ...

  5. Chrome 开发者工具的Timeline和Profiles提高Web应用程序的性能

    Chrome 开发者工具的Timeline和Profiles提高Web应用程序的性能 二.减少 HTTP 的请求数    当用户浏览页面时,如果我们在用户第一次访问时将一些信息一次性加载到客户端缓存, ...

  6. Android:日常学习笔记(2)——分析第一个Android应用程序

    Android:日常学习笔记(2)——分析第一个Android应用程序 Android项目结构 整体目录结构分析 说明: 除了APP目录外,其他目录都是自动生成的.APP目录的下的内容才是我们的工作重 ...

  7. ASP.NET Core如何使用压缩中间件提高Web应用程序性能

    前言 压缩可以大大的降低我们Web服务器的响应速度,压缩从而提高我们网页的加载速度,以及节省一定的带宽. 何时使用相应压缩中间件 在IIS,Apache,Nginx中使用基于服务端的响应压缩技术.中间 ...

  8. 分析现有 WPF / Windows Forms 程序能否顺利迁移到 .NET Core 3.0

    本文转自 https://blog.csdn.net/WPwalter/article/details/82859449 使用 .NET Core 3.0 Desktop API Analyzer 分 ...

  9. 鸿蒙内核源码分析(ELF格式篇) | 应用程序入口并不是main | 百篇博客分析OpenHarmony源码 | v51.04

    百篇博客系列篇.本篇为: v51.xx 鸿蒙内核源码分析(ELF格式篇) | 应用程序入口并不是main | 51.c.h.o 加载运行相关篇为: v51.xx 鸿蒙内核源码分析(ELF格式篇) | ...

随机推荐

  1. 理解分布式一致性:Paxos协议之Basic Paxos

    理解分布式一致性:Paxos协议之Basic Paxos 角色 Proposal Number & Agreed Value Basic Paxos Basic Paxos without f ...

  2. 5.Python是怎么解释的?

    Python是怎么解释的? Python language is an interpreted language. Python program runs directly from the sour ...

  3. SQL语句学习(一)

    这篇文章用来记录再学习SQL语句的过程. 首先,我们从简单的创建表开始.创建表的语法是CREATE TABLE 表名(列1的名字  列1的属性,列2的名字  列2的属性...); 如果希望将某一列作为 ...

  4. 使nfs同步生效

    我们在使用nfs的时候会遇到往nfs server端写个文件,client需要很久才能生效,我们想让立刻生效有两种办法:1.mount -t nfs -o noac IP:/data/xxxx /xx ...

  5. ServerVariables集合

    当讨论Request对象内容时,要研究的集合之一就是ServerVariables集合.这个集合包含了两种值的结合体,一种是随同页面请求从客户端发送到服务器的HTTP报头中的值,另外一种是由服务器在接 ...

  6. Java 类类型之 String 类型

    类类型 引用数据类型存的都是地址,通过地址指向对象: 基本数据类型存的都是具体值: 字符串 (String) 类型 特点: 1.字符创都是对象: 2.一旦初始化,不能被更改,字符串缓冲区支持可变的字符 ...

  7. centos6.5宽带拨号上网

    CentOS6以后要安装rp-pppoe这个软件,centos之前的版本是adsl-setup命令安装. (1)查看是否安装 #rpm -qa|grep rp-pppoe 没有内容输出则没安装,若可以 ...

  8. USACO 2.1 海明码 Hamming Codes (模拟+位运算+黑科技__builtin_popcount(n))

    题目描述 给出 N,B 和 D,要求找出 N 个由0或1组成的编码(1 <= N <= 64),每个编码有 B 位(1 <= B <= 8),使得两两编码之间至少有 D 个单位 ...

  9. 9) drf JWT 认证 签发与校验token 多方式登陆 自定义认证规则反爬 admin密文显示

    一 .认证方法比较 1.认证规则图 django 前后端不分离 csrf认证 drf 前后端分离 禁用csrf 2. 认证规则演变图 数据库session认证:低效 缓存认证:高效 jwt认证:高效 ...

  10. IIS搭建网站(二)

    win+IIS+ASP+ACCESS第二种搭建方式 安装 控制面板”,依次选“添加/删除程序”, 添加/删除Windows组件 在应用程序服务器前打钩.点击详细信息 将“Internet信息服务(II ...