SlabアロケータとRust
Rustのslab crateについての解説と、Boxとのベンチマーク
Rustで高速なソフトウェアを書こうとしたときに、メモリアロケーションのオーバーヘッドを削りたくなるのは誰でも抱く感情だと思う。
高速アロケーションを実現するクレートの代表にslabがある。Boxを使うか迷ったため簡単なベンチマークを取った。
実験
4種類のアクセスパターンで比較。実装はSlab、Slab(ウォームアップなし)、Box、bufpoolの四種類。
bufpoolは自作のアロケータで、Boxで確保したバッファへのポインタをSlabで管理する。Slabの高速なスロット管理とBoxのアドレス安定性を両立させる狙いがある。
LIFO (Last In, First Out)
スタック的なパターン。最後に確保したものを最初に解放。
stack = []
for i in 0..BATCH_SIZE:
stack.push(alloc())
while stack is not empty:
dealloc(stack.pop()) # 逆順
FIFO (First In, First Out)
キュー的なパターン。最初に確保したものを最初に解放。
queue = []
for i in 0..BATCH_SIZE:
queue.push(alloc())
for ptr in queue: # 順番通り
dealloc(ptr)
Random
ランダムにalloc/deallocを混ぜる。スロットを選んで空ならalloc、埋まっていればdealloc。
slots = [None] * BATCH_SIZE
for i in 0..(BATCH_SIZE * 2):
idx = random(0, BATCH_SIZE)
if slots[idx] is not None:
dealloc(slots[idx])
slots[idx] = None
else:
slots[idx] = alloc()
# 残りを解放
for ptr in slots:
if ptr is not None:
dealloc(ptr)
結果
下に合計実行時間を示す。BATCH_SIZEは100。バッチの実行回数は。
Boxは一定のバイト数まで一定の時間なのに対して、Slabアロケータは確保容量が小さいほど高速である。そしてSlabはウォームアップした方が明らかに速い(当然だが)。基本的には適切にSlabの初期容量を定め、小さな(256 byte以下)容量の確保であればSlabを使った方が高速だし、逆に大きなバッファになってきた場合もSlabの方が高速。素直なパフォーマンスと言える。部分的にBoxが勝っている場合もあるが、基本的には使えるならSlabを使った方がいいと言える。
Intelの方も見てみよう。安定はしないものの大まかな傾向は同じなので、アーキテクチャには大きく依存しないことがわかる。
M1Max、LIFO、256バイトでのレイテンシも見てみよう。
まとめ
Slabの方が基本的に速いし素直な特性をしている。
実験に使ったコードは https://github.com/namachan10777/memalloc-bench に置いてある。