ファイル読み込み #2

R5RS を読んでいたら、append は

結果のリストは、最後のリスト引数と構造を共有する場合を除いて、必ず新しく割り当てられる。

Revised(5) Report on the Algorithmic Language Scheme

ということだった。実は昨日改善したつもりだった readfile2 は改悪なんじゃないかと思って、ベンチマークをとってみることにした。

;; 文字列ベース
(define (readfile filepath)
  (let ((port (open-input-file filepath)))
    (let loop ((contents "") (c (read-char port)))
      (if (eof-object? c)
        (begin
          (close-input-port port)
          contents)
        (loop (string-append contents (make-string 1 c)) (read-char port))))))

;; リストベース(append)
(define (readfile2 filepath)
  (let ((port (open-input-file filepath)))
    (let loop ((contents '()) (c (read-char port)))
      (if (eof-object? c)
        (begin
          (close-input-port port)
          (list->string contents))
        (loop (append contents (cons c '())) (read-char port))))))

;; ベンチマーク
(define (benchmark4 fn st)
  (use srfi-19)
  (let loop ((c '0))
    (if (= c 5)
      (time-difference (current-time) st)
      (begin
        (fn "C:\\test.txt")
        (loop (+ c 1))))))

;; ベンチマーク実施
(benchmark4 readfile (current-time))
(benchmark4 readfile2 (current-time))

読み込むテキストのサイズは大体 80KB。

僕の環境だと、以下のような結果になった。

方法 時間
string-append 39.6 秒
append 189.6 秒


やっぱり改悪だった λ...


でも string-append も実用にならない遅さ。

もしやと思って作ってみたのが cons ベースの読み込み。

;; リストベース(cons)
(define (readfile3 filepath)
  (let ((port (open-input-file filepath)))
    (let loop ((contents '()) (c (read-char port)))
      (if (eof-object? c)
        (begin
          (close-input-port port)
          (list->string (reverse contents)))
        (loop (cons c contents) (read-char port))))))

;; ベンチマーク
(benchmark4 readfile3 (current-time))
方法 時間
cons 0.04 秒

おおっ!超劇的に速いじゃないですか(処理系は Gauche です)。

リストの並びが逆になってしまう点、なので最後にひっくり返さなきゃならない点などが「なんだかな〜」という感じだったんだけど、これは Scheme の定石なんですかね。

この手続きはペアを新しく割り当てて返し、そのcarはobj1、cdrはobj2である。

Revised(5) Report on the Algorithmic Language Scheme

なるほど。ペアは新しく割り当てられるけど、car も cdr も元々のものを流用するから速いわけだ。