C和Java效率對比試驗和編譯器優化影響


首先得承認這不是一個好例子,邏輯過於簡單,受環境的干擾也特別大。不能作為評價一門語言綜合效率的用例,僅僅是基於個人興趣的小實驗的記錄。

C語言版本1

#include<stdio.h>
int main(){
	long a = 0;
	for(long i=0; i<100000000; i++){
		a += i;
	}
	printf("%ld\n", a);
	return0;
}

Java版本1

public class T{
	publicstaticvoid main(String args[]){
		long a =0;
		for(long i=0; i<100000000;++i){
			a += i;
		}
		System.out.println(a);
	}
}

如以上代碼所示,計算0到100000000的累加值,測試過程及結果如下

gcc t.c
time ./a.out
4999999950000000
real	0m0.237s
user	0m0.234s
sys	0m0.002s

javac T.java
time java T
4999999950000000
real	0m0.123s
user	0m0.108s
sys	0m0.020s

神奇的結果,以效率著稱的C輸給了Java,Java版本的用時大概是C版本的1/2;
不過以上的結果是在gcc未開啟編譯優化的情況下得出的,讓我們看看開啟優化后的情況;開啟O2優化后的測試結果

gcc -O2 t.c
time ./a.out
4999999950000000
real	0m0.003s
user	0m0.001s
sys	0m0.002s

效果不可思議的好,都不能用提速多少倍來評價了,像作弊了一樣;我們有必要看一看優化都做了什么,首先查看一下不開啟優化時的匯編代碼;

LBB0_1:							## =>This Inner Loop Header: Depth=1
cmpq	$100000000, -24(%rbp)  	## imm = 0x5F5E100
jge	LBB0_4
## BB#2:                     	##   in Loop: Header=BB0_1 Depth=1
movq	-24(%rbp), %rax
movq	-16(%rbp), %rcx
addq	%rax, %rcx
movq	%rcx, -16(%rbp)
## BB#3:						##   in Loop: Header=BB0_1 Depth=1
movq	-24(%rbp), %rax
addq	$1, %rax
movq	%rax, -24(%rbp)
jmp	LBB0_1

代碼很簡單,循環的匯編版本,棧變量 -24(%rbp)與常量$100000000進行比較,大於等於時則跳出循環,否則進入循環進行累加,累加值放在-16(%rbp)另一個棧變量里;
然后是開啟優化之后的匯編代碼,只看關鍵部分
movabsq $4999999950000000, %rsi ## imm = 0x11C37934E58F80
編譯器直接把循環優化掉,算出了最終結果;這實在是很神奇,編譯器優化是我的知識盲區;不過我懷疑,我們平時的業務代碼中,可能能進行這么徹底的優化的用例不是那么多;常量循環累加可能太簡單了,我們稍微把邏輯增加一些,讓循環次數作為一個變量從外部獲得;
C語言版本2

#include<stdio.h>
int main(int args,char* argv[]){
	long a =0;
	long x =0;
	sscanf(argv[1],"%ld",&x);
	for(long i=0; i<x; i++){
		a += i;
	}
	printf("%ld\n", a);
	return0;
}

查看不優化時的匯編代碼如下,

LBB0_1:                                 ## =>This Inner Loop Header: Depth=1
movq	-40(%rbp), %rax
cmpq	-32(%rbp), %rax
jge	LBB0_4
## BB#2:                                ##   in Loop: Header=BB0_1 Depth=1
movq	-40(%rbp), %rax
movq	-24(%rbp), %rcx
addq	%rax, %rcx
movq	%rcx, -24(%rbp)
## BB#3:                                ##   in Loop: Header=BB0_1 Depth=1
movq	-40(%rbp), %rax
addq	$1, %rax
movq	%rax, -40(%rbp)
jmp	LBB0_1

和常量循環的代碼差不多,比較變量,進入循環累加或者跳出,設定同樣的循環次數,和常量循環版本運行結果幾乎一樣;然后查看優化之后的匯編代碼如下;

## BB#1:                                ## %.lr.ph
movl	$1, %ecx
cmovgq	%rax, %rcx
leaq	-1(%rcx), %rax
leaq	-2(%rcx), %rdx
mulq	%rdx
shldq	$63, %rax, %rdx
leaq	-1(%rcx,%rdx), %rbx

這段代碼以我微薄的匯編水平理解起來非常的吃力,cmov代表“如果src大於dst則進行移動”,lea是使用內存尋址計算指令進行計算,shld是邏輯左移;用高級語言來表示上面這段代碼的意義為 (x-1)(x-2)/2 + x - 1,簡化一下的話是x(x-1)/2,計算之后的結果確實是從0累加到x-1的值;也是算法級別的優化,直接把循環變為同等意義的多項式;

不過我還是覺得,一般的業務邏輯代碼可能得不到這種級別的優化,有機會要在復雜度上做更多的實驗,來驗證這個問題;

需要提出的問題1,是Java有沒有類似的優化參數和過程?2,假設Java沒有優化,為什么會比同樣沒有優化的C代碼快?

問題1需要等Java高手來解答,對於問題2,我聽說過一個聽起來很有道理的解釋是這樣的,C的編譯器要保證編譯出的指令在大部分CPU上正常運行,所以只能使用老的通用指令,而CPU不斷發展,產生了很多新的速度更快的指令,jvm是有能力根據不同的平台選擇最優指令的,所以同樣的指令邏輯,Java非常有可能會更快一些;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM