SDS文字列の共有

異なるデータ構造の間で同じSDS文字列を共有すると有利なプログラムでは、文字列の参照数を記録する構造体へSDS文字列を格納し、参照数を増減する関数を用意することを強く勧めます。

これは参照カウントと呼ばれるメモリー管理手法で、SDSでは次の2つの利点があります。

  • SDS文字列を解放し忘れたり、解放済みの文字列を再び解放したりすることによる、メモリーリークやバグが起きにくくなります。
  • SDS文字列を変更したとき、新しい文字列が別のメモリー位置を指す場合でも、すべての参照を更新する必要がなくなります。

これは非常によく使われるプログラミング手法ですが、ここでは基本的な考え方を説明します。次のような構造体を作ります。

struct mySharedStrings {
    int refcount;
    sds string;
}

新しい文字列を作成するときは、構造体を確保し、refcountを1に設定して返します。そして、共有文字列の参照カウントを変更する2つの関数を用意します。

  • incrementStringRefCountは、構造体内のrefcountを1増やすだけです。新しいデータ構造や変数などへ文字列の参照を追加するたびに呼び出します。
  • decrementStringRefCountは参照を削除するときに使います。この関数には特別な動作もあり、refcountが0になると、SDS文字列とmySharedString構造体を自動的に解放します。

ヒープ検査ツールとの関係

SDSは、mallocで確保したメモリー領域の途中を指すポインターを返すため、ヒープ検査ツールで問題になる可能性があります。ただし、次の点に留意してください。

  • よく使われるValgrindは、SDS文字列をpossibly lost(失われた可能性がある)メモリーと判定し、definitely lost(確実に失われた)とは判定しません。そのため、リークの有無を見分けやすくなります。私はRedisでValgrindを何年も使ってきましたが、実際のリークはすべて一貫して「definitely lost」と検出されました。
  • OSXの計測ツールは、SDS文字列をリークとは判定せず、メモリー領域の途中を指すポインターを正しく扱えます。

システムコールからのゼロコピー追加

ここまでで、ソースコードを読んでSDSライブラリーの内部をさらに調べるための道具はそろいました。ただし、公開されている低水準APIを使った興味深いパターンがあります。Redisでは、ネットワーク処理の性能を高めるために使っています。

sdsIncrLen()とsdsMakeRoomFor()を使うと、次の方式を組み立てられます。カーネルから来るバイトを中間バッファーへコピーせず、SDS文字列の末尾へ追加します。

oldlen = sdslen(s);
s = sdsMakeRoomFor(s, BUFFER_SIZE);
nread = read(fd, s+oldlen, BUFFER_SIZE);
... check for nread <= 0 and handle it ...
sdsIncrLen(s, nread);

sdsIncrLenの説明は、sds.cのソースコード内にあります。