2022/03/24


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

もともとデータ解析というか機械学習に幾らか興味があり、このブログや Zenn 等でも幾らか記事を書いてきたのですが、R 言語に関してはほぼほぼノータッチで、たまに遊んだりはしていましたが使えるというレベルではありませんでした。

以前から vim-lsp-settings という、Vim からあらゆる Language Server をインストールできるプラグインを作っており、そこで R の Language Server を試したところ、Windows で動かないバグを見付けパッチを送った際に少し触ったのが初めて R を触った体験です。このパッチがマージされたのをきっかけに少しだけ R を触る様になりました。

おそらく多くの皆さんも、R がどういった言語でどれくらい成熟しているのかはご存じないかもしれません。かくいう僕も今回 R を触って改めて驚いたのですが、R はかなり成熟しており、エコシステムはもちろん、周辺ツールや IDE、ライブラリを作る為の仕組み等も既に一通り揃った、ほぼほぼ完成したプラットフォームなのです。特に驚いたのが Windows への対応力です。僕の経験上、失礼ながらマイナーなプログラミング言語は Windows での動作が未保障でひどければ非サポートという状況が一般的で、うまく動いても手直しが必要というのが経験則だったのですが、R に至ってはほぼ何も手を加える事無く、いろいろなライブラリが動作します。

そんな中で本書を手に取り、色々なサンプルを1つずつ試しながら読んだのですが、一言で言えば「データ解析は奥が深い」です。そしてもう1つ「見せ方次第でデータは変わる」とも感じました。もちろんデータそのものが変わる訳ではありませんが、受け取る側の印象はまったく変わってくると思いますし、その為に必要なデータ加工のスキルはとても重要だと思いました。そして抽出されたデータを加工し、グラフ等で表現する一連のワークフローを行う際には、R は強い味方になってくれる事も理解できました。

こういったデータ解析はもちろん Python でも同じ様な事はできるのですが、R の IDE である RStudio のデキがかなり良く、おそらく巷にあるどのデータ解析 IDE よりも良く出来ているだろうと思いました。僕は R 言語はド素人な訳ですが、僕と同じ様に R 言語をビギナーから始めたい人には、初手から詳しい説明が書かれている本書はとても便利でありがたい1冊だと思いますし、R に慣れるまではしばらく近くに置いておきたい本になると思います。

Posted at by



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