C++/C语言速度优化

目录

前言

1、把后置加加/减减替换为前置加加/减减

原理解析

测试结果

2、将反复使用的数据储存为全局变量

 解释

测试

3、使用多线程

解释

测试

4、减少除法运算

解释

测试

5、减少拷贝(值传递),使用指针、引用、移动资源传递

解释

测试


前言

在编写代码的时候,一个程序的运行速度是十分重要的,如果速度过慢就会变得非常鸡肋。所以就需要时间优化.

1、把后置加加/减减替换为前置加加/减减

原理解析

首先,我们应该了解两个自增/自减符号的运算原理:

i++:

#include <stdio.h>
int main(){
	int a = 114514 ;
	int b = 0 ;
	b = a ++ ;
	printf("%d %d",a,b) ;//114515,114514
	return 0 ;
}

如果运行上面的代码,你就会发现,a的值比b要多1,那么也可以说b获取的是a的旧值,在这之后,a更新了新值(++操作),那么上面的代码实际上可以表达为如下:

#include <stdio.h>
int main(){
	int a = 114514 ;
	int b = 0 ;
	int t = a ;
	++ a ;
	b = t ;
	printf("%d %d",a,b) ;//114515,114514
	return 0 ;
}

当运行代码时,你就会发现这两个程序的结果是一样的,那么我们就可知,前置加加的原理就是先返回旧值,再进行加加操作,下面的代码更加可以印证这一结论:

#include <stdio.h>
int main(){
	int a = 0 ;
	int b = 0 ;
	b += (a ++) + (a ++) ;
	/*相当于
	int t = a ; ++ a ;
	int t2 = a ; ++ a ;
	b += t + t2 ;
	*/
	printf("%d %d",a,b) ;//2 1
	return 0 ;
}

测试结果

所以我们可以得出,前置加加要比后置加加多创建、返回一个临时变量,哪怕它很少、很小,但是哪怕如此,积少成多也可以严重拖慢程序运行速度(如果这个变量的类型很大的话,那速度更是会被严重降低),例如下面两个程序:

#include <stdio.h>
const int N = 100000 ;
class abc{
	public :               //实现一个长度不可变的环 
		long long data[N] ;//保存长度为N的longlong的元素环 
		int now ;          //当前环元素的下标 
		abc(void){ now = 0 ; }
		abc(abc& b){//拷贝构造函数
					//每次创建一个新的对象 
		            //都要耗费N个longlong(data)和一个int(now)的内存 
			for(int i = 0;i < N;++ i)
				data[i] = b.data[i] ;  //遍历复制 
			now = b.now ;              //获取下标 
		}
		bool is_begin(){ return !now ; }       				// 判断是否到了环的首部 
		bool is_end(){ return (now + 1) >= N ;}				//判断是否到了环的尾部 
		abc& operator++(){ (++ now) %= N ; return *this ; }	//使用下一个环元素 
		abc operator++(int){ abc b(*this) ; (++ now) %= N ; return b ;}
															//用临时对象保存下原来的值返回,
															//并且使用下一个环元素 
		long long& operator*(){ return data[now]; }			//返回环元素的引用 
} ;
int main(){
	abc a ;//构造一个abc类的对象a 
	while(!a.is_end())
		*a = 100 , a ++ ;//改动在这!!!!!!!!!!!!!!!!!! 
	return 0 ;
}
#include <stdio.h>
const int N = 100000 ;
class abc{
	public :               //实现一个长度不可变的环 
		long long data[N] ;//保存长度为N的longlong的元素环 
		int now ;          //当前环元素的下标 
		abc(void){ now = 0 ; }
		abc(abc& b){//拷贝构造函数
					//每次创建一个新的对象 
		            //都要耗费N个longlong(data)和一个int(now)的内存 
			for(int i = 0;i < N;++ i)
				data[i] = b.data[i] ;  //遍历复制 
			now = b.now ;              //获取下标 
		}
		bool is_begin(){ return !now ; }       				// 判断是否到了环的首部 
		bool is_end(){ return (now + 1) >= N ;}				//判断是否到了环的尾部 
		abc& operator++(){ (++ now) %= N ; return *this ; }	//使用下一个环元素 
		abc operator++(int){ abc b(*this) ; (++ now) %= N ; return b ;}
															//用临时对象保存下原来的值返回,
															//并且使用下一个环元素 
		long long& operator*(){ return data[now]; }			//返回环元素的引用 
} ;
int main(){
	abc a ;//构造一个abc类的对象a 
	while(!a.is_end())
		*a = 100 , ++ a ;//改动在这!!!!!!!!!!!!!!!!!! 
	return 0 ;
}

