New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ii: improve performance on buffer_new's worst case #617
Conversation
It doesn't change the default existing buffer search algorithm. It's
used only when the default existing buffer search algorithm doesn't find
an existing buffer. If any existing buffer isn't found by the default
existing buffer search algorithm, new buffer is allocated. It grows
index file size.
Existing buffer isn't found case is caused by the following case:
table_create a TABLE_NO_KEY
column_create a x COLUMN_SCALAR ShortText
table_create b TABLE_PAT_KEY ShortText
column_create b index COLUMN_INDEX a x
load --table a
[
{"x": "00000000"},
{"x": "00000001"},
...
{"x": "00486036"},
{"x": "00000000"},
{"x": "00000001"},
...
{"x": "00413962"}
]
buffer_open_by_tid() is a function that is just extracted code from
buffer_new(). It doesn't change any logic.
If cursor for searching existing buffer doesn't find any existing buffer
and has many records (= the worst case), many times are spent. It means
that index construction time is slow for the case.
This change limits the number of records for searching existing buffer.
The following case is the worst case:
table_create a TABLE_NO_KEY
column_create a x COLUMN_SCALAR ShortText
table_create b TABLE_PAT_KEY ShortText
column_create b index COLUMN_INDEX a x
load --table a
[
{"x": "00000000"},
{"x": "00000001"},
...
{"x": "00486036"},
{"x": "00000000"},
{"x": "00000001"},
...
{"x": "00413962"}
]
|
別の実装になりましたが f578810 をpushしました。 もともとbuffer_new()の中で |
|
さすがです! |
|
すみません、この実装だと、 重複なくカーソルを回せるといいのですが。。。 |
|
既存の挙動が壊れているのでそもそもこのアプローチはよくなさそうです。。。 |
|
キーにマルチバイト文字が含まれているときに1bitずつ減らして |
|
とりあえずnormalizeを通さずにカーソルを回すのがいいんじゃないでしょうか。 |
|
その方向でやってみます! |
GitHub: #617 It improves the buffer_new's worst case.
|
その方向で実装してみたので動作チェックとパフォーマンス測定をしてみる。 単体テストは失敗しなくなった。パフォーマンスはここに書いてあるケースは速くなった。 |
|
↑はとりあえずの結果。後でちゃんとやる。 |
|
キーが大きくて(たとえば256バイトとか1KiBとか)さらに見つからないケースだと1bitではなく1バイトずつ減らしていっても結構な時間がかかってしまうので、ずらしていく量は倍々で増やす(1, 2, 4, 8, ...)ことにした。 通常は元のキーサイズのプレフィックスサーチか元のキーサイズ-1のプレフィックスサーチで見つかるはずなので従来のケースへの影響はないはず。 |
|
いいと思います! |
See commit messages of 5b6129d and dd67962 about description of this case in English.
転置インデックスを更新するときに
buffer_new()をすることがありますが、データの内容・更新順序によって、すごく遅く、かつ、インデックスサイズがすごく増えることがあります。どのような内容・更新順序かというと次のようなケースです。
Rubyスクリプトだと次のようになります。
buffer_new()の中で語彙表をカーソルで回して既存のbufferがないか探していますが、↑のケースだとカーソルを回しきっても既存のbufferが見つかりません。00486036のレコードが終わって2週目(2回目の00000000)に入ると、レコードが増えるごとにカーソルが返すレコード数が増えるのでbuffer_new()の時間がどんどん増えてインデックス更新が遅くなります。また、既存のbufferが見つからないので毎回新しくbufferを作ります。このためインデックスサイズも大きくなります。
解決策として以下を提案します。
grn_ii_update_one()したときに新しく作ったbufferを使いまわせないかチェックする。buffer_new()にかかる時間が一定以下に抑えられるので前述のケースでの速度劣化が抑えられる。前者の実装は 5b6129d です。
grn_iiに追加しているnext_buffer_candidate_tidが使いまわせないかチェック対象のbuffer(に紐付いているtid)です。buffer_open_by_tid()はbuffer_open()のコードの一部を抜き出しただけでロジックは変えていません。SPLIT_COND()が変わっているのはbufferがb以外の変数にも入れられるようにです。後者の実装は dd67962 です。
EXISTING_BUFFER_FIND_LIMITの10000はなんとなくで決めました。特に計測したわけではありません。この変更を入れる前と入れた後では実行時間・サイズは以下のように変わります。データは前述のRubyスクリプトを実行した結果です。それを
groongaコマンドで処理しました。(*) 30分を過ぎたくらいからエラー発生。50万件後半くらいから発生する。45分くらいたって65万件くらいしかロードできていない。
以下は生結果です。
変更なし:
エラーログ:
変更あり: