薄いブログ

技術の雑多なことを書く場所

次なる pkg/errors を探してを読んで

https://tech.kanmu.co.jp/entry/2023/06/19/150000 を読んで思ったこと、調べたことを書きます。

pkg/errors の移行先を探すという話で

の2つが候補から最終的には cockroachdb/errors を選んでいました。

検討の中で求めるものの1つとして 性能が大きく劣化しないこと があげられていてベンチマークが行われていました。 ベンチマークソースコードと結果は以下になります。

github.com

結果 cockroachdb/errorsgoark/errs と比較してパフォーマンスは悪いというものでした。

ここで気になるのが何故パフォーマンスが悪いのかというところです。 ベンチマークを公開する上で数値のみだけではなく何故その結果になったのかという分析も付加されていたほうが良いと言うのが僕の考えです。

追試・検証

実際に上記のベンチマークを手元の環境で実行したところ傾向としては同じでした。

cockroachdb/errors

まずは cockroachdb/errors の flamegraph を見てみます。

cockroachdb/errors の flamegraph

cockroachdb/errors では StackTrace の Format が重い事がわかります。 そもそも StackTrace は pkg/errors のものを使用しています。

https://github.com/pkg/errors/blob/5dd12d0cfe7f152f80558d591504ce685299311e/stack.go#L64

Frame.Format はあまり最適化されておらず %+v を使うと Frame ごとに3回 FuncForPC, 2回 FileLine が呼び出されてしまいます。

StackTrace の Format 以外にも strings.ReplaceAll も気になります。 cockroachdb/errors は個人情報を出力しないようにする仕組みが存在しておりその処理で strings.ReplaceAll が使われています。

個人情報を出力しない仕組みの詳細については

を確認してください。

goark/errs

次は goark/errs の flamegraph を見てみます。

goark/errs の flamegraph

まず cockroachdb/errors の方にあった FuncForPC などの関数がありません。 ソースコードを確認したところ出力時に解決するのではなく New したタイミングで解決するようになっています。 しかも New を呼び出した関数名のみを取得します。なので goark/errs は適切にエラーを Wrap しなければスタックトレースになりません。

出力の処理は json.Marshal と fmt.Sprintf を使っています。 json.Marshal を高速化するために goccy/go-json に変えるのもありかもしれません。

まとめ

cockroachdb/errorsgoark/errs のパフォーマンス差異の理由は以下

  • 関数名解決のタイミングが異なる (cockroachdb/errors は出力時, goark/errs は生成時)
    • 今回のベンチマークの書き方では生成は1回なので cockroachdb/errors が不利になる
  • cockroachdb/errors は個人情報を出力しない仕組みのために若干オーバーヘッドが存在する
  • cockroachdb/errors のスタックトレースの出力が重いのは pkg/errors が原因
  • goark/errs.New はスタックトレースを取得しない (関数名のみの取得) ため有利になっている