perf: gsub streams pieces into an output port (O(n) assembly), compiles regex once
ober
a6fbc04d68e5d1e7ff08c7e731c77c7b85332bde
--- a/lib/jerboa-awk/builtins/string.sls +++ b/lib/jerboa-awk/builtins/string.sls @@ -112,22 +112,28 @@ (awk-expr-regex-pattern ere) (awk->string ere))) (target-str (awk->string target-expr)) + (re (env-regex env pattern)) + (tlen (string-length target-str)) + (out (open-output-string)) (count 0)) - (let loop ((pos 0) (result '())) - (let ((m (pregexp-match-positions (env-regex env pattern) target-str pos))) + ;; Compile the regex once and stream the gap/replacement pieces into an + ;; output port (O(n) assembly) instead of re-consing a piece list and + ;; apply-string-appending it at the end. + (let loop ((pos 0)) + (let ((m (pregexp-match-positions re target-str pos))) (if (not m) - (let ((final (string-append (apply string-append (reverse result)) - (substring target-str pos (string-length target-str))))) - (set-target! (make-awk-string final)) + (begin + (put-string out (substring target-str pos tlen)) + (set-target! (make-awk-string (get-output-string out))) (make-awk-number count)) (let* ((mpos (car m)) ;; full match (start . end) (start (car mpos)) (end (cdr mpos)) (replacement (build-replacement repl-str mpos target-str))) (set! count (+ count 1)) - (loop (if (= start end) (+ end 1) end) - (cons replacement - (cons (substring target-str pos start) result))))))))) + (put-string out (substring target-str pos start)) + (put-string out replacement) + (loop (if (= start end) (+ end 1) end)))))))) (define (build-replacement repl-str match-pair target-str) "Process replacement string: & = matched text, \\ = literal backslash"