動的型付けプログラミング言語や、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 を出力するだけであれば試してみては如何でしょうか。