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のソースコード内にあります。