昨晚寫完 GobiesVM 裡的IO.readlines函式後,很開心地用了有著100k個單詞的單行文字檔作測試。咦?怎麼一個字都沒有?

第一版的IO.readlines實作,就如同你在stackoverflow上會找到的一樣,是用bufio.Scanner來讀文字檔

1
2
3
4
5
6
input, err := os.Open(filename)
scanner := bufio.NewScanner(input)
for scanner.Scan() {
line := scanner.Text()
}

上面這段code看似沒有問題,在一般的文字檔(例如程式碼本身)測試也都正常,為什麼在遇到大檔的時候就出包了呢?
當我們去看 bufio.Scanner的實作 時,會發現 Scanner 在運作時,會將文字分為一個個的token,並交由SplitFunc決定如何區分token(預設是斷行),由於是buffered IO,所以會有每個token所能用的buffer的最大size(MaxScanTokenSize = 64 * 1024),以上面的檔案為例,因為100k個單字全部都集中於一行內,所以很輕易地就超過了這個限制。因此當我將每個詞分至多行後就可以正常運行。

有鑑於這樣的解決之道只是治標不治本,所以讓我們來看看第二個版本吧:

1
2
3
4
5
6
7
8
9
input, _ := os.Open(filename)
defer input.Close()
reader := bufio.NewReader(input)
line, err := reader.ReadString('\n')
for ; err == nil; line, err = reader.ReadString('\n') {
// Do something
}

同樣在bufio之下有實作io.Reader的buffered Reader物件可使用,裡頭也提供了bufio.Reader.ReadLine()可用,不過在文件內提到一般建議使用bufio.Reader.ReadString('\n'),而 bufio的實作 內也是後者單純些。

螢幕快照
螢幕快照

註:這裡雖然有印出內容,但以下的測試皆改為僅只有印出回傳陣列的大小,所以速度會有所不同。

這次測試出來的結果,相當地令人滿意,至少就如同上圖,GobiesVM的IO.readlines所用的時間都比MRI來得少。

然而事情沒有憨人想得那麼簡單,在不安穩的一覺醒來,我再度用10M行的文字檔作測試時,GobiesVM就原形畢露了。此時的MRI平均只要4秒多就能夠讀取完畢,而GobiesVM則要11秒左右。

當然最便利的藉口就是:GobiesVM沒有作最佳化!所以我就跑去編了個把最佳化(這些選項位於vm_opts.h裡,如果你想知道的話)全部關掉的MRI出來了…… XD
把最佳化全部拿掉之後的效果十分顯著,完成時間都在30秒以上。

……好吧,這樣的方法基本上只是自嗨,還是得找出一個比較實在點的方式才行。
可能的overhead在哪裡呢?一來當然就是即使Go效能出眾,但在多數時候拖著一個Runtime的它還是比輕盈的C還要來得慢些,二來buffered IO本來就是一個chunk一個chunk地讀進來時作處理,甚至會丟掉在所設定的delimiter後的內容,這樣難免會有些overhead。

所以綜合以上兩點,最偷吃步的方法就是:一口氣將檔案讀進來後再處理斷行:

1
2
3
4
5
6
7
content, _ := ioutil.ReadFile(filename)
str := string(content[:])
lines := strings.SplitAfter(str, "\n")
for _, line := range lines {
// Do something
}

以下是簡易的效能測試





















































GobiesVMMRI 2.0.0
7.9304.495
8.0044.058
7.1024.649
7.0755.129
7.5214.192
7.6963.851
7.2024.490
7.3273.732
7.6064.082
7.5364.041
Mean
7.4994.272

雖然還是有2~3秒的差距,但其實這也差不多是多數benchmark中Go與C本身的速度差異,所以個人是覺得這樣的結果尚可接受,況且再繼續追下去所花的時間與得到的效益相比可能沒有那麼高。

喔,有些人可能會好奇將檔案全部讀入是否會讓記憶體的使用量飆高,根據不負責的觀察,在使用第二個方法和第三個方法的memory peak並沒有明顯的差異(吃記憶體同樣都吃很兇的意味,而MRI本身也差不多)。