如何减少C++编写程序的CPU使用率

如何减少C++编写程序的CPU使用率,第1张

优化是一个非常大的主题,本文并不是去深入探讨性能分析理论,算法的效率,况且我也没有这个能力。我只是想把一些可以简单的应用到你的C++代码中的优化技术总结在这里,这样,当你遇到几种不同的编程策略的时候,就可以对每种策略的性能进行一个大概的估计。这也是本文的目的之所在。

一. 优化之橘轮桐前

在进行优化之前,我们首先应该做的是发现我们代码的瓶颈(bottleneck)在哪里。然而当你做这件事情的时候切忌从一个debug- version进行推断,因为debug-version中包含了许多额外的代码。一个debug-version可执行体要比release- version大出40%。那些额外的代码都是用来支持调试的,比如说符号的查找。大多数实现都为debug-version和release- version提供了不同的operator new以及库函数。而且,一个release-version的执行体可能已经通过多种途径进行了优化,包括不必要的临时对象的消除,循环展开,把对象移入寄存器,内联等等。

另外,我们要把调试和优化区分开来,它们是在完成不同的任务。 debug-version 是用来追捕bugs以及检查程序是否有逻辑上的问题。release-version则是用来做一些性能上的调整以及进行优化。

下面就让我们来看看有哪些代码优化技术吧:

二. 声明的放置

程序中变量和对象的声明放在什么位置将会对性能产生显著影响。同样,对postfix和prefix运算符的选择也会影响性能。这一部分我们集中讨论四个问题:初始化v.s 赋值,在程序确实要使用的地方放置声明,构造函数的初始化列表,prefix v.s postfix运算符。

(1)请使用初始化而不是赋值

在C语圆坦言中只允许在一个函数体的开头进行变量的声明,然而在C++中声明可以出现在程序的任何位置。这样做的目的是希望把对象的声明拖延到确实要使用它的时候再进行。这样做可以有两个好处:1. 确保了对象在它被使用前不会被程序的其他部分恶意修改。如果对象在开头就被声明然而却在20行以后才被使用的话,就不能做这样的保证。2. 使我们有机会通过用初始化取代赋值来达到性能的提升,从前声明只能放在开头,然而往往开始的时候我们还没有获得我们想要的值,因此初始化所带来的好处就无法被应用。但是现在我们可以在我们获得了想要的值的时候直接进行初始化,从而省去了一步。注意,或许对于基本类型来说,初始化和赋值之间可能不会有什么差异,但是对于用户定义的类型来说,二者就会带来显著的不同,因为赋值会多进行一次函数调用----operator =。因此当我们在赋值和初始化之间进行选择的话,初始化应该是我们的首选。

(2)把声明放在合适的位置上

在一些场合,通过移动声明到合适的位置所带来的性能提升应该引起我们足够的重视。例如:

bool is_C_Needed()

void use()

{

C c1

if (is_C_Needed() == false)

{

return//c1 was not needed

}

//use c1 here

return

}

上面这段代码中对象c1即使在有可能不使用它的情况下也会被创建,这样我们就会为它付出不必要的花费,有可能你会说一个对象c1能浪费多少时间,但是如果是这种情况呢:C c1[1000]我想就不是说浪费就浪费了。但是我们可以通过移动声明c1的位置来改变这种情况:

void use()

{

if (is_C_Needed() == false)

{

return//c1 was not needed

}

C c1//moved from the block's beginning

//use c1 here

return

}

怎么样,程序的性能是不是已经得到很大的改善了呢?因此请仔细分析你的代码,把声明放在合适的位置上,它桐旅所带来的好处是你难以想象的。

(3) 初始化列表

我们都知道,初始化列表一般是用来初始化const或者reference数据成员。但是由于他自身的性质,我们可以通过使用初始化列表来实现性能的提升。我们先来看一段程序:

class Person

{

private:

C c_1

C c_2

public:

Person(const C&c1, const C&c2 ): c_1(c1), c_2(c2) {}

}

当然构造函数我们也可以这样写:

Person::Person(const C&c1, const C&c2)

{

c_1 = c1

c_2 = c2

}

