当前位置 : 主页 > 网络安全 > 测试自动化 >

性能 – 如何提高Common Lisp中大型文件的读取速度

来源:互联网 收集:自由互联 发布时间:2021-06-22
最近我有一个处理大文件的任务,文件大小为460MB,并包含5777672行.当我使用 linux内置命令’wc’来计算文件行号时,它非常快: time wc -l large_ess_test.log5777672 large_ess_test.logreal 0m0.144suser 0m0.
最近我有一个处理大文件的任务,文件大小为460MB,并包含5777672行.当我使用 linux内置命令’wc’来计算文件行号时,它非常快:

time wc -l large_ess_test.log
5777672 large_ess_test.log

real    0m0.144s
user    0m0.052s
sys     0m0.084s

然后我使用以下代码来计算Common Lisp中的行号(SBCL 1.3.7 64位)

#!/usr/local/bin/sbcl --script
(defparameter filename (second *posix-argv*))
(format t "nline: ~D~%"
        (with-open-file (in filename)
          (loop for l = (read-line in nil nil)
             while l
             count l)))

结果让我很失望,因为与’wc’命令相比它真的很慢.我们只计算行号,即使没有任何其他操作:

time ./test.lisp large_ess_test.log
nline: 5777672

real    0m3.994s
user    0m3.808s
sys     0m0.152s

我知道SBCL提供了C函数接口,我们可以直接调用C程序.我相信如果我直接调用C函数,性能会提高,所以我写下面的代码:

#!/usr/local/bin/sbcl --script
(define-alien-type pointer (* char))
(define-alien-type size_t  unsigned-long)
(define-alien-type ssize_t long)
(define-alien-type FILE*   pointer)

(define-alien-routine fopen FILE*
  (filename c-string)
  (modes    c-string))

(define-alien-routine fclose int
  (stream FILE*))

(define-alien-routine getline ssize_t
  (lineptr (* (* char)))
  (n       (* size_t))
  (stream  FILE*))

;; The key to improve the performance:
(declaim (inline getline))
(declaim (inline read-a-line))

(defparameter filename (second *posix-argv*))

(defun read-a-line (fp)
  (with-alien ((lineptr (* char))
               (size    size_t))
    (setf size 0)
    (prog1
        (getline (addr lineptr) (addr size) fp)
      (free-alien lineptr))))

(format t "nline: ~D~%"
        (let ((fp (fopen filename "r"))
              (nline 0))
          (unwind-protect
               (loop
                  (if (= -1 (read-a-line fp))
                      (return)
                      (incf nline)))
            (unless (null-alien fp)
              (fclose fp)))
          nline))

注意有两个’declaim’行.如果我们不写这两行,性能几乎与以前的版本相同:

;; Before declaim inline:

;; time ./test2.lisp large_ess_test.log
;; nline: 5777672

;; real 0m3.774s
;; user 0m3.604s
;; sys  0m0.148s

但如果我们写出这两行,性能就会大幅提升:

;; After delaim inline:

;; time ./test2.lisp large_ess_test.log
;; nline: 5777672

;; real 0m0.767s
;; user 0m0.616s
;; sys  0m0.136s

我认为第一个版本的性能问题是’read-line’除了从流中读取一行之外还做了很多其他事情.此外,如果我们可以获得“读取线”的内联版本,速度将会提高.问题是我们可以这样做吗?是否还有其他(标准)方法可以在不依赖FFI(非标准)的情况下提高读取性能?

READ-LINE的主要问题之一是它为每个调用分配一个新字符串.这可能需要花费时间,具体取决于实施方案.

Common Lisp标准缺少一个函数,它将一行读入字符串缓冲区.

一些实现提供了将行读入缓冲区的函数的解决方案.例如,Allegro CL中的功能READ-LINE-INTO.

通常,实现提供缓冲输入的流.可以在此基础上实现搜索换行符,但是其代码可能是特定于实现的(或使用一些流抽象)和/或复杂的.

我不知道是否有这样的功能的官方实现,但这里可以找到类似的东西 – 对于SBCL来说看起来很复杂:

https://github.com/ExaScience/elprep/blob/master/buffer.lisp中的read-line-into-buffer

网友评论