乍一看,是不是感觉它们两个代码一模一样?可当我们看到main主函数while循环中,就可以发现微小的区别,在遍历a时,一个使用了a++,一个使用了++a,再让我们看看他们的速度比较(顺序相同):

天哪,仅仅是半行代码之差,运行的效率就天差地别!!!运行的时间竟然相差了百倍不止,几乎千倍!!!所以,注重代码的细节是很重要的!!!!!

2、将反复使用的数据储存为全局变量

 解释

需要一直使用的资源、图片、数据等,可以一直保存在内存中,不要重复加载,这是一个十分浪费时间的操作,要尽量避免。

测试

例如以下的程序,明明使用的永远都是pow10(8),一直都是同样的数据,那么就可以用变量保存起来,而不需要每次都进行计算。

#include <stdio.h>
int pow10(int n){
	int t = 1 ;
	while(n) -- n , t *= 10 ;
	return t ;
}
int a ;
int main(){
	//int t = pow10(8) ;
	while(a != -1){
		scanf("%d",&a) ;
		printf("它的千万位:%d\n",a / pow10(8) % 10) ;
		//printf("它的千万位:%d\n",a / t % 10) ;
	}
	return 0 ;
}

3、使用多线程

解释

如果程序中有可以同步进行的代码,那么使用多线程就可以同步计算他们,运行效率大大提升。

测试

注:本代码在windows10系统devc++上运行有效

#include <stdio.h>
#include <math.h>
#include <windows.h>
#include <time.h>
typedef struct Input{ long long from,to ; } Input ;

bool is_prime(long long num) ;
DWORD WINAPI ThreadProc(LPVOID) ;
long long func1() ;
long long func2() ;

int main(){
	long long s = 0 ;
	clock_t start = clock() ;
	s = func1() ;
	printf("不变化:%lld : %llums\n",s,clock() - start) ;
	start = clock() ;
	s = func2() ;
	printf("开线程:%lld : %llums\n",s,clock() - start) ;
	return 0 ;
}

long long func1(){
	long long s = 0 ;
	for(long long i = 1;i < 1000000;++ i)
		if(is_prime(i))++ s ;
	return s ;
}

long long func2(){
	Input a[5] = {{1,200000},
				 {200001,400000},
				 {400001,600000},
				 {600001,800000},
				 {800001,1000000}} ;
	HANDLE h[5] ;
	unsigned long ID[5] ;
	for(int i = 0;i < 5;++ i)
		h[i] = CreateThread(NULL,1,ThreadProc,a + i,1,ID + i) ;
	WaitForMultipleObjects(5,h,TRUE,INFINITE) ;
	long long ts = 0,s = 0 ;
	for(int i = 0;i < 5;++ i){
		GetExitCodeThread(h[i],(LPDWORD)(&ts)) ;
		s += ts ;
	}
	return s ;
}

DWORD WINAPI ThreadProc(LPVOID lpParameter) {
    Input* a = (Input*)lpParameter ;
    ++ (a -> to) ;
    long long s = 0 ;
    while((a -> from) < (a -> to)){
    	if(is_prime(a -> from)) ++ s ;
    	++ (a -> from) ;
	}
	ExitThread(s) ;
}

bool is_prime(long long num){
	if(num < 2)return false ;
	if(num < 4)return true ;
	if((num % 6) != 1 && (num % 6) != 5)return false ;
	const int end = sqrt(num) + 1 ;
	for(int i = 2;i < end;++ i)
		if(!(num % i))return false ;
	return true ;
}

4、减少除法运算

解释

无论是正数负数、整数小数,除法运算都需要大量的运算量,所以尽量优化为加、减、乘的运算可以节省时间。例如:a/2>b慢于a>b*2慢于a>b+b(当然,容易溢出的情况除外)

测试

