😊
Goのexcelize.StreamWriterを使ったらExcelファイルの出力が速くなった
はじめに
株式会社CastingONEでバックエンドエンジニアをしている村上です。
最近のマイブームは育爪です。
こちらのネイルオイルのおかげで、なんかいい感じになってる気がします。
excelize.StreamWriter
弊社ではバックエンドでExcelファイルを生成するために、Goのexcelizeを使っています。セルに値を出力する際、いままでFile.SetCellValueを使ってきましたが、StreamWriterというものがあることに気づきました。
v2.1.0で追加されたようで、下記のようにパフォーマンスが向上しています。
New functions NewStreamWriter and Flush to generate the new worksheet with huge amounts of data.
Compared to non-streaming writing, reduced memory usage by 90.2%, time cost by 53%
サイズの大きいファイルを出力しようとすると、時間がかかったりメモリ不足のエラーが発生したりしていたので、StreamWriterに乗り換えたところパフォーマンスをかなり改善できました。
備忘録をかねて、ベンチマークを残しておきたいと思います。
ベンチマーク
StreamWriterを使わない関数と使う関数を書いて、
package main
import (
"log"
"github.com/xuri/excelize/v2"
)
func NonStreaming() {
file := excelize.NewFile()
defer func() {
if err := file.Close(); err != nil {
log.Fatal(err)
}
}()
sheetName := "Test"
_, err := file.NewSheet(sheetName)
if err != nil {
log.Fatal(err)
}
for row := 1; row <= 100; row++ {
for col := 1; col <= 100; col++ {
cell, err := excelize.CoordinatesToCellName(col, row)
if err != nil {
log.Fatal(err)
}
err = file.SetCellValue(sheetName, cell, row+col)
if err != nil {
log.Fatal(err)
}
}
}
if err := file.SaveAs("NonStreaming.xlsx"); err != nil {
log.Fatal(err)
}
}
func Streaming() {
file := excelize.NewFile()
defer func() {
if err := file.Close(); err != nil {
log.Fatal(err)
}
}()
sheetName := "Test"
_, err := file.NewSheet(sheetName)
if err != nil {
log.Fatal(err)
}
sw, err := file.NewStreamWriter(sheetName)
if err != nil {
log.Fatal(err)
}
for row := 1; row <= 100; row++ {
values := []interface{}{}
for col := 1; col <= 100; col++ {
values = append(values, row+col)
}
cell, err := excelize.CoordinatesToCellName(1, row)
if err != nil {
log.Fatal(err)
}
err = sw.SetRow(cell, values)
if err != nil {
log.Fatal(err)
}
}
if err := sw.Flush(); err != nil {
log.Fatal(err)
}
if err := file.SaveAs("Streaming.xlsx"); err != nil {
log.Fatal(err)
}
}
ベンチマークを取るコードも書きました。
package main
import "testing"
func BenchmarkNonStreaming(b *testing.B) {
for i := 0; i < b.N; i++ {
NonStreaming()
}
}
func BenchmarkStreaming(b *testing.B) {
for i := 0; i < b.N; i++ {
Streaming()
}
}
StreamWriterを使った関数の方が、高速でメモリ効率も良い結果となりました。出力する値によって結果が異なる可能性もありますが、StreamWriterかなり良いと思いました!
$ go test -bench . -benchmem
goos: darwin
goarch: arm64
pkg: go-excelize
BenchmarkNonStreaming-8 75 15306014 ns/op 5927855 B/op 94783 allocs/op
BenchmarkStreaming-8 218 5791354 ns/op 1919249 B/op 48058 allocs/op
PASS
ok go-excelize 3.789s
デメリットとして、従来のFileは SetCellStyle、SetColStyle、SetRowStyleといった書式を設定するメソッドが充実していますが、StreamWriterにはそれらが揃っていないところがあります。今後機能が増えていくにつれて、さらに使いやすくなると思います!
おわりに
弊社ではいっしょに働いてくれるエンジニアを募集中です。社員でもフリーランスでも、フルタイムでも短時間の副業でも大歓迎なので、気軽にご連絡ください!
Discussion