blog.8-p.info

Message Passing という共著ブログをはじめました。karino2 さんは「内輪のブログ的な何か」と書いているけれど、まあブログと言い切ってしまって良いんじゃないか。RSS もあるし。

という話をセコンさんにしたら、はてなグループみたいですね、という話になった。私は Gemini の CAPCOM や、Gopher コミュニティのアグリゲーター周辺の、みんながお互いのブログ1を比較的読んでいて、時折言及しあう感じは良いなと思っていて、Message Passing は同じような小さなコミュニティ感がある。はてなアンテナより以前の、個人よりはコミュニティの色の強かった頃のアンテナにも同様のコミュニティ感があった。

こういう場所は、ちょっとインターネットからはパッと見つからなくなっている。Twitter は、私にはちょっと速すぎるし、短文を共有しあって伝えられることには限界がある。複数人で編集されている Scrapbox には結構面白いものがある。ブログをやって、トラックバックの代わりに Webmention を飛ばしあえばいいのかもしれないけど、うーん、これはあまりにトラックバックすぎて、今回はうまくいくと思える根拠が、自分にはあまりないのだった。


  1. Blog は Weblog の略称なので、Gemini では Gemlog, Gopher では Phlog と呼ばれることが多い。 ↩︎

Firecrackerはコンテナランタイムなのかという話への返信および補足です。「コンテナラインタイムではない」という結論は

このユースケースにおけるFirecrackerの立ち位置は、「runCをコンテナごとに隔離するための環境を作成」するためのVMプロバイダーになっている。

すでに出ているんですが、色々と補足したいことがあったので。

Firecracker は、コンテナユーザなら誰もが使っているランタイム「runc」を俯瞰する で定義されているような

ランタイムの実装には様々なものがありますが、それらは役割に応じて下図に示すように高レベルランタイム(CRIランタイム)と低レベルランタイム(OCIランタイム)という2つのレイヤに分類されます。

KubernetesContainer Runtime Interface (CRI) を実装した高レベルランタイムや、Open Container InitiativeRuntime Specification を実装した低レベルランタイムではありません。

Firecracker は、公式ページ にもあるとおり

Firecracker is a virtual machine monitor (VMM) that uses the Linux Kernel-based Virtual Machine (KVM) to create and manage microVMs.

KVM を使ったバーチャルマシンモニタで、ひらたくいうと、Linux カーネル上で、別の Linux カーネル + ユーザーランドを実行するためのソフトウェアです。厳密には Linux カーネルだけではなく OSv もゲストとして実行できます。

firecracker-containerd

firecracker-containerd は、そんな Firecracker を containerd から使うためのプロジェクトで、

  • containerd そのものに CreateVM などの VM むけの API を追加する firecracker-control プラグイン
  • containerd に既存の「コンテナを作る」といった API の裏で、Firecracker を呼び出すためのランタイム (shim)
  • Firecracker の中で実際に起動するゲストの Linux と、その中で動くエージェント

などで構成されています。なお、ここでの「ランタイム」は、CRI ランタイムとも、OCI ランタイムとも関係のない、containerd が規定する API を実装した小さなバイナリです。紛らわしいのと shim と呼ばれることもあるので、以下文中では shim で統一します。

containerd は、コンテナが起動されると同時に、この shim を立ち上げます。systemd などから起動されて、通常はホストのシャットダウンまで動き続けるデーモンである containerd に対して、shim はコンテナの起動と終了に対応して作成/破棄される比較的短命なプロセスです。

firecracker-containerd では、この shim から新規 VM の作成などを暗黙のうちに行うことで、外側からはホスト上で動いているコンテナに見えるけれど、実際にはホストの Linux とは別の、Firecracker 上で動くゲストの Linux で動作するコンテナ、というものを作り出しています。なお、コンテナ:VM は必ずしも 1:1 ではなく、1つの VM に複数のコンテナを同居させることも可能です。

ちなみに Kata Containers も、Overview に containerd-shim-kata-v2 について言及があるので、OCI ランタイム (低レベルランタイム) というよりは shim の一種といったほうが正確だと思います。

CRI

containerd 自体は CRI プラグイン込みでビルドできるので、firecracker-containerd を CRI ランタイムにすることもできるはずです。 いま master にある Makefile は CRI 抜きでビルドしているので「はず」なんですが、最近 containerd の master が Go モジュールを使うようになったので、CRI つきでビルドするのも昔ほど大変ではないはず。

ベスト オブ 2020

Dec 8, 2020

新型コロナウィルスの影響で、毎日の通勤をやめ、毎年の帰国をあきらめ、家族の結婚式が延期になった年だった。

リモートワーク

3月のはじめからずっと家で働いていた。通勤時間がなくて、昼食を家族でとれて、たまに怒っている子供をなだめたりできるのは良い。オフィスというのは、完全に家族を視界から排除して働ける仕組みなんだなと関心した。それで生産性は上がるんだろうけど、その向上が、最終的に自分にプラスとしてかえってくる感じはあまりしない。

