いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-521 (csv.ReuseRecord)(レコードを再利用して処理するよう指示)

関連記事

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。忘れないうちにメモメモ。。。

csv.Reader.ReuseRecord という設定値があることを最近知りました。こんな設定値あったのですね。どうもずっと昔からあったみたい。

名前の通り、内部処理で行データを設定するレコードを再利用するようになる設定です。

ループごとに行データを処理する実装であるなら、再利用されてても問題ないですね。

メモリ消費量とパフォーマンスも良くなるみたい。

以下、抜粋。

   // ReuseRecord controls whether calls to Read may return a slice sharing
    // the backing array of the previous call's returned slice for performance.
    // By default, each call to Read returns newly allocated memory owned by the caller.
    ReuseRecord bool

ReuseRecordは、Readの呼び出しが、パフォーマンスのために、前の呼び出しで返されたスライスのバッキング配列を共有するスライスを返すかどうかを制御します。デフォルトでは、Readの各呼び出しは、呼び出し元が所有する新しく割り当てられたメモリを返します。

デフォルトは false です。

試してみた

ベンチマーク取ってみることにしました。利用するCSVファイルは kenallのUTF-8版 (1行15列構成で124436行)です。

package main

import (
    "bufio"
    "encoding/csv"
    "errors"
    "io"
    "os"
    "testing"
)

const (
    FilePath   = "utf_ken_all.csv"
    FieldCount = 15
)

func Benchmark_Csv_ReuseRecord(b *testing.B) {
    for range b.N {
        readCsv(true)
    }
}

func Benchmark_Csv_No_ReuseRecord(b *testing.B) {
    for range b.N {
        readCsv(false)
    }
}

func readCsv(reuse bool) error {
    var (
        file *os.File
        err  error
    )
    if file, err = os.Open(FilePath); err != nil {
        return err
    }
    defer file.Close()

    var (
        bufR = bufio.NewReader(file)
        csvR = csv.NewReader(bufR)
    )
    csvR.ReuseRecord = reuse
    csvR.FieldsPerRecord = FieldCount

    var (
        rec []string
    )
    for {
        if rec, err = csvR.Read(); errors.Is(err, io.EOF) {
            break
        }

        if err != nil {
            return err
        }

        for _, field := range rec {
            io.Discard.Write([]byte(field))
        }
    }

    return nil
}

Taskfile.yml

# https://taskfile.dev

version: '3'

vars:
  KEN_ALL_FILE: utf_ken_all.csv
  KEN_ALL_ZIP: utf_ken_all.zip
  KEN_ALL_URL: https://www.post.japanpost.jp/zipcode/dl/utf/zip/{{.KEN_ALL_ZIP}}

tasks:
  default:
    cmds:
      - task: benchmark
  ken-all:
    cmds:
      - wget {{.KEN_ALL_URL}}
      - unzip {{.KEN_ALL_ZIP}}
  benchmark:
    cmds:
      - go test -bench=. -run='^$' -benchmem -count 1 -benchtime 10s

実行

$ task ken-all benchmark
task: [ken-all] wget https://www.post.japanpost.jp/zipcode/dl/utf/zip/utf_ken_all.zip
--2024-12-13 05:15:38--  https://www.post.japanpost.jp/zipcode/dl/utf/zip/utf_ken_all.zip
Resolving www.post.japanpost.jp (www.post.japanpost.jp)... 43.253.212.151
Connecting to www.post.japanpost.jp (www.post.japanpost.jp)|43.253.212.151|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 2031933 (1.9M) [application/zip]
Saving to: ‘utf_ken_all.zip’

utf_ken_all.zip                                        100%[===========================================================================================================================>]   1.94M  3.61MB/s    in 0.5s    

2024-12-13 05:15:40 (3.61 MB/s) - ‘utf_ken_all.zip’ saved [2031933/2031933]

task: [ken-all] unzip utf_ken_all.zip
Archive:  utf_ken_all.zip
  inflating: utf_ken_all.csv         
task: [benchmark] go test -bench=. -run='^$' -benchmem -count 1 -benchtime 10s
goos: linux
goarch: amd64
pkg: github.com/devlights/try-golang/examples/singleapp/csv_reuse_record
cpu: AMD EPYC 7B13
Benchmark_Csv_ReuseRecord-16                 100         120650208 ns/op        39519468 B/op    1991002 allocs/op
Benchmark_Csv_No_ReuseRecord-16               78         140634474 ns/op        69383930 B/op    2115437 allocs/op
PASS
ok      github.com/devlights/try-golang/examples/singleapp/csv_reuse_record     23.311s

確かにメモリ消費もパフォーマンスもReuseRecordを有効にした方が若干速くなりますね。

サンプルは以下にアップしてあります。

github.com

参考情報

pkg.go.dev

Goのおすすめ書籍


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。