Có cách nào khác hiệu quả hơn cho tìm kiếm chuyển tiếp khi tìm kiếm một ký tự không?


7

Tôi cần chia nội dung của bộ đệm thành một danh sách các chuỗi. Ký tự null được sử dụng để phân tách các mục.

Đó là các mục được phân tách bằng các ký tự dòng mới, sau đó tôi có thể sử dụng cách tiếp cận tương tự như 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))

Tôi giả sử forward-linelà hiệu quả, nhưng việc sử dụng line-beginning-positionline-end-positionlà một chút đáng ngờ. Nhưng vì ký tự null được sử dụng nên tôi không thể làm điều đó.

Một cách để làm điều đó sẽ là:

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

Tôi cũng đã xem xét sự thay đổi này:

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

Điều đó thực sự hiệu quả hơn? Văn bản trong bộ đệm không được chỉnh sửa, nhưng tôi sẽ tưởng tượng rằng việc tìm kiếm các thuộc tính không tồn tại vẫn sẽ thêm một chi phí.

Thay vì đọc bộ đệm thành một chuỗi và sau đó tách chuỗi tôi muốn thay vào đó làm việc trực tiếp trên bộ đệm - một lần nữa giả định rằng điều đó thực sự hiệu quả hơn.

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

Có một cái gì đó như search-forward-chartồn tại và sẽ có hiệu quả hơn search-forward?

Tôi cho rằng tôi có thể sử dụng:

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

Nhưng tôi hy vọng rằng nó sẽ có sẵn như là một chức năng nếu nó hiệu quả hơn search-forward.


1
(skip-chars-forward "^\0")nên làm công việc.
Tobias

@Tobias Chỉ cần đánh tôi với nó. :) Nó nhanh gần gấp ba lần so với (search-forward "\0" nil t)trên máy của tôi.
Basil

@Basil Tuy nhiên, chương trình tổng thể cần phải được định hình. Thường các hàm c thuần đánh bại các công cụ biên dịch byte. Vì vậy, có thể các (split-string (buffer-substring-no-properties) "\0")biến thể chiến thắng. Hơn nữa, hiệu suất có thể phụ thuộc vào cấu trúc của văn bản. (Có nhiều mã thông báo ngắn bị chấm dứt bởi các ký tự null hoặc có các mã thông báo lớn chỉ có một vài ký tự không.)
Tobias

@Tobias Tôi biết, dù sao tôi cũng sẽ làm một số bài kiểm tra vì tò mò. @tarsius Lưu ý rằng char-aftercó thể trở lại nil.
Basil

1
Bạn có chắc chắn rằng việc tách chuỗi bộ đệm tại 0thực sự là nút cổ chai của ứng dụng của bạn trước khi bạn đào sâu đến vậy không?
Tobias

Câu trả lời:


10

Tôi đã chạy các điểm chuẩn sau

GNU Emacs 27.0.50
(build 14, x86_64-pc-linux-gnu, X toolkit, Xaw3d scroll bars)
of 2018-02-21

không có tùy chỉnh, tức là bằng cách bắt đầu Emacs với -Qcờ.

Có cách nào khác hiệu quả hơn cho tìm kiếm chuyển tiếp khi tìm kiếm một ký tự không?

[...]

Có một cái gì đó như search-forward-chartồn tại và sẽ có hiệu quả hơn search-forward?

Vì @Tobias chỉ ra một cách chính xác trong một nhận xét , một sự thay thế nhanh hơn search-forwardkhi tìm kiếm một ký tự duy nhất là skip-chars-forward. Một số điểm chuẩn theo sau.

Ký tự Null ở cuối bộ đệm

(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"))))

cho

a: (6.959186105 0 0.0)
b: (2.527484532 0 0.0)

Các dòng kết thúc dài

(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)))))

cho

a: (10.596461232 0 0.0)
b: (4.896477926  0 0.0)

Các dòng kết thúc ngắn

(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)))))

cho

a: (3.642238859 0 0.0)
b: (2.281851267 0 0.0)

Lưu ý rằng chênh lệch thời gian nhỏ hơn với các dòng ngắn có khả năng do độ phức tạp của vòng thử nghiệm (b) cao hơn. Bên cạnh đó, đảo ngược sự chỉ đạo của tìm kiếm (ví dụ sử dụng point-max, skip-chars-backward, bobp, và backward-char) làm cho không có sự khác biệt đáng chú ý.

Điều đó thực sự hiệu quả hơn? Văn bản trong bộ đệm không được chỉnh sửa, nhưng tôi sẽ tưởng tượng rằng việc tìm kiếm các thuộc tính không tồn tại vẫn sẽ thêm một chi phí.

Hãy xem nào:

(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))))

cho

a: (7.069123577999999 1000 6.8170885259999885)
b: (7.072005507999999 1000 6.819331175000003)
c: (7.064939498999999 1000 6.812288113000008)

vì vậy không có sự khác biệt trong một bộ đệm không được chứng minh. Lưu ý rằng tôi phải thực hiện cuộc gọi buffer-stringtrong một hàm được biên dịch byte riêng biệt, nếu không nó sẽ được tối ưu hóa thành một hằng số bên dưới benchmark-run-compiled.

Thay vì đọc bộ đệm thành một chuỗi và sau đó tách chuỗi tôi muốn thay vào đó làm việc trực tiếp trên bộ đệm - một lần nữa giả định rằng điều đó thực sự hiệu quả hơn.

Hãy kiểm tra. Ba hàm sau sẽ cho cùng một kết quả:

(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))

Ký tự Null ở cuối bộ đệm

(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))))

cho

a: (2.46373737  200 1.5349787340000005)
b: (1.046089159 100 0.7499454190000003)
c: (1.040357797 100 0.7460460909999975)

Các dòng kết thúc dài

(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))))

cho

a: (4.065745779999999  300 2.3008262569999927)
b: (2.787263217        274 2.097104968000009)
c: (2.7745770399999996 275 2.112500514999999)

Các dòng kết thúc ngắn

(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))))

cho

a: (1.346149274 85 0.640683847)
b: (1.010766266 80 0.6072433190000055)
c: (0.989048037 80 0.6078114269999908)

Vì vậy, bạn có thể có thể tăng tốc độ ~ 2 lần bằng cách sử dụng skip-chars-{forward,backward}, nhưng như @Tobias chỉ ra , nó có đáng để thêm độ phức tạp không?

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.