リモートワークはいつか試したいことのひとつだったので、それができたのは良かった。

#100DaysToOffload

「一年で個人ブログを100回更新する」という #100DaysToOffload に賛同/参加して、英語ブログをたくさん更新した。

karino2 さんが ブログを書き続ける能力 (2019) でいっているように、

ブログをある程度コンスタントに書き続ける、というのは、ブログを書く能力の一部な気がする。 書いてないと失われる、というだけでなく、書いている事で続けられるようになる、みたいな部分がある。 書きたい事がある時だけ書く、と思って最初だけなんか気合入れて書いて、以後別段書く事無い、というのは失敗パターンだよな。 書き続ける事が必要だという事を知ってる、というのはブログを書く能力の一つに思う。

書き続けてみるとなんだか続くもので、もう 74/100 まで続いている。

日本では、cho45 さんが CGMサービスの矜持について (2016) で書いていた、

まぁ結局、ノウハウ蓄積みたいなコンテンツってASP型で預けるのは不安がありすぎるので、自力で配信しようねみたいなウェブ縄文時代みたいな話になるんですが……

「ウェブ縄文時代」が4年越しでちょっとしたブームになっていて、まあブームというには自分とあまり年の離れていない数人しか参加していない気がするけれど、これも良かった。

音楽

冒頭に書いた話と少し矛盾するけれど、周りの音を遮断したいときに BGM として音楽を聞くことは、オフィスにいるときよりも増えたと思う。#100DaysToOffload でも10回に1回は音楽について書いていたのも手伝って、一年を通して色々と聞いた印象がある。

去年に書いた tricot は、メジャーデビューしてから Topshelf Records で買えなくなてしまって残念、という話をブログに書いたら Twitter で Topshelf Records 自身から返信が来て、なかなかインターネットみがあった。

本は、読んでいないわけではないんだけど、一年を通して印象に残ったものはなし。偶然の気もするし、バスで通勤しなくなって、本を読む時間が減ったせいのようにも思う。

インターネット

Kicks Condor は Web 2.0 以前のブログ黎明期を彷彿とさせるサイトで、夏頃に突然「消えて」しまって、why the lucky stiff みたいになるのかと思ったけど、無事に帰ってきて安心した。

どこから見ればいいのかわからないときは、

このあたりからどうぞ。

おわりに

この記事は 2020 Advent Calendar 2020 の8日目です。参加は2回目で、去年のベストもあります。7日は ooooooooo さんでした。明日はサイトウナヲキさんです。

コロナウィルス定点観測です。

仕事

相変わらず自宅で働いている。

買い物

ついに自家用車を買った! 私はまだ免許をとれていなくて、妻が主に運転している。もうしわけない。

結果として、私が週に一回近所の QFC に買いにいくことはなくなって、週末などに家族全員で Fred Meyer や Trader’s Joe に行くことが多くなった。前者は広さもあってか特に入場制限はされていない。後者は入場制限があって、行列に数分並ぶことになる。

学校

子供が行っている学校のひとつでコロナウィルスの感染があった。自分の子供は行っていない曜日で、かかっていたのは先生1子供1ですんだみたいだけど、数日間学校がしまって大変だった。いまはもう再開している。

ニュース

特に追っているものはなし。

政治

大統領選が終わり、Joe Biden が大統領になった。Donald Trump は個人的には好きにはなれない大統領だったので、良かった。

一方で、自分が生まれた国が、たまたま「移民国家」というのは、大変な話だと思う。自分の母国語が比較的単純で、かつ学ぶときの費用対効果が高くて、国家としても移民をうけいれることをよしとしていて、進学から就職までいわゆる外国人と常に競争しなくてはいけない生活というのは、きびしい。

私は、色々あって、そういう環境を許容可能なものとして来ている立場だけど、それも「どうしようもなくなったら日本に帰ろう」というセーフティーネットが支えになっているところがある。競争から降りる自由がない状態で、移民国家に生まれた人が、ある種の不満を持つのは否定できないなと思う。

Ruby の Hash 実装

Nov 19, 2020

Rust には aHash という、x86 系の新しい CPU についている AES 暗号のための命令を使った、高速なハッシュ関数がある。これは hashbrown という Rust 向けの SwissTable 実装ではデフォルトになっていて、hashbrown は Rust 本体の HashMap の実装でもあるんだけど、そちらでは SipHash 1-3 がハッシュ関数として使われている。

という話を、英語ブログの方に書いた後に、ふと Ruby はどうなっているんだっけ、と思ったので調べてみた。

昔に趣味で Ruby, 仕事で Perl を書いていたころは「CRuby のソースコードはまあ読めるよねえ。でも Perl は謎のマクロが多くてまじできつい。」と思っていたんだけど、2020年に CRuby のソースコードを読むと、全体的にコメントが少ないのと、とりわけ昔のコミットのコミットメッセージが短いので、結構きつかった。

ハッシュマップの実装には2つの軸がある

