いろいろ備忘録日記

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

Goメモ-414 (重複した呼び出しを抑制したい)(x/sync/singleflightパッケージ)

関連記事

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

概要

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

ヘビーな処理を行う関数なりメソッドなりがあって、それの呼び出しが特定の時間幅で重複して行われることがあったりします。

それらの重複した呼び出しを抑制して、最初に得られた結果をキャッシュして返すというのもよくありますね。

Go には、sync.OnceValue などがあるので、それで事足りることが多いのですが、singleflight というパッケージもあります。

singleflightパッケージは、重複した関数呼び出しを抑制するためのメカニズムを提供します。

このパッケージは、特に高価な操作や重複する操作が同時に複数のゴルーチンから要求される場合に有効です。

singleflightパッケージは、golang.org/x/sync/singleflightライブラリに含まれており、主に以下の機能を提供します。

  • 重複呼び出しの抑制:同じキーに対する複数のリクエストが同時に発生した場合、最初のリクエストが完了するまで他のリクエストを待機させ、結果を共有します。
  • 効率の向上:重複した操作を防ぐことで、サービスやデータベースへの不要な負荷を軽減します。
  • シンプルなAPI:Group型を使用して、重複する操作を管理します。

Cache Stampedeなどが発生する可能性がある部分などで利用出来ます。

sync.OnceValueなどと異なり、Group.Forget() が存在するのがちょっとした違いですかね。

サンプル

package main

import (
    "log"
    "sync"
    "time"

    "golang.org/x/sync/singleflight"
)

func init() {
    log.SetFlags(0)
}

func heavy(delay time.Duration, prefix string) {
    log.Printf("%s start", prefix)
    defer log.Printf("%s end  ", prefix)

    time.Sleep(delay)
}

func main() {
    // Group.Do() or Group.DoChan() を利用して
    // 重複する呼び出しが発生する箇所や処理負荷が高い操作などの呼び出しを
    // 抑制することが出来る。
    //
    // Cache Stampedeが発生する可能性がある部分には非常に有効です。

    const (
        KEY = "FUNC-GROUP-KEY"
    )

    var (
        grp     = &singleflight.Group{}
        ready   = make(chan struct{})
        results = make(chan (<-chan singleflight.Result))
        wg      = sync.WaitGroup{}
    )

    wg.Add(3)
    go func() {
        defer wg.Done()

        results <- grp.DoChan(KEY, func() (any, error) {
            <-ready
            heavy(3*time.Second, "func1")
            return "func1", nil
        })
    }()
    go func() {
        defer wg.Done()

        results <- grp.DoChan(KEY, func() (any, error) {
            <-ready
            heavy(1*time.Second, "func2")
            return "func2", nil
        })
    }()
    go func() {
        defer wg.Done()

        results <- grp.DoChan(KEY, func() (any, error) {
            <-ready
            heavy(2*time.Second, "func3")
            return "func3", nil
        })
    }()
    go func() {
        defer close(results)
        wg.Wait()
    }()

    // よーいドン
    close(ready)

    for ret := range results {
        log.Printf("%+v", <-ret)
    }

    // 更に追加呼び出し
    //   ただし、実行する前に Group.Forget() を呼び出して
    //   キーに紐づく結果を忘れさせてから実行
    grp.Forget(KEY)
    ret := grp.DoChan(KEY, func() (any, error) {
        heavy(1*time.Second, "func4")
        return "func4", nil
    })
    log.Printf("%+v", <-ret)
}

実行すると以下の様になります。

$ task
task: [build] go build -o app
task: [run] ./app
func1 start
func1 end  
{Val:func1 Err:<nil> Shared:true}
{Val:func1 Err:<nil> Shared:true}
{Val:func1 Err:<nil> Shared:true}
func4 start
func4 end  
{Val:func4 Err:<nil> Shared:false}

最初の3つの重複した呼び出しは抑制されて一つの結果が3回取得出来ていることが分かります。

4回目の呼び出しは、事前にキーに紐づく結果を忘れて( Forget メソッド)もらってから実行していますので、Sharedの値がfalseとなります。

参考情報

Goのおすすめ書籍


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

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

