首先得承認這不是一個好例子,邏輯過於簡單,受環境的干擾也特別大。不能作為評價一門語言綜合效率的用例,僅僅是基於個人興趣的小實驗的記錄。
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非常有可能會更快一些;