#include <stdio.h>
int main(){
	printf("请输入双方战力:") ;
	int a,b ;
	scanf("%d%d",&a,&b) ;
	//if((a / 2) > b)
	//else if((b * 2) < a)
	else if((b + b) < a)
		printf("第一方战力碾压!") ;
	//else if((b / 2) > a)
	//else if((a * 2) < b)
	else if((a + a) < b)
		printf("第二方战力碾压!") ;
	else 
		printf("双方斗得难舍难分!") ;
	return 0 ;
}

5、减少拷贝(值传递),使用指针、引用、移动资源传递

解释

每一次拷贝都会产生一个新的临时变量,不但浪费内存,而且浪费时间。(1)如果一块内存、一个变量要长期使用,那么就可以使用指针、引用减少拷贝,加快运行速度。(2)当一块内存即将释放时,如果还需要使用,则把它的资源给其他的变量进行移动赋值、构造,避免了内存的多次申请、释放,减少内存碎片,也加快运行速度(多用于占用内存较大的对象,对应移动拷贝、移动赋值函数,使用标准库函数move等可以实现)

两种情况,打个比方就是这样:

(1)图书馆里有很多的书,其中有一本就是小明喜欢看的(需要获取变量的值)。此时,小明想要看这一本书,他可以那一个空本子(新创建的临时变量),靠着自己的天才画工,照着这本书一模一样的临摹一本出来(拷贝传递,值传递,复制保存的值),可这太浪费时间了。于是,小明就可以用小纸条(指针,引用)记下图书在图书馆中的位置(变量的地址),每次想看这本书时,只要根据记录下的位置找到这本书就能看了(寻址访问),就可以大大节省时间(由于无需拷贝复制)。(假设小明走路去图书馆所花的时间十分短,可以忽略不计)

(2)小壮有一本书,小明很喜欢(需要获取变量的值),并且小壮不喜欢,要把它扔掉(当前变量即将被释放)作为天才画家的小明,当然可以照着那本书临摹一本出来(拷贝复制),可实在不方便。那么,小壮就决定把书送给小明(资源传递),这样小明就不用大费周章了,也节约了纸张(节省时间、内存)

测试

#include <stdio.h>
#include <stdlib.h>

template<typename _Tp>
struct remove_reference
{ typedef _Tp   type ; } ;
template<typename _Tp>
struct remove_reference<_Tp&>
{ typedef _Tp   type ; } ;
template<typename _Tp>
struct remove_reference<_Tp&&>
{ typedef _Tp   type ; } ;
template <typename T>
typename remove_reference<T> :: type&& move(T&& t) noexcept{
    return static_cast<typename remove_reference<T> :: type&&>(t) ;
}

int a,b,c ;
class abc{
	public :
		abc(){
			data = new int[100000] ;
			for(int i = 0;i < 100000;++ i)
				data[i] = rand() % 100 ;
			++ a ;
		}
		abc(abc& t){
			data = new int[100000] ;
			for(int i = 0;i < 100000;++ i)
				data[i] = t.data[i] ;
			++ b ;
		}
		abc(abc&& t){
			data = t.data ;
			t.data = 0 ;
		}
		~abc() {
			++ c ;
			if(data)delete data ; 
		}
		int* data ;
} ;

abc get(){
	static abc t ;
	return t ;
}

abc* get2(){
	static abc t ;
	return &t ;
}

abc& get3(){
	static abc t ;
	return t ;
}

abc get4(){
	abc t ;
	return move(t) ;
}

int main(){
	a = b = c = 0 ;
	abc t1 = get() ;
	printf("普通构造%d次,拷贝构造%d次,析构函数%d次\n",a,b,c) ;
	a = b = c = 0 ;
	abc* t2 = get2() ;
	printf("普通构造%d次,拷贝构造%d次,析构函数%d次\n",a,b,c) ;
	a = b = c = 0 ;
	const abc& t3 = get3() ;
	printf("普通构造%d次,拷贝构造%d次,析构函数%d次\n",a,b,c) ;
	a = b = c = 0 ;
	abc t4 = get4() ;
	printf("普通构造%d次,拷贝构造%d次,析构函数%d次\n",a,b,c) ;
	return 0 ;
}

可以看到只有值传递的方式会多使用一次拷贝构造函数(第四种移动构造方式的一次析构是因为其他的几个函数中的t都是静态常量,会保持不释放,属于全局变量,而第四种方法适用于资源转移,所以原先的对象会进行一次析构,但不会释放data内存块)

  • 62
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值