November 22, 2019

English | 日本語

Publishing Rust module in open source at crate.io (日本語)

Rust の module (crate) で spin-sync を公開しました。

spin-sync はスピンロックを使っている事以外は std::sync のように振る舞います。(Wikipedia Spinlock)

使っていただけると、嬉しいです。

どういう時に使うのか?

主にロックのバイト数を減らしたい時だと思っています。

私の PC (x86_64 Linux) では std::sync::Mutex は 1 インスタンスあたり 40 byte 消費します。 しかし、スピンロックは普通 1 byte しか使いません。(spin-sync もそうです。)

一般的に、多くのオブジェクトで一つの排他ロックを共有するとロック競合が多くなるので、排他ロックは細かく分ける方が良いと言われています。。 しかし、排他ロックの数を多くしすぎるとメモリ消費量も増える可能性があります。 そんな時には、排他ロック一個あたりのサイズが重要になるのではないでしょうか?

例えばデータベースを作成する場合。

データベースが内部でキャッシュを持つ事は良くあります。今、キャッシュされた各アイテムの平均サイズが 160 byte であると仮定しましょう。 キャッシュされたアイテムがそれぞれ排他ロックを持つには、どれだけのメモリが必要でしょうか?

各排他ロックのサイズが 40 byte であるとすると、このロックだけでキャッシュ用のメモリの約 20 % を消費することになります。 しかし排他ロックのサイズが 1 byte であるならば、ロックのメモリ消費量ははキャッシュ用メモリの 1 % 未満に抑えられます。

なぜ、わざわざ新しい crate を作成したのか?

似たような crate はすでに公開されています。 例えば spin はすでに多くの実績があります。

しかし、 spin は少し危険に思えました。

spin のインターフェースは std::sync と異なる

std::sync と異なり, spin は "poisoning" と呼ばれる panic をスレッド間で伝搬する仕組みを実装していません。

これ自体は大きな問題では無いと思います。 Poisoning が役に立つ事はまれでしょう。

重要な事は、これによりインターフェースが変わってしまった事です。

例えば std::sync::Mutex::lock(&self) の戻り値は Result です。 mutex 自体が汚染された時にエラーを返すためです。しかし、 spin::Mutex::lock(&self) は汚染によるエラーが発生しないので Result で戻り値をラップしません。

そのため、std::sync を使ったコードを spin で置き換える改修にはひと手間かかってしまいます。

個人的には、絶対にエラーを返さないとしても Result でラップしても良かったと思うのですが……

排他ロック周りはテストが簡単とは限らないので、 std::sync の知見を流用するためにもインターフェースを揃えた物を使いたいです。

(ちなみに、 spin-sync は "poisoning" を実装済みで spin-sync::Mutex::lock(&self) の戻り値は Result です。)

spin のガードは !Sync を実装していない

これはちょっと深刻。 Rust のセーフティーシステムを壊しかねない

例えば std::sync::MutexGuard!Sync を実装しているのに対し、 spin::MutexGuardSync を実装しています。

もちろん、 Sync!Sync は正反対の意味です。

なぜこのような仕様になったのか私には良くわかりませんが、もしかすると nightly [1] toolchain [2] が必要だったからかもしれません。 Rust は推論により Sync などの一部の trait を自動実装する事があります。(spinSync を実装しているのは、このため)

この機能を止めるには negative trait [3] を明示的に実装する必要があり、 negative trait を実装するには現在の所 nightly toolchain が必要になってきます。(std library は除く)

!Sync により、 Rust のコンパイラーはある種のバグを検知します。 spin はこの機能を止めてしまっています。 プログラマーがこの事を常に認識できるとは限らないと思うのですが…

(spin-sync!Sync を実装しています。そのため、現在の所ビルドには nightly toolchain が必要です。)

パフォーマンスはどうでしょう?

一般的に、スピンロックは高速であると言われています。 spin-sync も多くの場合 std::sync::Mutex よりも高速でしょう。

spin と比べると spin-sync は poisoning を実装しているので僅かに遅い気がしますが、大差は無いと考えています。