Goメモ-413 (既存関数を置き換え)(go:linkname, コンパイラディレクティブ)

関連記事

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

概要

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

標準ライブラリの time.Sleep() ってどんな実装になってるんだろうって思ってソース見てみたら

// Sleep pauses the current goroutine for at least the duration d.
// A negative or zero duration causes Sleep to return immediately.
func Sleep(d Duration)

https://cs.opensource.google/go/go/+/refs/tags/go1.22.3:src/time/sleep.go;l=9

ん?実装が無い。。?なんで?ってなりました。

んで、情報を探すと以下を発見。

forum.golangbridge.org

回答を見ると、実際には runtime パッケージの方に定義されているよとのこと。

てことで、src/runtime/time.go というファイルを見てみると

// timeSleep puts the current goroutine to sleep for at least ns nanoseconds.
//
//go:linkname timeSleep time.Sleep
func timeSleep(ns int64) {

https://cs.opensource.google/go/go/+/refs/tags/go1.22.3:src/runtime/time.go;l=178

あった。よく見ると go:linkname というディレティブが付いている。

これは何だ?って調べると以下のとても分かりやすい説明をしてくださっている記事がありました。感謝。

zenn.dev

なるほど・・・。既存関数を置き換えられるのですね。当然安全ではないので、しょっちゅう利用するものでは無い。

でも、知っておくとイザというときに役に立ちそう。

以下、自分用で試してみたサンプルです。上のzennの記事で用いられているサンプルほぼそのままとなりましたが。

サンプル

package main

import (
    "log"
    "time"
    _ "unsafe"
)

// fixedNow は、固定の時間を返す time.Now() のモックです。
//
// 標準ライブラリの time.Now() を置き換えるために
// linknameコンパイラディレクティブを付与しています。
//
// linknameコンパイラディレクティブを利用するためには
// unsafeパッケージをインポート(明示的、暗黙的問わず)する必要があります。
//
// linknameは、標準ライブラリ内で利用されており、例えば time.Sleep() も
// 実際のソースコードを見ると以下の宣言となっており、宣言のみで実装がありません。
//
// # $(go env GOROOT)/src/time/sleep.go
//
// package time
//
// func Sleep(d Duration)
//
// 実体は、runtime.timeSleep() となっています。
//
// # $(go env GOROOT)/src/runtime/time.go
//
// package runtime
//
// //go:linkname timeSleep time.Sleep
// func timeSleep(ns int64) { ... }
//
//go:linkname fixedNow time.Now
func fixedNow() time.Time {
    return time.Date(2000, 1, 1, 0, 0, 0, 0, time.Local)
}

func init() {
    log.SetFlags(0)
}

func main() {
    // linknameコンパイラディレクティブによって time.Now が置き換えられる
    log.Println(time.Now())
}

try-golang/examples/singleapp/replace_timenow_with_golinkname_directive at main · devlights/try-golang · GitHub

実行すると以下のようになります。

$ task
task: [build] go build -o app
task: [default] ./app
2000-01-01 00:00:00 +0000 UTC

time.Now() の結果を固定日時になるように置き換えているので、どのタイミングでtime.Now()を呼んでも同じ日時となります。

参考情報

Goのおすすめ書籍


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

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

Goメモ-412 (指定したオフセットから書き込む)(io.OffsetWriter)

関連記事

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

概要

以下、自分用のメモです。たまに使いたいときに忘れるので、ここにメモメモ。。。

ioパッケージの下には、ある目的に特化したストリームがいろいろあります。

今回は、指定したオフセットから書き込む動きをしてくれる io.OffsetWriter について。

サンプル

package ioop

import (
    "bufio"
    "io"
    "os"
    "path/filepath"

    "github.com/devlights/gomy/errs"
    "github.com/devlights/gomy/output"
)

// OffsetWrite は、io.OffsetWriter を利用したサンプルです。
//
// io.OffsetWriter は、io.WriterAt インターフェースを実装しているものを要求する。
//
// os.File が io.WriterAt を実装している。
//
// # REFERENCES
//   - https://pkg.go.dev/io@go1.22.2#OffsetWriter
//   - https://cs.opensource.google/go/go/+/go1.22.2:src/io/io.go;l=570
func OffsetWrite() error {
    w, err := os.CreateTemp(os.TempDir(), "trygolang")
    if err != nil {
        return err
    }

    fstat := errs.Drop(w.Stat())
    {
        defer w.Close()

        bufW := bufio.NewWriter(w)
        bufW.WriteString("helloworld こんにちは世界")
        bufW.Flush()

        offW := io.NewOffsetWriter(w, 11) // "helloworld " の次の位置(つまり「こ」)にオフセットを設定
        offW.Write([]byte("コンニチハ"))

        offW.Seek(int64(15), io.SeekStart) // "コンニチハ" の次の位置(つまり「世」)にシークポジションをセット
        offW.Write([]byte("セカイ"))
    }

    data := errs.Drop(os.ReadFile(filepath.Join(os.TempDir(), fstat.Name())))
    output.Stdoutl("[offW]", string(data))

    return nil
}

try-golang/examples/basic/ioop/offsetwrite.go at main · devlights/try-golang · GitHub

実行すると以下のようになります。

$ task
task: [build] go build .
task: [run] ./try-golang -onetime

ENTER EXAMPLE NAME: ioop_offset_write

[Name] "ioop_offset_write"
[offW]               helloworld コンニチハセカイ


[Elapsed] 165.78µs

参考情報

Goのおすすめ書籍


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

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

Goメモ-411 (指定した範囲のデータだけ読み出す)(io.SectionReader)

関連記事

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

概要

以下、自分用のメモです。たまに使いたいときに忘れるので、ここにメモメモ。。。

ioパッケージの下には、ある目的に特化したストリームがいろいろあります。

今回は、指定した範囲のデータだけ読み出す動きをしてくれる io.SectionReader について。

サンプル

package ioop

import (
    "bytes"
    "io"
    "strings"

    "github.com/devlights/gomy/output"
)

// SectionRead は、io.SectionReader を利用したサンプルです。
//
// io.SectionReader は、指定した範囲のデータを読み込んでくれる特殊ストリーム。
//
// > SectionReader implements Read, Seek, and ReadAt on a section of an underlying ReaderAt.
//
// > SectionReaderは、Read、Seek、ReadAtを実装しています。
//
// # REFERENCES
//   - https://pkg.go.dev/io@go1.22.2#SectionReader
//   - https://cs.opensource.google/go/go/+/refs/tags/go1.22.2:src/io/io.go;l=501
func SectionRead() error {
    var (
        r    = strings.NewReader("helloworld こんにちは世界")
        secR = io.NewSectionReader(r, 11, 15)
        buf  = new(bytes.Buffer)
        err  error
    )

    _, err = io.Copy(buf, secR)
    if err != nil {
        return err
    }

    output.Stdoutl("[secR]", buf.String())

    return nil
}

try-golang/examples/basic/ioop/sectionread.go at main · devlights/try-golang · GitHub

実行すると以下のようになります。

$ task
task: [build] go build .
task: [run] ./try-golang -onetime

ENTER EXAMPLE NAME: ioop_section_read

[Name] "ioop_section_read"
[secR]               こんにちは


[Elapsed] 24.97µs

参考情報

Goのおすすめ書籍


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

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

Goメモ-410 (指定したサイズ分だけ読み出す)(io.LimitReader)

関連記事

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

概要

以下、自分用のメモです。たまに使いたいときに忘れるので、ここにメモメモ。。。

ioパッケージの下には、ある目的に特化したストリームがいろいろあります。

今回は、指定したサイズ分だけ読み出す動きをしてくれる io.LimitReader について。

サンプル

package ioop

import (
    "bytes"
    "io"

    "github.com/devlights/gomy/output"
)

// LimitRead は、io.LimitedReader のサンプルです.
//
// 通信データのように固定部分を決まったサイズで読み取るときなどに便利。
//
// # REFERENCES
//   - https://pkg.go.dev/io@go1.19.3#LimitedReader
func LimitRead() error {
    const (
        ReadSize = 0x04
        BufSize  = 0xff
    )

    var (
        message     = "hello world"
        src         = bytes.NewBufferString(message)
        limitReader = io.LimitReader(src, ReadSize)
    )
    output.Stdoutf("[LimitReader]", "original: %v\n", message)

    for {
        var (
            buf  = make([]byte, BufSize)
            size int
            err  error
        )

        size, err = limitReader.Read(buf)
        if err != nil {
            if err == io.EOF {
                break
            }
            return err
        }

        output.Stdoutf("[LimitRead]", "%d byte(s) read: %v\n", size, string(buf[:size]))
    }

    return nil
}

try-golang/examples/basic/ioop/limitread.go at main · devlights/try-golang · GitHub

実行すると以下のようになります。

$ task
task: [build] go build .
task: [run] ./try-golang -onetime

ENTER EXAMPLE NAME: ioop_limit_read

[Name] "ioop_limit_read"
[LimitReader]        original: hello world
[LimitRead]          4 byte(s) read: hell


[Elapsed] 22.76µs

参考情報

Goのおすすめ書籍


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

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

Goメモ-409 (teeコマンドのように読み出しながら別のストリームに書き込む)(io.TeeReader)

関連記事

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

概要

以下、自分用のメモです。たまに使いたいときに忘れるので、ここにメモメモ。。。

ioパッケージの下には、ある目的に特化したストリームがいろいろあります。

今回は、teeコマンドと同じような動きをしてくれる io.TeeReader について。

サンプル

package ioop

import (
    "io"
    "strings"

    "github.com/devlights/gomy/output"
)

// byteCounter は、書き込まれたバイト数を数える特殊 io.Writer です。
type byteCounter int

func (me *byteCounter) Write(p []byte) (n int, err error) {
    *me = byteCounter(int(*me) + len(p))
    return len(p), nil
}

// TeeRead は、io.TeeReader を利用したサンプルです。
//
// io.TeeReader は、teeコマンドと同じような動きをする。読み取ったデータは戻り値のReaderを経由して取得し、さらに引数で指定したio.Writerにも書き込まれる。
//
// > TeeReader returns a Reader that writes to w what it reads from r. All reads from r performed through it are matched with corresponding writes to w.
//
// > TeeReaderは、rから読み取ったものをwに書き込むReaderを返します。TeeReaderを介して実行されるrからのすべての読み取りは、対応するwへの書き込みと一致します。
//
// io.TeeReaderには、内部バッファを持っていない。
// なので、rから読み取ったデータは即時wに書き込まないとブロックされてしまう。
//
// # REFERENCES
//   - https://pkg.go.dev/io@go1.22.2#TeeReader
//   - https://cs.opensource.google/go/go/+/refs/tags/go1.22.2:src/io/io.go;l=618
func TeeRead() error {
    r := strings.NewReader("helloworld こんにちは世界")
    w := byteCounter(0)
    b, _ := io.ReadAll(io.TeeReader(r, &w))

    output.Stdoutl("[r]", string(b))
    output.StdoutHr()
    output.Stdoutl("[w]", int(w))

    return nil
}

try-golang/examples/basic/ioop/teeread.go at main · devlights/try-golang · GitHub

実行すると以下のようになります。

$ task
task: [build] go build .
task: [run] ./try-golang -onetime

ENTER EXAMPLE NAME: ioop_tee_read

[Name] "ioop_tee_read"
[r]                  helloworld こんにちは世界
--------------------------------------------------
[w]                  32


[Elapsed] 34.05µs

参考情報

Goのおすすめ書籍


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

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

Goメモ-408 (書式表示で指定ビット桁を表示)(fmt, %0Nb)

関連記事

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

概要

以下、自分用のメモです。たまに使いたいときに忘れるので、ここにメモメモ。。。

たまに、数値を指定したビット桁で揃えて表示したいときがあったりします。

%b に桁指定をするだけなのですが %b ごと忘れることが多いので、以下サンプル。

サンプル

package formatting

import (
    "fmt"
)

// Nbit は、書式表示の %b を利用して指定ビット数分を表示するサンプルです。
func Nbit() error {
    var (
        v1 = 127
        v2 = 1 << 8
        v3 = 1 << 10
        v4 = 0x5555
        v5 = 0xaaaa

        fn = func(v int) {
            // %0Nb で、指定ビット桁数で2進数表示できる
            // 以下は 16ビット 分で表示
            //
            // 当然、指定桁より大きい場合も表示されるが、表示がずれるので注意
            fmt.Printf("%016b\t0x%04[1]x\t%5[1]d\n", v)
        }
    )

    for _, v := range []int{v1, v2, v3, v4, v5} {
        fn(v)
    }

    return nil
}

try-golang/examples/basic/formatting/format_bit.go at main · devlights/try-golang · GitHub

実行すると以下のようになります。

$ task
task: Task "build" is up to date
task: [run] ./try-golang -onetime

ENTER EXAMPLE NAME: formatting_nbit

[Name] "formatting_nbit"
0000000001111111        0x007f    127
0000000100000000        0x0100    256
0000010000000000        0x0400   1024
0101010101010101        0x5555  21845
1010101010101010        0xaaaa  43690


[Elapsed] 146.61µs

参考情報

Goのおすすめ書籍


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

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

Goメモ-407 (cmp.Or, cmp.Compareを用いて複合キーのソート処理を実装)

関連記事

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

概要

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

Go 1.22 で、cmp.Or という関数が追加されていたので、ついでにそれを利用して複合キーでのソート処理が楽に書けるサンプルをメモ書き。

サンプル

package cmpop

import (
    "cmp"
    "slices"

    "github.com/devlights/gomy/output"
)

// CompositeSortKeys は、cmp.Or, cmp.Compareを用いて複合キーのソート処理を実装するサンプルです。
func CompositeSortKeys() error {
    type Person struct {
        Name string
        Age  uint8
    }

    var (
        people = []Person{
            {"Aikawa", 21},
            {"Tanaka", 22},
            {"Kato", 33},
            {"Suzuki", 44},
            {"Tanaka", 44},
            {"Aikawa", 66},
        }
    )

    output.Stdoutl("[before]", people)

    // 名前の昇順が第1キー、年齢の降順が第2キーとする
    slices.SortFunc(people, func(x, y Person) int {
        return cmp.Or(
            cmp.Compare(x.Name, y.Name),
            -cmp.Compare(x.Age, y.Age),
        )
    })

    output.Stdoutl("[after ]", people)

    return nil
}

try-golang/examples/basic/cmpop/composite_sort_key.go at main · devlights/try-golang · GitHub

実行すると以下のようになります。

$ task
task: Task "build" is up to date
task: [run] ./try-golang -onetime

ENTER EXAMPLE NAME: cmpop_composite_sort_key

[Name] "cmpop_composite_sort_keys"
[before]             [{Aikawa 21} {Tanaka 22} {Kato 33} {Suzuki 44} {Tanaka 44} {Aikawa 66}]
[after ]             [{Aikawa 66} {Aikawa 21} {Kato 33} {Suzuki 44} {Tanaka 44} {Tanaka 22}]


[Elapsed] 62.25µs

参考情報

https://pkg.go.dev/cmp#example-Or

https://pkg.go.dev/cmp#example-Or-Sort

Goのおすすめ書籍


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

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

Goメモ-406 (最初のゼロ値ではない値を返す)(cmp.Or, go 1.22で追加)

関連記事

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

概要

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

Go 1.22 で、cmp.Or という関数が追加されていたのですね。知らなかったです。

https://pkg.go.dev/cmp#Or

引数に指定した値の中で、最初にゼロ値ではない値を返してくれます。これ便利ですねー。痒いところに手が届く系の関数。

無くても困らないのですが、あるとちょっと処理が楽出来るようになるので便利。

複合キーでソート処理書くときもちょっと楽になりますね。

サンプル

package cmpop

import (
    "cmp"

    "github.com/devlights/gomy/output"
)

// Or は、cmp.Or[T comparable]() のサンプルです。
// cmp.Or は、Go 1.22 で追加されました。
//
// > Or returns the first of its arguments that is not equal to the zero value. If no argument is non-zero, it returns the zero value.
//
// > (Orは、引数のうちゼロ値ではない最初の引数を返す。どの引数もゼロ値ではない場合、ゼロ値を返す。)
//
// comparableが対象となるので、以下に対して利用できる。
//
//   - booleans
//   - numbers
//   - strings
//   - pointers
//   - channels
//   - arrays of comparable types
//   - structs whose fields are all comparable types
//
// # REFERENCES
//   - https://pkg.go.dev/cmp#Or
//   - https://pkg.go.dev/builtin#comparable
//   - https://blog.carlana.net/post/2024/golang-cmp-or-uses-and-history/
func Or() error {
    type (
        V struct {
            Val int
        }
    )
    var (
        ints = []int{0, 0, 99, 1, 0}
        strs = []string{"", "hello", "world", ""}
        objs = []*V{nil, {999}, nil}
    )

    output.Stdoutl("[non-zero]", cmp.Or(ints...))
    output.Stdoutl("[non-zero]", cmp.Or(strs...))
    output.Stdoutl("[non-zero]", cmp.Or(objs...))

    return nil
}

try-golang/examples/basic/cmpop/or.go at main · devlights/try-golang · GitHub

実行すると以下のようになります。

$ task
task: [build] go build .
task: [run] ./try-golang -onetime

ENTER EXAMPLE NAME: cmpop_or

[Name] "cmpop_or"
[non-zero]           99
[non-zero]           hello
[non-zero]           &{999}


[Elapsed] 45.16µs

参考情報

https://pkg.go.dev/cmp#example-Or

https://pkg.go.dev/cmp#example-Or-Sort

Goのおすすめ書籍


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

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

Goメモ-405 (構造体のパディングを可視化)(go-tools/cmd/structlayout)

関連記事

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

概要

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

構造体を定義する際に、うまくアライメントをあわせないとパディングが生じます。

いつも頭で計算してやっていたのですが、以下のようなツールがあることをしりました。

github.com

staticcheckの人がつくっていたんですね。

使ってみたところ、とても便利だったので忘れないようここにメモメモ。。。

サンプル

以下のようなソースを用意。

package main

type A struct {
    V1 uint
    V2 bool
    V3 bool
    V4 int
    V5 int32
    V6 int16
    V7 byte
}

func main() {
}

見たらすぐパディング入るやんって構造してますがw

んで、以下のツールをインストールします。

$ go install honnef.co/go/tools/cmd/structlayout@latest
$ go install honnef.co/go/tools/cmd/structlayout-pretty@latest
$ go install github.com/ajstarks/svgo/structlayout-svg@latest

一番デフォルトな使い方は以下。パッケージ、構造体名と指定します。

$ structlayout app A
A.V1 uint: 0-8 (size 8, align 8)
A.V2 bool: 8-9 (size 1, align 1)
A.V3 bool: 9-10 (size 1, align 1)
padding: 10-16 (size 6, align 0)
A.V4 int: 16-24 (size 8, align 8)
A.V5 int32: 24-28 (size 4, align 4)
A.V6 int16: 28-30 (size 2, align 2)
A.V7 byte: 30-31 (size 1, align 1)
padding: 31-32 (size 1, align 0)

これでも分かるのですが、以下のようにすると、もう少し見やすく表示してくれます。

$  structlayout -json app A | structlayout-pretty
    +--------+
  0 |        | <- A.V1 uint (size 8, align 8)
    +--------+
    -........-
    +--------+
  7 |        |
    +--------+
  8 |        | <- A.V2 bool (size 1, align 1)
    +--------+
  9 |        | <- A.V3 bool (size 1, align 1)
    +--------+
 10 |        | <- padding (size 6, align 0)
    +--------+
    -........-
    +--------+
 15 |        |
    +--------+
 16 |        | <- A.V4 int (size 8, align 8)
    +--------+
    -........-
    +--------+
 23 |        |
    +--------+
 24 |        | <- A.V5 int32 (size 4, align 4)
    +--------+
    -........-
    +--------+
 27 |        |
    +--------+
 28 |        | <- A.V6 int16 (size 2, align 2)
    +--------+
 30 |        | <- A.V7 byte (size 1, align 1)
    +--------+
 31 |        | <- padding (size 1, align 0)
    +--------+

すごく見やすい。また、以下のようにすると svg で出力してくれます。

$ structlayout -json app A | structlayout-svg -t "app.A" > struct.svg

こんな感じ。

素晴らしい。ツール作成してくださった方に感謝です。

参考情報

Goのおすすめ書籍


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

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