- 公開日
RubyとGoとBashで並行処理のパフォーマンス比較をしてみた
Rubyで書いたコードをGoで書き直したらどれくらい早くなるかを検証したくてやってみた。ついでにBashでもどうなるかも比較した。
画像ファイルをダウンロードするだけの単純な処理での比較。複雑な処理になるとまた結果は全然違ってくると思います。あしからず。
比較する処理内容
100個の画像をダウンロードするコードをサンプルにやってみた。
Rubyの場合
Ruby 2.6 で検証。
直列ダウンロード
まずは何も考えず順番に1つ1つダウンロードするRubyコード。
# no_thread.rb
require 'open-uri'
(1..100).each do |i|
open("./images/#{i}.png", 'wb') do |f|
f.write open("https://dummyimage.com/600x400").read
end
end
$ time ruby no_thread.rb
ruby no_thread.rb 1.85s user 0.28s system 1% cpu 2:05.11 total
- 実行時間: 約2分。遅い
- CPU: 1%。CPUはほとんど使い切れていないことがわかる
Threadで並行ダウンロード
Threadを使って並行に処理してみる。
# thread.rb
require 'open-uri'
threads = []
(1..100).each do |i|
threads << Thread.new do
open("./images/#{i}.png", 'wb') do |f|
f.write open("https://dummyimage.com/600x400").read
end
end
end
threads.map(&:join)
$ time ruby thread.rb
ruby thread.rb 1.64s user 0.28s system 71% cpu 2.670 total
- 実行時間: 2.6秒。劇的な改善
- CPU: 70%。うまくCPUも使えている
Goの場合
Goで書き直してみる。Goのバージョンは1.13を使用。
こちらのコードを参考に書いてみた。
// go.go
package main
import (
"fmt"
"io"
"net/http"
"os"
"sync"
)
func main() {
var wg sync.WaitGroup
var url string = "https://dummyimage.com/600x400"
for i := 0; i < 100; i++ {
file, err := os.Create(fmt.Sprintf("images/%d.png", i))
if err != nil {
panic(err)
}
wg.Add(1)
go func() {
response, err := http.Get(url)
if err != nil {
panic(err)
}
defer response.Body.Close()
defer file.Close()
io.Copy(file, response.Body)
wg.Done()
}()
}
wg.Wait()
}
Go runで実行
コンパイルせずにgo run
で実行してみる。
$ time go run go.go
go run go.go 0.79s user 0.37s system 25% cpu 4.532 total
- 実行時間: 約4.5秒
- CPU: 25%
build済みバイナリで実行
buildして実行する。
$ go build go.go
$ time ./go
./go 0.23s user 0.12s system 11% cpu 3.111 total
- 実行時間: 約3秒。RubyのThreadコードより少し遅かった
- CPU: 約10%。速度のわりにCPU消費が著しく低い!
Bashの場合
Bash(シェルスクリプト)の場合も試してみる。
# bash.bash
for i in `seq 1 100`; do
wget --background --quiet "https://dummyimage.com/600x400" -O images/$i.png > /dev/null
done
$ time ./bash.bash
./bash.bash 0.25s user 0.30s system 43% cpu 1.241 total
- 実行時間: 約1.2秒。速い!
- CPU: 約40%
まとめ
単純な並行ダウンロードという処理において、実行速度だけでいうとRubyもThreadを使えばGoと同じ速度は出せるということがわかった。
一方でCPUコストに関してはビルド済みのGoバイナリのほうが圧倒的低く済むということが判明した。つまり複雑な計算処理においてはGoがRubyよりずっとアドバンテージがあるであろうことがわかった。
今回は単純な処理なのでRubyとGoの間で処理速度に大きな差は生まれなかったものの、比較するロジックが複雑化すればするほどRubyはGoに負けそうな予感がした。