那么究竟二者会带来什么样的性能差异呢,要想搞清楚这个问题,我们首先要搞清楚二者是如何执行的,先来看初始化列表:数据成员的声明 *** 作都是在构造函数执行之前就完成了,在构造函数中往往完成的只是赋值 *** 作,然而初始化列表直接是在数据成员声明的时候就进行了初始化,因此它只执行了一次copy constructor。再来看在构造函数中赋值的情况:首先,在构造函数执行前会通过default constructor创建数据成员,然后在构造函数中通过operator =进行赋值。因此它就比初始化列表多进行了一次函数调用。性能差异就出来了。但是请注意,如果你的数据成员都是基本类型的话,那么为了程序的可读性就不要使用初始化列表了,因为编译器对两者产生的汇编代码是相同的。

(4)postfix VS prefix 运算符

prefix运算符++和—比它的postfix版本效率更高,因为当postfix运算符被使用的时候,会需要一个临时对象来保存改变以前的值。对于基本类型,编译器会消除这一份额外的拷贝,但是对于用户定义类型,这似乎是不可能的。因此请你尽可能使用prefix运算符

三. 内联函数

内联函数既能够去除函数调用所带来的效率负担又能够保留一般函数的优点。然而,内联函数并不是万能药,在一些情况下,它甚至能够降低程序的性能。因此在使用的时候应该慎重。

1.我们先来看看内联函数给我们带来的好处:从一个用户的角度来看,内联函数看起来和普通函数一样,它可以有参数和返回值,也可以有自己的作用域,然而它却不会引入一般函数调用所带来的负担。另外,它可以比宏更安全更容易调试。

当然有一点应该意识到,inline specifier仅仅是对编译器的建议,编译器有权利忽略这个建议。那么编译器是如何决定函数内联与否呢?一般情况下关键性因素包括函数体的大小,是否有局部对象被声明,函数的复杂性等等。

2.那么如果一个函数被声明为inline但是却没有被内联将会发生什么呢?理论上,当编译器拒绝内联一个函数的时候,那个函数会像普通函数一样被对待,但是还会出现一些其他的问题。例如下面这段代码:

// filename Time.h

#include

#include

using namespace std

class Time

{

public:

inline void Show() { for (int i = 0i<10i++) cout<}

因为成员函数Time::Show()包括一个局部变量和一个for循环,所以编译器一般拒绝inline,并且把它当作一个普通的成员函数。但是这个包含类声明的头文件会被单独的#include进各个独立的编译单元中:

// filename f1.cpp

#include "Time.hj"

void f1()

{

Time t1

t1.Show()

}

// filename f2.cpp

#include "Time.h"

void f2()

{

Time t2

t2.Show()

}

结果编译器为这个程序生成了两个相同成员函数的拷贝:

void f1()

void f2()

int main()

{

f1()

f2()

return 0

}

当程序被链接的时候,linker将会面对两个相同的Time::Show()拷贝,于是函数重定义的连接错误发生。但是老一些的C++实现对付这种情况的办法是通过把一个un-inlined函数当作static来处理。因此每一份函数拷贝仅仅在自己的编译单元中可见,这样链接错误就解决了,但是在程序中却会留下多份函数拷贝。在这种情况下,程序的性能不但没有提升,反而增加了编译和链接时间以及最终可执行体的大小。

但是幸运的是,新的C++标准中关于un-inlined函数的说法已经改变。一个符合标准C++实现应该只生成一份函数拷贝。然而,要想所有的编译器都支持这一点可能还需要很长时间。

另外关于内联函数还有两个更令人头疼的问题。第一个问题是该如何进行维护。一个函数开始的时候可能以内联的形式出现,但是随着系统的扩展,函数体可能要求添加额外的功能,结果内联函数就变得不太可能,因此需要把inline specifier去除以及把函数体放到一个单独的源文件中。另一个问题是当内联函数被应用在代码库的时候产生。当内联函数改变的时候,用户必须重新编译他们的代码以反映这种改变。然而对于一个非内联函数,用户仅仅需要重新链接就可以了。

这里想要说的是,内联函数并不是一个增强性能的灵丹妙药。只有当函数非常短小的时候它才能得到我们想要的效果,但是如果函数并不是很短而且在很多地方都被调用的话,那么将会使得可执行体的体积增大。最令人烦恼的还是当编译器拒绝内联的时候。在老的实现中,结果很不尽人意,虽然在新的实现中有很大的改善,但是仍然还是不那么完善的。一些编译器能够足够的聪明来指出哪些函数可以内联哪些不能,但是,大多数编译器就不那么聪明了,因此这就需要我们的经验来判断。如果内联函数不能增强行能,就避免使用它.

一、在BIOS中优化CPU

1、打开CPU二级缓存

对于一些特殊主板,CPU的某些功能在默认的情况下是不开启的,需要在BIOS中将其打开,如CPU的内部高速缓存和超线程技术等。打开CPU的内部高速缓存的方法如下:

步骤1

进入BIOS设置主界面,选择“Advanced

BIOS

Features”设置项,按Enter键进入。

步骤2

将“CPU

Internal

Cache”设置为“Enabled”,即打开CPU的二级缓存。打开后可以减少CPU在存储器读/写周期中的等待时间,从而提升CPU的工作效率。

步骤3

CPU二级缓存ECC校验也是一个很重要的参数。将“CPU

L2

Cache

ECC

Checking”设置为“Enabled”,可启用CPU内部L2Cache,进行ECC检测。它可以侦察并纠正单位信号错误,保持资料的准确性,对超频的稳定性有帮助,但不能侦察双位信号错误。

2、设置超线程技术

超线程技术回增强处理器的性能,提高工作效率。因此,对于一些支持超线程技术的CPU(如P42.8C等),可以对其进行如下设置:

步骤1

进入主板BIOS中,在“Advanced

BIOS

Features”中找到“

Hyper-Threading

Technology”选项。

步骤2

将其设置为“Enabled”。

