Ich habe die folgenden Benchmarks durchgeführt
GNU Emacs 27.0.50
(build 14, x86_64-pc-linux-gnu, X toolkit, Xaw3d scroll bars)
of 2018-02-21
ohne Anpassungen, dh indem Emacs mit der -Q
Flagge gestartet wird .
Gibt es eine effizientere Alternative zur Vorwärtssuche bei der Suche nach einem einzelnen Zeichen?
[...]
Gibt es so etwas search-forward-char
und wäre das effizienter als search-forward
?
Wie @Tobias in einem Kommentar richtig hervorhebt , ist eine schnellere Alternative zur search-forward
Suche nach einem einzelnen Zeichen skip-chars-forward
. Einige Benchmarks folgen.
Nullzeichen am Pufferende
(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"))))
gibt
a: (6.959186105 0 0.0)
b: (2.527484532 0 0.0)
Lange nullterminierte Zeilen
(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)))))
gibt
a: (10.596461232 0 0.0)
b: (4.896477926 0 0.0)
Kurze nullterminierte Zeilen
(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)))))
gibt
a: (3.642238859 0 0.0)
b: (2.281851267 0 0.0)
Es ist zu beachten, dass der geringere Zeitunterschied bei kurzen Leitungen wahrscheinlich auf die höhere Schleifenkomplexität von Test (b) zurückzuführen ist. Darüber hinaus die Richtung der Suche Umkehren (dh mit point-max
, skip-chars-backward
, bobp
, und backward-char
) macht keinen spürbaren Unterschied.
Ist das eigentlich effizienter? Der Text im Puffer ist nicht geeignet, aber ich würde mir vorstellen, dass das Suchen nach nicht vorhandenen Eigenschaften immer noch einen Overhead verursachen würde.
Wir werden sehen:
(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))))
gibt
a: (7.069123577999999 1000 6.8170885259999885)
b: (7.072005507999999 1000 6.819331175000003)
c: (7.064939498999999 1000 6.812288113000008)
also kein Unterschied in einem nicht propertisierten Puffer. Beachten Sie, dass ich den Aufruf von buffer-string
in eine separate bytekompilierte Funktion stellen musste, sonst wäre er auf eine Konstante unter optimiert worden benchmark-run-compiled
.
Anstatt den Puffer in eine Zeichenfolge einzulesen und dann die Zeichenfolge aufzuteilen, möchte ich stattdessen direkt am Puffer arbeiten - wiederum unter der Annahme, dass dies tatsächlich effizienter ist.
Lass uns nachsehen. Die folgenden drei Funktionen sollten das gleiche Ergebnis liefern:
(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))
Nullzeichen am Pufferende
(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))))
gibt
a: (2.46373737 200 1.5349787340000005)
b: (1.046089159 100 0.7499454190000003)
c: (1.040357797 100 0.7460460909999975)
Lange nullterminierte Zeilen
(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))))
gibt
a: (4.065745779999999 300 2.3008262569999927)
b: (2.787263217 274 2.097104968000009)
c: (2.7745770399999996 275 2.112500514999999)
Kurze nullterminierte Zeilen
(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))))
gibt
a: (1.346149274 85 0.640683847)
b: (1.010766266 80 0.6072433190000055)
c: (0.989048037 80 0.6078114269999908)
Sie können also wahrscheinlich eine ~ 2-fache skip-chars-{forward,backward}
Beschleunigung erzielen, wenn Sie verwenden , aber wie @Tobias hervorhebt , ist es die zusätzliche Komplexität wert?
(skip-chars-forward "^\0")
sollte den Job machen.