ハッシュマップの実装には、ハッシュ関数と、キーと値のペアをどう格納するかという、2つの直交する軸がある。冒頭で出てきた aHash はハッシュ関数だし、SwissTable はキーと値のペアをどう格納するか、という軸になる。

CRuby の場合は、ハッシュ関数は SipHash 1-3, キーと値のペアは open addressing 系の実装を使っている。それでは、実際の実装を見てみよう。

siphash.c

CRuby の SipHash 1-3 は、そのまま siphash.c の中に、sip_hash13() という名前で定義されている。なお SipHash 1-3 が導入されたのは2017年のことで、初めて SipHash が導入された2012年には、SipHash 2-4 が使われていた。

ちなみに、この SipHash 導入時の最初のコミットメッセージは

siphash

  • random.c (rb_memhash): use siphash.

これだけ。3行!

ここで定義された sip_hash13() は、random.c にある rb_memhash() でラップされて

st_index_t
rb_memhash(const void *ptr, long len)
{
    sip_uint64_t h = sip_hash13(hash_salt.key.sip, ptr, len);
#ifdef HAVE_UINT64_T
    return (st_index_t)h;
#else
    return (st_index_t)(h.u32[0] ^ h.u32[1]);
#endif
}

各地で使われている。

ちなみに、SipHash 導入以前の rb_memhash() は MurmurHash で、それより前の Ruby 1.8 時代にはもっと簡単なハッシュ関数だったらしい。Ruby 1.8 時代に HashDoS 攻撃 を回避しようとした経緯は HashDoS脆弱性との戦い! Rubyコミッター・卜部昌平が明かすプログラム堅牢化のノウハウ (2018) にまとまっている。

hash.c

CRuby の Hash クラスの実装は hash.c にある、けれど全体的に Ruby 向けのメソッドを定義したりしているところが多くて、肝心のハッシュマップとしてのデータ構造はあまり見えてこない。というのも、CRuby の Hash クラスに対応する C 側の構造体である RHash は、実際には st_table という別の構造体の、薄いラッパーとして定義されている。

struct RHash {
    struct RBasic basic;
    union {
        st_table *st;
        struct ar_table_struct *ar; /* possibly 0 */
    } as;
    const VALUE ifnone;
    union {
        ar_hint_t ary[RHASH_AR_TABLE_MAX_SIZE];
        VALUE word;
    } ar_hint;
};

コメント…

CRuby のような言語処理系では、ハッシュマップは言語処理系そのものでも良く使われる。CRuby はセルフホスティングしていない (処理系自体は Ruby ではなく C で書かれている) ので、C から使いやすいハッシュマップを作るのは必要不可欠だったんだろう。

st.c

st.c には、他のファイルたちとちがって、比較的コメントがある。

/* This is a public domain general purpose hash table package
   originally written by Peter Moore @ UCB.
   The hash table data structures were redesigned and the package was
   rewritten by Vladimir Makarov <vmakarov@redhat.com>.  */

/* The original package implemented classic bucket-based hash tables
   with entries doubly linked for an access by their insertion order.
   To decrease pointer chasing and as a consequence to improve a data
   locality the current implementation is based on storing entries in
   an array and using hash tables with open addressing.

クレジットされている Vladimir Makarov さんは、RedHat のブログに Towards Faster Ruby Hash Tables (2017) として経緯をまとめている。

Modern processors have several levels of cache. Usually, the CPU reads one or a few lines of the cache from memory (or another level of cache). So CPU is much faster at reading data stored close to each other. The current implementation of Ruby hash tables does not fit well with modern processor cache organization, which requires better data locality for faster program speed.

おまけ: その他のハッシュ関数

st.c には、実は MurmurHash も残っている。

/* This hash function is quite simplified MurmurHash3
 * Simplification is legal, cause most of magic still happens in finalizator.
 * And finalizator is almost the same as in MurmurHash3 */
#define BIG_CONSTANT(x,y) ((st_index_t)(x)<<32|(st_index_t)(y))
#define ROTL(x,n) ((x)<<(n)|(x)>>(SIZEOF_ST_INDEX_T*CHAR_BIT-(n)))

さらに FNV まである。

NO_SANITIZE("unsigned-integer-overflow", PUREFUNC(static st_index_t strcasehash(st_data_t)));
static st_index_t
strcasehash(st_data_t arg)
{
    register const char *string = (const char *)arg;
    register st_index_t hval = FNV1_32A_INIT;

    /*
     * FNV-1a hash each octet in the buffer
     */
    while (*string) {
	unsigned int c = (unsigned char)*string++;
	if ((unsigned int)(c - 'A') <= ('Z' - 'A')) c += 'a' - 'A';
	hval ^= c;

	/* multiply by the 32 bit FNV magic prime mod 2^32 */
	hval *= FNV_32_PRIME;
    }
    return hval;
}

Ruby は、外部からの入力をうけつけない (e.g. 変数の名前) みたいなところでは、これらの高速だけど暗号学的ではないハッシュ関数を使っている。