2022/03/20


おなじみC/C++から使えるJSONライブラリを紹介するコーナー。まずは過去のまとめ。

C/C++ から扱える JSON ライブラリを数多く紹介してきましたが、パフォーマンスの観点では simdjson が不動の物にしていました。

GitHub - simdjson/simdjson: Parsing gigabytes of JSON per second

Documentation Usage documentation is available: Basics is an overview of how to use simdjson and its...

https://github.com/simdjson/simdjson

がしかし yyjson の登場により、この認識を改めなければならなくなりました。

GitHub - ibireme/yyjson: The fastest JSON library in C

yyjson A high performance JSON library written in ANSI C. Features Fast : can read or write gigabyte...

https://github.com/ibireme/yyjson

まずはこのベンチマーク結果を見て下さい。

benchmark

追記 このグラフは simdjson の DOM API という古い API が使われていた頃の物らしく、ondemand API という最近の API を使うとまた結果が異なる様です。

一部で simdjson の方が勝ってはいるものの、殆どのケースで yyjson が上回っています。

yyjson の特徴は以下の通り。

  • 高速: 最新の CPU で秒間ギガバイトの JSON データを読み書きできる
  • ポータブル: ANSI C(C89)に準拠
  • 標準: RFC8259 標準に厳密に準拠
  • 安全: 完全な JSON 形式、数値形式、UTF-8 バリデーション
  • 精度: int64、uint64、および double の数値を正確に読み書き可能
  • 制限が少ない: 無制限の JSON レベル、\u0000 および null で終了しない文字列をサポート
  • 拡張可能: コメント、末尾のコンマ、nan/inf、カスタムメモリアロケータを許可するオプション
  • 開発者向け: 1個のヘッダファイル1個のCファイルのみ、統合が簡単

ソースの中を見てみましたが、随所で SIMD による最適化が行われています。データ構造も SIMD で扱う為に 64 ビット単位(タイプ、長さ、ペイロードの3つで 128 ビット)にしてあります。

いつもの通りサンプルコードを書いてみました。本ブログサイトのエントリを JSON 形式にした物をパースしてみます。

#include 
#include 

int
main() {
  yyjson_read_err err;
  yyjson_doc *doc = yyjson_read_file("foo.json",
      YYJSON_READ_NOFLAG,
      NULL,
      &err);
  if (!doc) {
    fprintf(stderr"%s\n", err.msg);
    exit(1);
  }

  yyjson_val *root = yyjson_doc_get_root(doc);

  yyjson_val *title = yyjson_obj_get(root, "title");
  printf("title: %s\n", yyjson_get_str(title));

  yyjson_val *entries = yyjson_obj_get(root, "entries");

  size_t idx, max;
  yyjson_val *entry;
  yyjson_arr_foreach(entries, idx, max, entry) {
    title = yyjson_obj_get(entry, "title");
    printf("%s\n", yyjson_get_str(title));
  }

  yyjson_doc_free(doc); 
  return 0;
}

直感的で parson や jannson を使った事がある方であればどんな API かすぐに分かると思います。

ライセンスも MIT ですので業務で使う事もできますね。

Posted at by



2022/01/27


動的型付けプログラミング言語や、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



2022/01/08


技術評論社様、執筆者の皆様、献本ありがとうございました。

エキスパートたちのGo言語 一流のコードから応用力を学ぶ Software Design plus エキスパートたちのGo言語 一流のコードから応用力を学ぶ Software Design plus
上田 拓也, 青木 太郎, 石山 将来, 伊藤 雄貴, 生沼 一公, 鎌田 健史, 上川 慶, 狩野 達也, 五嶋 壮晃, 杉田 寿憲, 田村 弘, 十枝内 直樹, 主森 理, 福岡 秀一郎, 三木 英斗, 森 健太, 森國 泰平, 森本 望, 山下 慶将, 渡辺 雄也
技術評論社 Kindle版 / ¥3,212 (2021年12月27日)
 
発送可能時間:

本誌のサブタイトルは

入門書では得られないノウハウはコードにある

です。

ちなみに筆者も似た様な書籍「みんなの Go 言語」を共著で執筆しましたが、本書はこちらの拡充版とも言えるでしょう。

改訂2版 みんなのGo言語 改訂2版 みんなのGo言語
松木 雅幸, mattn, 藤原 俊一郎, 中島 大一, 上田 拓也, 牧 大輔, 鈴木 健太
技術評論社 Kindle版 / ¥2,350 (2019年08月01日)
 
発送可能時間:

「エキスパートたちのGo言語」は20名もの共著で色々なノウハウが沢山掲載されています。「みんなの Go 言語」は7名の共著で個々の特色を色濃く出せたと思いますので、どちらも良い作品だと思います。

昨今の Go 言語は、日々ライブラリが更新され、新しいライブラリが生まれ、トレンドが更新され、古いライブラリが使われなくなるという、細胞が角質となり剥がれ落ちまた新しい細胞が生まれる様な循環を繰り返しています。

その様な中でこういった作品を出すという事は、言ってみればリスキーな事になり得ますし、「みんなの Go 言語」を書いた際にも感じていました。しかしこういった情報をきちんと纏まった形で公開する事は非常に大切で、体系立てて読める事はとても素晴らしい事だと再認識しました。

本書の読者レベルで言うと、Go の基本的なコードが読み書き出来て、ライブラリの選定もある程度はできる、中級者くらいかなと思います。前述の様に最近の Go はツールやライブラリの入れ替わりが速い状況ですので、開発者にキャッチアップの能力が必要になります。

  • ツールやライブラリのトレンド
  • CI/CD 周りの扱い方
  • セキュリティfixのキャッチアップ
  • AWS/GCP などクラウドのトレンド

これら何れも Go 言語に限った話ではないのですが、Go 言語の場合はクラウドで使われる事が多く、自動化もしやすい為、特にツール類や CI/CD 周りに気を使わないといけない事が多いです。本書ではそういった内容を「2021 年の Go 言語」としてうまく書かれているなと思いました。

例えば通常の言語ではあまり気にされないかもしれない「リトライ処理」、クラウドで使われる事を意識してずいぶん慎重に扱われます。バックオフを意識したライブラリだけでも沢山公開されています。本書の中でもリトライ処理だけで10ページも割かれています。context の使い方やリトライ処理といった、基本的な使い方だけでなく、cgo を扱う際のノウハウ、GitHub Actions を扱う上でのノウハウ、ウェブアプリを作る際に必要な JWT/OAuth といった認証周り、WebAssembly など、色々なカテゴリで書かれています。

興味のある方は、ぜひ一度、目次とその章で割かれているページ数を参照されるのが良いと思います。1点、牽引が少ない気はしました。もしかしたら改訂版などで改良されるかもしれないですね。

本書の内容に関して、全て知ってはいなくても良いと思いますが、読んで「あー、あれね」くらいで理解が出来れば Go 言語のエキスパートと呼ばれても良いのではないでしょうか。Go 言語を使ってお仕事をされておられる方であれば、買って間違いなしの書籍だと思います。

Posted at by