起因
我的一個項目使用 Kotlin 編寫,他是一個多維數據庫應用程序,所以會非常頻繁的操作 int 數組,其中有段程序就需要進行 幾億次的數組清除動作,類似這樣的代碼:
Arrays.fill(target, 0);
這個Arrays.fill其實就是jdk自帶的一個實現,非常簡陋,就是一個for循環填充數據。
所以我想改進他,將常見的數組長度編寫成單個的實現,比如清除8個長度的方法如下:
fun clear8(target: IntArray) { if(target.size < 8){ throw IndexOutOfBoundsException() } target[0] = 0 target[1] = 0 target[2] = 0 target[3] = 0 target[4] = 0 target[5] = 0 target[6] = 0 target[7] = 0 }
不要懷疑你的眼睛,這樣的寫法通常是有效的。好的編譯器會優化我寫的代碼,當然,更好的編譯器會優化一個簡單數組的for循環,這是后話。
那我們就測試一下吧。
import java.util.* import kotlin.system.measureNanoTime fun main() { test3() } private fun test3() { val size = 8 val time2 = measureNanoTime { val target = IntArray(size) for (i in 0 until 10_0000_0000) { IntArrays.clear8(target) } } println("fill$size $time2") val time1 = measureNanoTime { val target = IntArray(size) for (i in 0 until 10_0000_0000) { Arrays.fill(target, 0) } } println("Arrays.fill$size $time1") println() } internal object IntArrays { fun clear8(target: IntArray) { if(target.size < 8){ throw IndexOutOfBoundsException() } target[0] = 0 target[1] = 0 target[2] = 0 target[3] = 0 target[4] = 0 target[5] = 0 target[6] = 0 target[7] = 0 } }
測試結果:
fill8 55,408,200
Arrays.fill8 2,262,171,100
可以看出,使用展開的方式,比java自帶的2.2秒,性能提高了40倍!!
與Java的性能對比
我感嘆kotlin的編譯器真的很強,但仔細一想,不對啊, Kotlin 就是基於 JVM 的,功勞應該是 java 的虛擬機運行時很厲害,所以如果這個程序如果轉化為java直接編寫是不是更快,至少性能一致吧。說干就干。

//IntArrays.java import java.util.Arrays; final class IntArrays { static void clear8(int[] target) { /* if (target.length < 8){ throw new IndexOutOfBoundsException(); }*/ target[0] = 0; target[1] = 0; target[2] = 0; target[3] = 0; target[4] = 0; target[5] = 0; target[6] = 0; target[7] = 0; } } // IntArraysDemoJava.java import java.util.Arrays; public final class IntArraysDemoJava { public static void main(String[] var0) { test1(); } private static void test1() { long count = 1000000000; long start = System.nanoTime(); final int[] target = new int[8]; for(int i = 0; i < count; i++) { IntArrays.clear8(target); } long time2 = System.nanoTime() - start; System.out.println("fill8 " + time2); start = System.nanoTime(); for(int i = 0; i < count; i++) { Arrays.fill(target, 0); } long time1 = System.nanoTime() - start; System.out.println("Arrays.fill8 " + time1); System.out.println(); } }
測試結果如下:
fill8 2,018,500,800
Arrays.fill8 2,234,306,500
天啊,在java下這種優化幾乎沒有效果,java我沒有找到什么 release編譯參數的概念,最多只有debug = false,我是在gradle中包含
compileJava { options.debug = false }
那么就是說,Kotlin生成的字節碼要好於 Java生成的字節碼?
Java Kotlin ALOAD 0 ALOAD 1 ICONST_0 ICONST_0 ICONST_0 ICONST_0 IASTORE ASTORE ALOAD 0 ALOAD 1 ICONST_1 ICONST_1 ICONST_0 ICONST_0 IASTORE IASTORE
字節碼稍微不同,你要是問我為什么? 我母雞啊。。。。。。
與C# 的對比
作為一個 .net 的死忠粉,這個時候就會想着是不是 c# 更快一些,更何況 .net core 3做了大量的性能優化,
class Program { static void Main(string[] args) { Test3.test1(); } } class Test3 { public static void test1() { long count = 1000000000; var watch = System.Diagnostics.Stopwatch.StartNew(); int[] target = new int[8]; for (int i = 0; i < count; i++) { Clear8(target); } watch.Stop(); Console.WriteLine("fill8 " + watch.Elapsed); watch.Restart(); for (int i = 0; i < count; i++) { Array.Clear(target, 0,8); } watch.Stop(); Console.WriteLine("Array.Clear8 " + watch.Elapsed); Console.WriteLine(); } static void Clear8(int[] target) { /* if (target.Length < 8) { throw new IndexOutOfRangeException(); }*/ target[0] = 0; target[1] = 0; target[2] = 0; target[3] = 0; target[4] = 0; target[5] = 0; target[6] = 0; target[7] = 0; } }
測試成績:
fill8 00:00:02.7462676
Array.Clear8 00:00:08.4920514
和Java比起來還要慢,甚至系統自帶的Array.clear更加慢,這怎么能讓我忍,於是一通的 Span.Fill(0),結果更不理想。
和Nim對比的性能
興趣提起來了,那就使用C語言實現一個....... 沒寫出來,我笨......,那就使用 Rust 實現一個,還是沒有實現出來,按照教程一步步寫,還是沒有搞定..........
最后折騰出來一個 Nim 環境,嗯,還是這個簡單。

import times, strutils proc clear8*[int](target: var seq[int]) = target[0] = 0 target[1] = 0 target[2] = 0 target[3] = 0 target[4] = 0 target[5] = 0 target[6] = 0 target[7] = 0 proc clear*[int](target: var seq[int]) = for i in 0..<target.len: target[i] = 0 proc test3() = const size = 8 var start = epochTime() var target = newseq[int](size) for i in 0..<10_0000_0000: target.clear8() let elapsedStr = (epochTime() - start).formatFloat(format = ffDecimal, precision = 3) echo "fill8 ", elapsedStr start = epochTime() for i in 0..<10_0000_0000: target.clear() let elapsedStr2 = (epochTime() - start).formatFloat(format = ffDecimal, precision = 3) echo "Arrays.fill ", elapsedStr2 test3()
測試成績,注意要加 --release 參數。
fill8 3.499
Arrays.fill 5.825
失望,及其失望。
備注
所有測試是在我的台式機上進行的,配置如下:
AMD Ryzen 5 3600 6 Core 3.59 Ghz
8 GB RAM
Windows 10 64 專業版
所有測試都使用release編譯。