単一の文字を検索するときに、search-forwardに代わるより効率的な方法はありますか?


7

バッファの内容を文字列のリストに分割する必要があります。null文字は、アイテムを区切るために使用されます。

アイテムが改行文字で区切られている場合は、同じ方法を使用できますprocess-lines

(let (lines)
  (while (not (eobp))
    (setq lines (cons (buffer-substring-no-properties
               (line-beginning-position)
               (line-end-position))
              lines))
    (forward-line 1))
  (nreverse lines))

私は仮定しforward-line、効率的ですが、使用line-beginning-positionしてline-end-position少し疑わしいです。しかし、ヌル文字が使用されているため、とにかくそれを行うことはできません。

それを行う1つの方法は次のとおりです。

(split-string (buffer-string) "\0")

私もこのバリエーションを検討していました:

(split-string (buffer-substring-no-properties (point-min)
                                              (point-max))
              "\0")

それは実際にはより効率的ですか?バッファ内のテキストはプロパティ化されていませんが、存在しないプロパティを探すとオーバーヘッドが増えると思います。

バッファを文字列に読み取ってから文字列を分割する代わりに、バッファを直接操作したいと思います。これも実際にはより効率的であると想定しています。

(let ((beg (point))
      items)
  (while (search-forward "\0" nil t)
    (push (buffer-substring-no-properties beg (1- (point))) items)
    (setq beg (point)))
  (nreverse items))

のようなものsearch-forward-charはありsearch-forwardますか?それはより効率的ですか?

私は使用できると思います:

(while (not (= (char-after) ?\0)) (forward-char))

しかし、私はそれがよりも効率的である場合、関数として利用できることを期待しますsearch-forward


1
(skip-chars-forward "^\0")仕事をする必要があります。
トビアス

@トビアスちょうど私を倒しました。:)それ(search-forward "\0" nil t)は私のマシンのほぼ3倍の速さです。
バジル

@Basilそれにもかかわらず、プログラム全体をプロファイルする必要があります。多くの場合、純粋なC関数はバイトコンパイルされたものよりも優れています。だから多分(split-string (buffer-substring-no-properties) "\0")バリアントが勝つ。さらに、パフォーマンスはテキストの構造に依存する場合があります。(ヌル文字で終了する短いトークンがたくさんあるか、ヌル文字が少ししかない大きなトークンがあります。)
Tobias

@Tobias私は知っている、とにかく私は好奇心からさらにいくつかのテストをするつもりだった。@tarsius char-after戻り値に注意してくださいnil
バジル

1
0深く掘り下げる前に、バッファ文字列の分割が本当にアプリケーションのボトルネックであることを確認しましたか?
トビアス

回答:


10

私は次のベンチマークを実行しました

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-maxskip-chars-backwardbobp、及び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が指摘するように、追加の複雑さの価値はありますか?

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.