Goでの画像処理のパフォーマンスを改善した話 CPUキャッシュ、スライス

ウォーターマークをスクラッチでGoで実装した過程で、あまたの改善を行いましたが、その一つを紹介。 「画像のブロック処理のために、先んじて1次元スライスを並べ替える。」です。 メモリ効率は改善しましたが、速度については残念ながら期待する結果が得られませんでした。 Before After スライスを左上から行優先 スライスを左上からブロック/行優先 「ブロック」とは画像の分割したまとまりを指します。 例えば「4ピクセル四方のブロックに分割する」としたとき、80ピクセル四方の画像は、横に20個、縦に20個の計400個のブロックに分割できます。 ウォーターマークのアルゴリズムでは、画像の各ブロックに対してやや重たい計算します。 この時、データの順番を予め並び替えることで、CPUキャッシュを活用し、かつスライスのコピーをなくすことで、速度を改善を試みました。 実装詳細 もとのスライスから取得するためにコピーを複数回、かつ計算結果を戻すのに同じだけコピーを行います。 このコピーをしないように、取りやすい形にスライスを並べ替えておくのが今回の改善です。 src := []int{.....} // block 0番目を取りたい! // Before // src の 10 ~ 13, 24 ~ 27, xxx, xxx の4箇所からデータを取らないと行けない! copy(dist[0], src[0]) copy(dist[1], src[1]) copy(dist[2], src[2]) copy(dist[3], src[3]) // After // 先にシャッフル // 一度だけ参照すればOK! return src[0:8:8] 詳細は以下。 // Before wavelets := dwt.HaarDWT(img, srcWidth, nil) src := wavelets[0] // cA blockAt := 0 for startY := 0; startY < waveletHeight; startY += waveletBlockHeight { for startX := 0; startX < waveletWidth; startX += waveletBlockWidth { // ここでsrcからコピー b := get(src, startX, startY) // 計算: DCT -> SVD -> 計算 -> 逆変換で戻す v, idct := dct. [続きを読む]

slog.Handlerとログレベル

※追記 2025-10-26T21:09 stdout で詰まっている説。 SetDefaultの前後でログを出力していて、AWS Lambdaのデフォルトのレポート出力と競合している? デフォルトのレポート出力中に、SetDefaultがスイッチをしようとしてロック? 解決策 「関数の起動直後のSetDefaultをしてかついる場合は、ログを極力出さない」かもです。こんなんでいいのか、という気もします。 経過見て、余力あれば追記します。

以下、発生していた問題です。

独自に slog.Handler 実装して slog.HandlerOptions.Levelを指定すると、slog.SetDefaultした瞬間に動かなくなる。

// main.go
logger := slog.New(mylog.NewJSONHandler(os.Stdout, 
    &slog.HandlerOptions{Level: slog.LevelDebug},
))
slog.SetDefault(logger) // ここで死ぬ

// mylog.go

type mylogHandler struct {
	slog.Handler
}

func NewJSONHandler(w io.Writer, opts *slog.HandlerOptions) slog.Handler {
	return &mylogHandler{Handler: slog.NewJSONHandler(w, opts)}
}

※ AWS Lambda だと動かない問題が発生して、ローカルで動かすとなんの問題もない、という不思議状態。

問題点以上。上のコードに加えて、前後でslog.Debugなどを2,3コ出力していた。 以下、誤った解決策。

一応、下で解決した。

logger := slog.New(mylog.NewJSONHandler(os.Stdout, nil))
slog.SetDefault(logger)
slog.SetLogLoggerLevel(slog.LevelDebug)

// いける
// slog.HandlerOptions で渡さずに、 slog.SetLogLoggerLevel で指定する。

内部でデッドロックしているかもで、関連するISSUEもあるよう。 原因は正直不明で、気が向いたら調査します。 わからん。

go 

Go/iter でPipelineパターンやってみた

Goのv1.23で追加されたiterパッケージでPipelineパターンをやってみる。 Pipelineパターンはiterと併用できる? Pipelineパターンをiterと併用するメリットは? Pipelineパターンやiterはどこで使うべき? 要件 アプリケーションのバックエンド開発を想定。 商品を単価と個数をrepositoryから取り出して、単価x個数の合計金額をserviceで計算、最後handlerで計算結果を確認という流れ。 実装 iterパターンの実装を確認。 type Repository struct { items [][2]int // [][2]int{price, num} } func (r Repository) Generate() iter.Seq2[int, [2]int] { // r.itemsはコンストラクタによってlenは100 return slices.All(r.items) } type Service struct { r Repository } func (s Service) Iter() iter.Seq[int] { return func(yield func(int) bool) { for _, item := range s.r.Generate() { p := s.sumPrice(item[0], item[1]) _ = yield(p) } } } type Router struct { s Service } func (r Router) HandleWithIter() { var count int for sum := range r. [続きを読む]