2022/01/27

Recent entries from same category

  1. Go 言語プログラミングエッセンスという本を書きました。
  2. errors.Join が入った。
  3. unsafe.StringData、unsafe.String、unsafe.SliceData が入った。
  4. Re: Go言語で画像ファイルか確認してみる
  5. net/url に JoinPath が入った。

動的型付けプログラミング言語や、class を持つようなプログラミング言語を使う場合、そのフィールドメンバの定義順などを意識する事は少ないかもしれません。

Go は struct という、C言語が持っている構造体と同じ様にメモリ配置され、構造体そのものをレシーバとして、メソッドを定義しつつプログラミングを行います。struct はデータ構造そのものを示し、それが大きくなるにつれ、当然ながらメモリ使用量も多くなります。

さて、例えば以下のコードを実行すると、struct 100 個分のサイズは幾らになるでしょうか。

package main

import (
    "fmt"
    "unsafe"
)

type Account struct {
    TimeZone struct {
        Name       string `json:"name"`
        UtcOffset  int    `json:"utc_offset"`
        TzinfoName string `json:"tzinfo_name"`
    } `json:"time_zone"`
    Protected                bool   `json:"protected"`
    ScreenName               string `json:"screen_name"`
    AlwaysUseHTTPS           bool   `json:"always_use_https"`
    UseCookiePersonalization bool   `json:"use_cookie_personalization"`
    SleepTime                struct {
        Enabled   bool        `json:"enabled"`
        EndTime   interface{} `json:"end_time"`
        StartTime interface{} `json:"start_time"`
    } `json:"sleep_time"`
    GeoEnabled                bool   `json:"geo_enabled"`
    Language                  string `json:"language"`
    DiscoverableByEmail       bool   `json:"discoverable_by_email"`
    DiscoverableByMobilePhone bool   `json:"discoverable_by_mobile_phone"`
    DisplaySensitiveMedia     bool   `json:"display_sensitive_media"`
    AllowContributorRequest   string `json:"allow_contributor_request"`
    AllowDmsFrom              string `json:"allow_dms_from"`
    AllowDmGroupsFrom         string `json:"allow_dm_groups_from"`
    SmartMute                 bool   `json:"smart_mute"`
    TrendLocation             []struct {
        Name        string `json:"name"`
        CountryCode string `json:"countryCode"`
        URL         string `json:"url"`
        Woeid       int    `json:"woeid"`
        PlaceType   struct {
            Name string `json:"name"`
            Code int    `json:"code"`
        } `json:"placeType"`
        Parentid int    `json:"parentid"`
        Country  string `json:"country"`
    } `json:"trend_location"`
}

func main() {
    var accounts [100]Account
    fmt.Println(unsafe.Sizeof(accounts))
}

実行すると 22400 (22kb) と表示されます。前述の様に、Go の struct はC言語と同じ様にメモリ上に配置されます。したがって、フィールドの配置順を気を付けるだけでメモリを節約できる事になります。以下のコマンドを実行して fieldalignment をインストールして下さい。

go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest

struct が定義されているソースファイルを引数に指定するか、以下の様に実行して下さい。

fieldalignment .

すると以下の様に表示されます。

.../main.go:8:14: struct of size 224 could be 192
.../main.go:9:11: struct with 32 pointer bytes could be 24
.../main.go:18:27: struct with 40 pointer bytes could be 32
.../main.go:32:30: struct with 96 pointer bytes could be 72

一般的に実行バイナリを生成するプログラミング言語は、データをメモリに載せる際にアクセスしやすいようにワードサイズ(コンピュータがメモリを読み書きする際の単位)の倍数の位置にデータを配置します。このためワードサイズに満たないフィールドの直後に、パディングという隙間を意図的に挿れることで調整しています。これにより上記の様に改善可能な空白が生まれてしまいます。

fieldalignment コマンドはフィールドの定義順を変える事で、メモリ上のアライメントにうまく配置できる様にするツールです。fieldalignment に -fix フラグを付けて実行すると、実際に struct が書き換えられます。

fieldalignment -fix .

書き換えられたソースコードは以下の通り。

package main

import (
    "fmt"
    "unsafe"
)

type Account struct {
    SleepTime struct {
        Enabled   bool        `json:"enabled"`
        EndTime   interface{} `json:"end_time"`
        StartTime interface{} `json:"start_time"`
    } `json:"sleep_time"`
    TimeZone struct {
        Name       string `json:"name"`
        UtcOffset  int    `json:"utc_offset"`
        TzinfoName string `json:"tzinfo_name"`
    } `json:"time_zone"`
    AllowDmGroupsFrom       string `json:"allow_dm_groups_from"`
    ScreenName              string `json:"screen_name"`
    AllowDmsFrom            string `json:"allow_dms_from"`
    AllowContributorRequest string `json:"allow_contributor_request"`
    Language                string `json:"language"`
    TrendLocation           []struct {
        Name        string `json:"name"`
        CountryCode string `json:"countryCode"`
        URL         string `json:"url"`
        Woeid       int    `json:"woeid"`
        PlaceType   struct {
            Name string `json:"name"`
            Code int    `json:"code"`
        } `json:"placeType"`
        Parentid int    `json:"parentid"`
        Country  string `json:"country"`
    } `json:"trend_location"`
    GeoEnabled                bool `json:"geo_enabled"`
    DiscoverableByMobilePhone bool `json:"discoverable_by_mobile_phone"`
    DisplaySensitiveMedia     bool `json:"display_sensitive_media"`
    UseCookiePersonalization  bool `json:"use_cookie_personalization"`
    AlwaysUseHTTPS            bool `json:"always_use_https"`
    Protected                 bool `json:"protected"`
    SmartMute                 bool `json:"smart_mute"`
    DiscoverableByEmail       bool `json:"discoverable_by_email"`
}

func main() {
    var accounts [100]Account
    fmt.Println(unsafe.Sizeof(accounts))
}

実行すると struct 100 個のサイズは 19200 (19kb) になりました。struct 内のフィールドの定義順に意味がある場合には使えませんが、単に JSON を出力するだけであれば試してみては如何でしょうか。

Posted at by