私は次のベンチマークを実行しました
GNU Emacs 27.0.50
(build 14, x86_64-pc-linux-gnu, X toolkit, Xaw3d scroll bars)
of 2018-02-21
カスタマイズなし、つまり-Q
フラグを付けてEmacsを起動する。
単一の文字を検索するときに、search-forwardに代わるより効率的な方法はありますか?
[...]
のようなものsearch-forward-char
はありsearch-forward
ますか?それはより効率的ですか?
@Tobias がコメントで正しく指摘しているのでsearch-forward
、単一の文字を検索するよりも高速な代替手段ですskip-chars-forward
。いくつかのベンチマークが続きます。
バッファの終わりにヌル文字
(with-temp-buffer
(dotimes (_ 10000)
;; Newline-terminated line of random printable ASCII
(insert (make-string 200 (+ #x20 (random #x5e))) ?\n))
;; NUL
(insert 0)
(garbage-collect)
(message "a: %s" (benchmark-run-compiled 1000
(goto-char (point-min))
(search-forward "\0")))
(message "b: %s" (benchmark-run-compiled 1000
(goto-char (point-min))
(skip-chars-forward "^\0"))))
与える
a: (6.959186105 0 0.0)
b: (2.527484532 0 0.0)
ヌルで終了する長い行
(with-temp-buffer
(dotimes (_ 10000)
;; Null-terminated line of random printable ASCII
(insert (make-string 200 (+ #x20 (random #x5e))) 0))
(garbage-collect)
(message "a: %s" (benchmark-run-compiled 1000
(goto-char (point-min))
(while (search-forward "\0" nil t))))
(message "b: %s" (benchmark-run-compiled 1000
(goto-char (point-min))
(while (progn (skip-chars-forward "^\0")
(not (eobp)))
(forward-char)))))
与える
a: (10.596461232 0 0.0)
b: (4.896477926 0 0.0)
ヌルで終了する短い行
(with-temp-buffer
(dotimes (_ 10000)
;; Null-terminated line of random printable ASCII
(insert (make-string 4 (+ #x20 (random #x5e))) 0))
(garbage-collect)
(message "a: %s" (benchmark-run-compiled 1000
(goto-char (point-min))
(while (search-forward "\0" nil t))))
(message "b: %s" (benchmark-run-compiled 1000
(goto-char (point-min))
(while (progn (skip-chars-forward "^\0")
(not (eobp)))
(forward-char)))))
与える
a: (3.642238859 0 0.0)
b: (2.281851267 0 0.0)
短いラインとの時間差が小さいのは、テスト(b)のループが複雑であるためと考えられます。さらに、検索の方向を反転(すなわち使用してpoint-max
、skip-chars-backward
、bobp
、及びbackward-char
)目立った違いはありません。
それは実際にはより効率的ですか?バッファ内のテキストはプロパティ化されていませんが、存在しないプロパティを探すとオーバーヘッドが増えると思います。
どれどれ:
(defun a ()
(buffer-string))
(defun b ()
(buffer-substring (point-min) (point-max)))
(defun c ()
(buffer-substring-no-properties (point-min) (point-max)))
(dolist (f '(a b c))
(byte-compile f))
(with-temp-buffer
(dotimes (_ 10000)
;; Random-length random-printable-ASCII newline-terminated line
(dotimes (_ (random 200))
(insert (+ #x20 (random #x5e))))
(insert ?\n))
(garbage-collect)
(message "a: %s" (benchmark-run 1000 (a)))
(garbage-collect)
(message "b: %s" (benchmark-run 1000 (b)))
(garbage-collect)
(message "c: %s" (benchmark-run 1000 (c))))
与える
a: (7.069123577999999 1000 6.8170885259999885)
b: (7.072005507999999 1000 6.819331175000003)
c: (7.064939498999999 1000 6.812288113000008)
したがって、未処理のバッファに違いはありません。への呼び出しをbuffer-string
別のバイトコンパイルされた関数に配置する必要があることに注意してください。そうしないと、で定数に最適化されbenchmark-run-compiled
ます。
バッファを文字列に読み取ってから文字列を分割する代わりに、バッファを直接操作したいと思います。これも実際にはより効率的であると想定しています。
確認しよう。次の3つの関数は同じ結果になります。
(defun a ()
(split-string (buffer-string) "\0"))
(defun b ()
(goto-char (point-min))
(let (l)
(while (let ((p (point)))
(push (buffer-substring-no-properties
p (+ p (skip-chars-forward "^\0")))
l)
(not (eobp)))
(forward-char))
(nreverse l)))
(defun c ()
(goto-char (point-max))
(let (l)
(while (let ((p (point)))
(push (buffer-substring-no-properties
p (+ p (skip-chars-backward "^\0")))
l)
(not (bobp)))
(backward-char))
l))
(dolist (f (a b c))
(byte-compile f))
バッファの終わりにヌル文字
(with-temp-buffer
(dotimes (_ 10000)
;; Newline-terminated line of random printable ASCII
(insert (make-string 200 (+ #x20 (random #x5e))) ?\n))
;; NUL
(insert 0)
(garbage-collect)
(message "a: %s" (benchmark-run 100 (a)))
(garbage-collect)
(message "b: %s" (benchmark-run 100 (b)))
(garbage-collect)
(message "c: %s" (benchmark-run 100 (c))))
与える
a: (2.46373737 200 1.5349787340000005)
b: (1.046089159 100 0.7499454190000003)
c: (1.040357797 100 0.7460460909999975)
ヌルで終了する長い行
(with-temp-buffer
(dotimes (_ 10000)
;; Null-terminated line of random printable ASCII
(insert (make-string 200 (+ #x20 (random #x5e))) 0))
(garbage-collect)
(message "a: %s" (benchmark-run 100 (a)))
(garbage-collect)
(message "b: %s" (benchmark-run 100 (b)))
(garbage-collect)
(message "c: %s" (benchmark-run 100 (c))))
与える
a: (4.065745779999999 300 2.3008262569999927)
b: (2.787263217 274 2.097104968000009)
c: (2.7745770399999996 275 2.112500514999999)
ヌルで終了する短い行
(with-temp-buffer
(dotimes (_ 10000)
;; Null-terminated line of random printable ASCII
(insert (make-string 4 (+ #x20 (random #x5e))) 0))
(garbage-collect)
(message "a: %s" (benchmark-run 100 (a)))
(garbage-collect)
(message "b: %s" (benchmark-run 100 (b)))
(garbage-collect)
(message "c: %s" (benchmark-run 100 (c))))
与える
a: (1.346149274 85 0.640683847)
b: (1.010766266 80 0.6072433190000055)
c: (0.989048037 80 0.6078114269999908)
したがって、おそらくを使用することで〜2倍のスピードアップが得られますskip-chars-{forward,backward}
が、@ Tobiasが指摘するように、追加の複雑さの価値はありますか?
(skip-chars-forward "^\0")
仕事をする必要があります。