Island Life

< ピアノレッスン118回目 | ピアノレッスン119回目 >

2014/02/12

文字列の部分共有

JavaのString実装が変更になった話。以前はsubstringは部分文字列を 共有してたけど、でかい文字列の一部だけを参照しつづけるような使い方だと メモリリークが問題になるってことで、部分文字列共有をやめたそうだ。

Gaucheも部分文字列を共有する実装なんでこれは気になる。

Cとのやりとりを置いとくなら、文字列のフラグメントを木にして管理するのが 良いとは思うのだけれどね。そうすれば

  • 文字列接合もO(1)になる
  • substringは部分木もしくはフラグメントを共有するだけなので、余分なメモリをretainしない
  • 木のノードに文字オフセットを持たせておけば、マルチバイト文字列のインデックスアクセスが現在のO(n)からO(log n)になる

わりと良いことづくめ。ただ、Cに渡す時に木をフラットな文字配列に直さないと ならないので、Cを頻繁に呼び出す時のオーバヘッドが大問題。

一度フラットな配列にしたらそれをキャッシュしておくことはできるけれど、 その場合、C呼び出しに使った文字列は2倍のメモリを消費し続けることになる。

妥協案として、フラットな配列の方への参照をweakにしておくという手はあるかも。 それならもう使わないキャッシュはいずれ回収される。

かなり大きな変更になるし、性能評価してみないと何とも言えないから、 やるとしたら1.0以降かなあ。

Tag: Gauche

Past comment(s)

通りすがりです (2014/02/13 01:52:06):

メリットの3つめとして挙げられている点についてですが、 通常の配列を使った文字列ならアクセス時間はO(1)なのでは?

shiro (2014/02/13 04:58:52):

まあ、Cのchar*に渡す時にどうせ変換が必要ならコードポイント配列に切り替えてもいい、という議論はできますが、フラグメントをマルチバイトで持ってると表示の時などはフラグメントをただ流すだけで良いのでマルチバイトの優位性がまるきり失われるというわけでもないかなと。

shiro (2014/02/13 05:09:14):

あ、ちなみになんでもともと文字の配列じゃなくてマルチバイト文字列なのかってのはscheme関係のMLやらニュースグループやらで何度も議論してるのでここでは繰りかえしません。

Kei (2014/02/13 16:21:47):

Clojureのコレクション(?)に近いイメージに思えますね。(Cに渡す部分はないですけど) JavaのStringはリーク以外にもメモリ使用量が減るというメリットもあったはず。オフセットとカウントが減るから系8バイト(?intのサイズ不明)。フラグメントにして管理すると多少メモリ効率が悪くなる気もしないでもないですが、どうなんでしょう?

shiro (2014/02/14 06:21:36):

どういうケースを想定するかですが、ポインタに8バイト使うのにフラグメントが数バイトとか数十バイトじゃ割が合わないでしょうから、現実的には大部分の短い文字列はこれまでどおりの表現になるだろうと思います(文字列接合でも閾値以下のものはコピーする)。おそらく影響を受けるのは数KB以上の長い文字列で、そういうのを大量に扱いかつsubstringを取りまくるというケースに当たってみないとはっきりした挙動はわからんですね。

ただBoehm GCはnon copyingなので巨大な連続するメモリをがんがんアロケートしたりCGしたりするとフリーメモリのフラグメンテーションが起きるってのはあって、OSのページくらいを基準に分割して持つくらいならツリー分の空間オーバヘッドはそれほどでもないでしょうし、GCの負担軽減分が勝るかもしれません。そういう長い文字列はだいたいScmDString (JavaのStringBuilderみたいなやつ) で作られるんですがScmDString自体はチャンクに貯めてって最後に連続領域にコピー、ってしてるんで、生成時のコピーも省ける可能性があります。

(しかし、Cに渡すところで連続領域をアロケートするなら結局元の木阿弥かもしれず…)

Post a comment

Name: