do_search()の効率化

メッセージ

"PukiWiki/1.4/ちょっと便利に/do_search()の改造実験" (削除済み)から移動。

do_search()の改造実験 by Cue

検索文字列の含まれるページをリストアップする関数ですが、割と重い処理のようなので軽くできないか考えてみます。

期待する効果

1.4.6の配布ファイルで呼び出している以下の2箇所の処理が軽減されます。

改造点

  1. pregの呼出し回数が多いのでパターンをstudyさせてみる。(効果大*1
  2. キーワードを含むか否かの判定にpreg_grep()を使っているが、キーワードを含む全ての行を抽出する必要は無いのでpreg_match()に置き換える。(効果中)
  3. ↑の結果、ページ内容を配列で取得しなくても良いのでfread()を使って一括読み込みする*2。(効果大)
  4. ファイル内容を読んだ場合にはfstat()で取得した時刻情報を流用する。(効果微妙*3
  5. links_update()から呼ばれる場合*4、キーワードの完全一致で良いのでget_search_words()の呼出しを抑制する。(効果大*5

パッチ

1.4.6(func.php,v 1.46)からの差分

--- lib/func.php	Mon Jul 04 00:09:28 2005
+++ lib/func.new.php	Thu Mar 30 21:42:50 2006
@@ -190,32 +190,37 @@
 	$retval = array();
 
 	$b_type = ($type == 'AND'); // AND:TRUE OR:FALSE
-	$keys = get_search_words(preg_split('/\s+/', $word, -1, PREG_SPLIT_NO_EMPTY));
+	$keys = $non_format ? array(preg_quote($word, '/')) :
+		get_search_words(preg_split('/\s+/', $word, -1, PREG_SPLIT_NO_EMPTY));
+	$keys = preg_replace('/^.*$/', '/$0/S', $keys);
 
 	$_pages = get_existpages();
 	$pages = array();
 
 	$non_list_pattern = '/' . $non_list . '/';
-	foreach ($_pages as $page) {
+	foreach ($_pages as $file=>$page) {
 		if ($page == $whatsnew || (! $search_non_list && preg_match($non_list_pattern, $page)))
 			continue;
 
+		$source = $non_format ? '' : $page . "\n";
 		// 検索対象ページの制限をかけるかどうか (ページ名は制限外)
 		if ($search_auth && ! check_readable($page, false, false)) {
-			$source = get_source(); // Empty
+			$stat = NULL;
 		} else {
-			$source = get_source($page);
+			if (($fp = fopen(DATA_DIR . $file, 'rb')) === FALSE)
+				continue;	//	ページが消えている?
+			flock($fp, LOCK_SH);
+			$stat = fstat($fp);
+			$source .= fread($fp, $stat['size']);
+			fclose($fp);
 		}
-		if (! $non_format)
-			array_unshift($source, $page); // ページ名も検索対象に
 
-		$b_match = FALSE;
 		foreach ($keys as $key) {
-			$tmp     = preg_grep('/' . $key . '/', $source);
-			$b_match = ! empty($tmp);
+			$b_match = preg_match($key, $source);
 			if ($b_match xor $b_type) break;
 		}
-		if ($b_match) $pages[$page] = get_filetime($page);
+		if ($b_match) $pages[$page] = isset($stat) ?
+			$stat['mtime'] - LOCALZONE : get_filetime($page);
 	}
 	if ($non_format) return array_keys($pages);

コメント

Search wordsfunc.phpHits/2020Time
"BugTrack"r1.541428pages20.4sec
r1.576.2sec
"開発日記"r1.54618pages3.9sec
r1.572.0sec
"Web委員"r1.5452pages4.1sec
r1.5751pages10.2sec
"b委"r1.5452pages5.8sec
r1.5751pages11.7sec
"b"r1.541845pages18.3sec
r1.573.3sec
"委"r1.5458pages3.6sec
r1.571.8sec

取り込まれたコードを見て

取り込み作業お疲れ様でした。遅まきながら感想など。

fstat()とstat()の速度差に関する補足説明

4番目の改造点を効果微妙と評しましたが、これは多数のページにヒットするような検索はあまりないだろうという前提に立っています。
Cueが書き込んだパッチの当該部分を見てもらうと$b_match=trueの場合だけページの更新時刻を調べる構造になっていますので、この前提に立てばそもそもget_filetime()の呼び出しはそれほど発生していない事になります。逆に言うと多量のページがヒットする検索ではfstat()とstat()の速度差は無視できないものとなります。

どの程度無視できないのか?

速度の計測は裏で何が動いているか把握できる隔絶された環境で厳密に比較すべきですが追試可能であることも重要でしょう。おあつらえ向き(?)に1.4.7のコードでは検索ヒットしたページのpassage取得のみ別ループでの処理*11に変えられているので、この点を利用してstat()系の処理の重さを見積もることができたりします*12
下の結果は`*を使ってPlus!の公式サイトで検索したときのものです(勝手に実験に使って済みません>plusの方々*13)。

検索文字convert timehitしたページ数
`0.21sec程度7
*0.30sec程度689

多数のページにヒットする検索があまりないなら構う事もないのですが、実は同様の全ページに渡るstat()取得がget_source()呼び出しに隠されています(is_page()(の中のfile_exists())とfilesize()*14)。軽量化ネタなわけですし、このあたりもう少し配慮できたんじゃないの?と感じてしまいます。

links_update()からの呼び出し

link.phpには踏み込みたくなかったので5番目の改造点で留めておいたのですが、もしteananさんがlink.php側からココに到達したとなると話は違ってきます(どこからとは書いてないですけど)。link.phpの各処理で最も重い部分はInlineConverterの呼び出しと考える方が自然でしょう。もしこの処理を軽くしようとするならばむしろpreg_grep()を使ってInlineConverterに渡す行数を絞るコードを別に用意する方が正しいと思います。*15


*1 普通はそれほど効果は出ないですが今回は効きます
*2 func.php内部でファイル読み込みするという構成上の問題はありますが
*3 stat()とfstat()の速度差が大きい環境ではそこそこ効果的かも?
*4 $non_formatが真の時はlinks_update()からの呼出しと想定して変更したので自作プラグインの中に影響を受けるものがあるかもしれません
*5 preg_match()の速度アップとリストアップの確実性が増す(=links_get_objects()を通すページ数が減る)ためですが、もう一歩踏み込むには他の方も指摘していますが2回ファイルを読まないように変えた方が良いのでしょう
*6 ちなみに、('A') が顔文字に見えてしまったのは動揺しているからでしょうか (^^;
*7 改造点1
*8 と、ついさっきまで昼寝していた私が言う
*9 実は私も寝てました(笑)
*10 Changelogを見てニヤリと笑う
*11 passage取得前にclearstatcache()しているのとほぼ等価
*12 あくまで参考程度ですが、コードに手をつけず自分のサイトで試すにはお手軽かと
*13 動作しているコードが(ほぼ)把握できて、ページ数がそこそこあるサイトは他に思い当たらなかったのです
*14 余談ですがflock()とfilesize()の間にclearstatcche()は不要??
*15 ついでながら、私は手をつける気がありませんのでlink.phpの他の変な点もメモしておきます。
a)links_init()では影響されないのにlinks_update()では$search_authの影響がある事
b)ページ削除時にlinks_delete()が呼ばれる事(関数名はそれっぽいけど?)
c)複数行プラグインの書式に対応していない事
d)ページ編集でAutolink←→BracketNameにリンク種別が変わった場合追随できない事
あと1つあった気がしますが失念しました。


トップ   編集 凍結 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2010-08-17 (火) 22:05:09
Site admin: PukiWiki Development Team

PukiWiki 1.5.4+ © 2001-2022 PukiWiki Development Team. Powered by PHP 8.2.12. HTML convert time: 0.276 sec.

SourceForge