Eu executei os seguintes benchmarks em
GNU Emacs 27.0.50
(build 14, x86_64-pc-linux-gnu, X toolkit, Xaw3d scroll bars)
of 2018-02-21
sem personalizações, ou seja, iniciando o Emacs com a -Q
bandeira.
Existe uma alternativa mais eficiente para avançar na busca por um único caractere?
[...]
Existe algo assim search-forward-char
e isso seria mais eficiente do que search-forward
?
Como o @Tobias aponta corretamente em um comentário , é uma alternativa mais rápida para search-forward
procurar um único caractere skip-chars-forward
. Alguns benchmarks a seguir.
Caractere nulo no final do buffer
(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"))))
dá
a: (6.959186105 0 0.0)
b: (2.527484532 0 0.0)
Linhas longas terminadas em nulo
(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)))))
dá
a: (10.596461232 0 0.0)
b: (4.896477926 0 0.0)
Linhas curtas terminadas em nulo
(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)))))
dá
a: (3.642238859 0 0.0)
b: (2.281851267 0 0.0)
Observe que a menor diferença de tempo com linhas curtas provavelmente ocorre devido à maior complexidade do loop do teste (b). Além disso, invertendo o sentido de busca (ou seja, utilizando point-max
, skip-chars-backward
, bobp
, e backward-char
) não faz qualquer diferença perceptível.
Isso é realmente mais eficiente? O texto no buffer não é propertizado, mas eu imaginaria que procurar as propriedades inexistentes ainda adicionaria uma sobrecarga.
Vamos ver:
(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))))
dá
a: (7.069123577999999 1000 6.8170885259999885)
b: (7.072005507999999 1000 6.819331175000003)
c: (7.064939498999999 1000 6.812288113000008)
portanto, não há diferença em um buffer não autorizado. Observe que eu tive que fazer a chamada buffer-string
em uma função separada por compilação de bytes, caso contrário ela teria sido otimizada para uma constante abaixo benchmark-run-compiled
.
Em vez de ler o buffer em uma string e depois dividi-la, eu gostaria de trabalhar diretamente no buffer - novamente assumindo que isso é realmente mais eficiente.
Vamos checar. As três funções a seguir devem fornecer o mesmo resultado:
(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))
Caractere nulo no final do buffer
(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))))
dá
a: (2.46373737 200 1.5349787340000005)
b: (1.046089159 100 0.7499454190000003)
c: (1.040357797 100 0.7460460909999975)
Linhas longas terminadas em nulo
(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))))
dá
a: (4.065745779999999 300 2.3008262569999927)
b: (2.787263217 274 2.097104968000009)
c: (2.7745770399999996 275 2.112500514999999)
Linhas curtas terminadas em nulo
(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))))
dá
a: (1.346149274 85 0.640683847)
b: (1.010766266 80 0.6072433190000055)
c: (0.989048037 80 0.6078114269999908)
Portanto, você provavelmente pode obter uma aceleração de ~ 2x usando skip-chars-{forward,backward}
, mas como o @Tobias aponta , vale a pena a complexidade extra?
(skip-chars-forward "^\0")
deve fazer o trabalho.