 穗数凳 步骤3

设置完毕后重启电脑,然后在开机自检画面时会显示两个处理器。当用户进入系统后还可以在系统的“设备管理器”里面看到两个处理器,这就代表超毕消线程技术已经成功打开。

二、系统设置优化CPU

1、调整程序优先级

在Windows系统中,对CPU的优化处理并不多,可以通过设置CPU优先等级的方法来优化CPU。Windows

2000/XP内的应用程序共分为32个等级(0~31),一般都处于0~15级之间。默认情况下,程序的优先级别都为8,即“标准”。在有些时候,可以将一些无关紧要的进程的优先级调低,这样可以提升CPU的性能。

要设置CPU的优先级,可以执行以下 *** 作:

步骤1

按Ctrl+Alt+Del组合键,打开“Windows任务管理器”窗口。

步骤2

选中要设置优先级的程序,单击鼠标右键,在d出的快捷菜单中选择“设置优先级”→“低”的命令,即可降低程序的级别。

4

2、优化处理器二级缓存容量

在Windows

XP中,为加快系统运行,可以在注册表中设置二级缓存容量。方法如下:

步骤1

首先打开注册表,找到如下分猜旅支:“HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\SessionManager\\MemoryManagement\\”。

步骤2

选择其下的“SecondLevelDataCache”,用户根据自己所用的处理器设置即可,例如Athlon

XP是“38”,P4Northwood是“512”。

单线程程序的都是这样的,只用一个核,所以占用50%。

可以用绝弯漏多线程技术解决CPU占用率过高的问题。

以下为本人原创:

#include <stdio.h>

#include <windows.h>

#include <process.h>

int end = 0

void calc()

{

int i=1

while(i++)

end = 1

}//长时间计算子程序

void MyThread(void*)

{

calc()

_endthread()//并烂结束线程

}//线程主体

int main()

{

int n

HANDLE hThread = (HANDLE)_beginthread(MyThread, 0, NULL)//创建另一线程

for(n=0!end++n)

{

//idle time = 80%, busy time = 20%

if(n==10) n=0

if(n==0) SuspendThread(hThread)//闹姿挂起线程

if(n==8) ResumeThread(hThread)//恢复线程

Sleep(100)

}

printf("OK!\n")

return 0

}//主线程


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/yw/12312566.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-24
下一篇 2023-05-24

发表评论

登录后才能评论

评论列表(0条)

保存