golang, node.js 與 python 文件處理性能測試
在這個示例對應的實際任務中,不是要統計字元的數量,而是要處理 http 的訪問日誌,按|分隔的狀態碼,路徑,域名,錯誤碼等等。
很多同學給我提演算法方面的建議,那樣會導致代碼抽象層級比較低,修改起來會比較難。所以我傾向於找到一種不那麼犧牲抽象層級,同時又比較快速的寫法。-----------------------------------------------------
任務設定:
對於一個簡單的大文件,讀取該文件並對內容按行分隔,對行內內容按 `|` 分隔,統計所遇到的所有字元數。
測試環境,就是我的筆記本:
型號名稱:tMacBook Air
型號標識符:tMacBookAir5,2
處理器名稱:tIntel Core i5
處理器速度:t1.8 GHz
處理器數目:t1
核總數:t2
L2 緩存(每個核):t256 KB
L3 緩存:t3 MB
內存:t4 GB
首先,用 ruby 生成一個簡單的大文件,1000w行數據,
文件信息如下:
生成腳本:possible_value = [a, b, c, d, e]nnwfile = File::open(test_split.txt, w)nn(1..10000000).each dontwfile.write("#{possible_value.sample}|#{possible_value.sample}|#{possible_value.sample}|#{possible_value.sample}|#{possible_value.sample}n")nendnnwfile.close()n
首先,先進行 node.js 的測試:
第一個 node.js 腳本,是直接讀取整個文件,然後進行內容處理:
var fs = require(fs)nnvar startTime = new Date();nnvar content = fs.readFileSync(./test_split.txt, utf-8)nnvar charCount = {}nncontent.split(n).forEach(function (line) {n line.split(|).forEach(function (char) {n charCount[char] = charCount[char] || 0;nn charCount[char]++n })n})nnvar endTime = new Date();nnconsole.log(charCount, charCount)nconsole.log(diff time, endTime - startTime)nn/*ncharCount { c: 9996207,n b: 9997125,n a: 10000165,n e: 10001490,n d: 10005013,n : 1 }ndiff time 11627n*/n
處理的時間在 11.6s 左右。
第二個 node.js 腳本,通過流的方式按行讀取文件並處理每一行:
var fs = require(fs)nvar readline = require(readline)nnvar startTime = new Date();nnvar charCount = {}nnvar lineReader = readline.createInterface({n input: require(fs).createReadStream(./test_split.txt)n});nnlineReader.on(line, function (line) {n var chars = line.split(|)n for (var j = 0; j < chars.length; j++) {n var char = chars[j]n charCount[char] = charCount[char] || 0;n charCount[char]++n }n});nnlineReader.on(close, function () {n var endTime = new Date();nn console.log(charCount, charCount)n console.log(diff time, endTime - startTime)n})nn/*ncharCount { c: 9996207, b: 9997125, a: 10000165, e: 10001490, d: 10005013 }ndiff time 11337n*/n
時間不差太多。後來我看了一下讀取文件的時間,全量讀取整個95M的文件,在我這裡的時間是200ms以下。所以時間基本是花在cpu上而非io上。
第三個node.js腳本,使用 for 循環代替 foreach:
var fs = require(fs)nnvar startTime = new Date();nnvar content = fs.readFileSync(./test_split.txt, utf-8)nnvar charCount = {}nnvar lines = content.split(n);nfor (var i = 0; i < lines.length; i++) {n var chars = lines[i].split(|)n for (var j = 0; j < chars.length; j++) {n var char = chars[j]n charCount[char] = charCount[char] || 0;n charCount[char]++n }n}nnvar endTime = new Date();nnconsole.log(charCount, charCount)nconsole.log(diff time, endTime - startTime)nn/*ncharCount { c: 9996207,n b: 9997125,n a: 10000165,n e: 10001490,n d: 10005013,n : 1 }ndiff time 9448n*/n
9.4s 這樣,相比第一個腳本快了有2s。
然後是 golang 的測試,
第一個同樣是全量讀取文件:
package mainnnimport (nt"fmt"ntioutil "io/ioutil"nt"strings"nt"time"n)nnfunc main() {ntstartTime := time.Now()nntcontent, err := ioutil.ReadFile("test_split.txt")nntif err != nil {nttpanic(err)nt}nntcharCounter := make(map[string]int64)nntlines := strings.Split(string(content), "n")nntfor _, line := range lines {nttchars := strings.Split(line, "|")nttfor _, char := range chars {ntttcharCounter[char]++ntt}nt}nntdiffTime := time.Since(startTime)nntfmt.Println(charCounter)ntfmt.Println("diff time: ", diffTime)nnt/*ntttmap[c:9996207 b:9997125 a:10000165 e:10001490 d:10005013 :1]nttdiff time: 11.340632875snt*/n}n
11s 的話,看起來跟 node.js 沒差太多啊。。我本身還以為編譯型語言會快很多。
golang 在編譯的時候,都是直接 go build filename 這樣的,貌似也沒有編譯優化的選項。
第二個是按行讀取的:
package mainnnimport (nt"bufio"nt"fmt"nt"os"nt"strings"nt"time"n)nnfunc main() {ntstartTime := time.Now()nntfile, err := os.Open("test_split.txt")nntif err != nil {nttpanic(err)nt}nntscanner := bufio.NewScanner(file)nntcharCounter := make(map[string]int64)nntfor scanner.Scan() {nttchars := strings.Split(scanner.Text(), "|")nttfor _, char := range chars {ntttcharCounter[char]++ntt}nt}nntdiffTime := time.Since(startTime)nntfmt.Println(charCounter)ntfmt.Println("diff time: ", diffTime)n}nn/*nmap[d:10005013 c:9996207 b:9997125 a:10000165 e:10001490]ndiff time: 8.201861898sn*/n
8.2s,不知為何會比第一個快。。。
然後到 python 的,python 只寫了一個腳本,是按行讀取,只是分別用了 cpython 和 pypy 來運行:
import timennstart = time.time()nncharCounter = {}nnwith open(test_split.txt) as f:ntfor line in f:nttline = line.strip()nttchars = line.split(|)nttfor char in chars:ntttcharCounter[char] = charCounter.get(char, 0) + 1nnend = time.time()nnprint charCounternprint diff time, end - startnn# {a: 10000165, c: 9996207, b: 9997125, e: 10001490, d: 10005013}n# diff time 30.5330450535n
以上是 cpython 的結果,可以看到跟 node.js 的結果已經拉開很多了,差距有 20s。
而 pypy 的結果,跑了多次都顯示是:
{c: 9996207, b: 9997125, a: 10000165, e: 10001490, d: 10005013}ndiff time 5.69767999649n
只需要5.6s,把golang和node.js都甩開了。。原因不明。。。
推薦閱讀:
※世界五大類刀劍性能評比,武士刀進不了前三?
※JIT 為什麼能大幅度提升性能?
※預算4500左右配一台主機(僅主機)玩OW和GTA,求大神推薦一下目前這個價位才能配到最性價比的機器?