前兩天我看到了一篇文章,測試Java和Go和Python的效能,其測試內容是一個排序,排序一億次,然後看那個語言耗時最短,我先貼一下這個文章的測試結果,Java竟然比Go快了一倍不止,Go不是號稱接近C的效能嗎,難道?結尾我會把我看的這篇文章連結共享出來,接下來聽我分析,
準備測試程式碼
Java測試程式碼
可以看的出來邏輯很簡單,對一個固定陣列排序,for迴圈執行一億次,記錄總的耗時時間,程式碼和我看過的文章程式碼一致。
public static void main(String[] args) {
//記錄開始時間
long start = System.nanoTime();
int num = 100000000;
for (int i = 0; i < num; i++) {
BubbleSort(1, 2, 3, 4, 5, 6, 7, 8, 9);
}
//列印耗時時間
System.out.println(System.nanoTime() - start);
}
//排序
public static void BubbleSort(int... arr) {
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] < arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
Go測試程式碼
和Java的功能是一樣的,也是一億次排序,程式碼和我看過的文章程式碼一致。
func Sort() {
start := time.Now().UnixNano()
var arr []int
const NUM int = 100000000
for i := 0; i < NUM; i++ {
arr = []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
bubbleSort(arr)
}
//列印消耗時間
fmt.Println(time.Now().UnixNano() - start)
}
//排序
func bubbleSort(arr []int) {
for j := 0; j < len(arr)-1; j++ {
for k := 0; k < len(arr)-1-j; k++ {
if arr[k] < arr[k+1] {
temp := arr[k]
arr[k] = arr[k+1]
arr[k+1] = temp
}
}
}
}
我們分別執行上面這兩段程式碼,看看結果到底多少呢,我的本地環境如下:
Java : jdk1.8 GoLang :1.12
i7處理器,16G記憶體,Windows10系統
1.Java結果:3263111300 ns = 3263 ms = 3.2 s
2.Go結果: 7165483700 ns = 7165 ms = 7.1 s
看到這個結果你信了嗎? Java比Go的效能要好,快了一倍不止,我以前看到的文章難道都欺騙了我嗎?
解密開始
仔細觀察兩段程式碼,其實是有一些細微區別的,有時候一點點的細微區別導致的結果千差萬別,甚至讓你得出一個錯誤結論從而誤導你,看下面Go的程式碼,這個程式碼片段是在Sort方法中出現的,我們看到有一個arr變數,這個變數並沒有在for迴圈中定義,而是在for迴圈外定義的,在for迴圈裡面不斷被重新賦值。
var arr []int
const NUM int = 100000000
for i := 0; i < NUM; i++ {
arr = []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
bubbleSort(arr)
}
將上面的程式碼改成如下:
const NUM int = 100000000
for i := 0; i < NUM; i++ {
arr := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
bubbleSort(arr)
}
完成之後,我們再次測試,結果如下,這下心裡稍微平衡一點了,要不然我一直熱愛的Go語言竟然效能如此之差,怎麼受得了呢。那麼為什麼就改了一行程式碼,差距如此之大。
1.Java結果:3263111300 ns = 3263 ms = 3.2 s
2.Go結果: 4137247700 ns = 4137 ms = 4.1 s
這其實設計到Go的變數分配問題,Go記憶體分兩種堆和棧,一個變數要麼被分配在堆上,要麼分配在棧上。
堆:由 GC 負責回收。對應於程式地址空間的堆
棧:不涉及 GC 操作。每個 goroutine 都有自己的棧,初始時被分配在程式地址空間的棧上,擴容時被分配在程式地址空間的堆上。
我們這裡的arr變數是一個區域性變數,那麼到底Go將它分配在哪裡呢?,我們對這兩段程式碼做反編譯分析,寫一個main.go檔案,在main函式中分別呼叫兩個排序函式,然後執行這個命令:go tool compile -m main.go
,得到結果如下,其中第一個圈紅的是修改前的程式碼arr變數是被分配在堆上,修改後的程式碼arr變數是被分配在棧上,這是Go自主最佳化的,是怎麼確定呢?Go透過做逃逸分析得出的,相信大家已經明白一些了,關於變數分配就說到這裡,這個話題很深,可以聊很久,後面公眾號會單獨談這個問題的,現在能說明白問題就行。
事實上,如果你再深入一下,你對Java和Go的for迴圈次數調整一下,比如都調整為迴圈一千次,你再比較結果,你會發現Go的效能比Java的好,為什麼呢?我提個醒,你可以從GC和Java的JIT最佳化方面思考一下。
再有如果對Go的bubbleSort方法改為指標傳遞,如下,那麼Go的執行效能又將如何,你可以試一試,留言區討論。
func bubbleSort(arr *[]int) {
}
寫在最後
拋開應用場景去談效能都是耍流氓,每個語言都有自己的應用場景,有編譯時,執行時的最佳化等,單單靠一個排序函式的結果探討Java和Go的效能是錯誤的。網上也有很多實驗來說明Go的效能是Java的好幾倍,希望你見到時好好思考一下,這個測試是客觀的嗎?為什麼會出現這個我意料之外的結果?
我看到的文章連結:https://studygolang.com/articles/25933
覺得有用大家關注一下公眾號啊,微信搜尋 “技術人技術事” 就可以了,閱讀更多精彩文章
本作品採用《CC 協議》,轉載必須註明作者和本文連結