【问题标题】:Fast CSV reading in Common Lisp在 Common Lisp 中快速读取 CSV
【发布时间】:2015-08-15 22:41:48
【问题描述】:

在 CL 中读取 csv 文件的最快方法是: 1) 第一行中的所有字段都进入一个名为 column-names 的数组 2) 以下所有行的第一个字段进入另一个字段 称为行名的数组 3)所有其他字段进入另一个称为值的数组 ?

我的文件有以下形式,只是有更多的列和行:

"";"ES1 Index";"VG1 Index";"TY1 Comdty";"RX1 Comdty";"GC1 Comdty"
"1999-01-04";1391.12;3034.53;66.515625;86.2;441.39
"1999-01-05";1404.86;3072.41;66.3125;86.17;440.63
"1999-01-06";1435.12;3156.59;66.4375;86.32;441.7
"1999-01-07";1432.32;3106.08;66.25;86.22;447.67

而我想要的结果是:

#("1999-01-04" "1999-01-05" "1999-01-06" "1999-01-07" )
#("" "ES1 Index" "VG1 Index" "TY1 Comdty" "RX1 Comdty" "GC1 Comdty")
#(1391.12 3034.53 66.515625 86.2 441.39 1404.86 3072.41 66.3125 86.17 440.63
  1435.12 3156.59 66.4375 86.32 441.7 1432.32 3106.08 66.25 86.22 447.67)

您是否知道一些已经这样做的 CL 库? 是否有任何关于 I/O 性能的一般性问题(可能是特定于编译器的)我应该注意?

这是我现在的做法:

(with-open-file (stream "my-file.csv" :direction :input)
   (let* ((header (read-line stream nil))
          (columns-list (mapcar #'read-from-string
                                (cl-ppcre:split ";" header)))
          (number-of-columns (length columns-list))
          (column-names (make-array number-of-columns
                                    :initial-contents columns-list))
          (rownames (make-array 1 :adjustable t :fill-pointer 0))
          (values (make-array 1 :adjustable t :fill-pointer 0)))
 (set-syntax-from-char #\; #\ )
 (loop
    :for reader = (read stream nil stream)
    :until (eq reader stream)
    :do (progn (vector-push-extend reader row-names)
           (loop
              :for count :from 2 :upto number-of-columns
              :do (vector-push-extend (read stream nil)
                                  values)))
    :finally (return (values row-names
                      column-names
                      values)))))

注意:我不会在实际代码中使用 set-syntax-from-char,我只是为了这个示例而使用它。

【问题讨论】:

  • 其他答案给你最快的便携方式。最快的非便携方式是尽量减少复制(最慢的部分)。拆分字符串时会发生复制(例如,基于引号或读取行中的新行拆分)。如果您的实现可以被说服并支持某种 C FFI,它允许您在某些 cl 对象中引用 c 样式数组,该对象仅存储指针和长度,那么您可以只映射 csv 文件并解析它ram,通过指向内存中的文件来构建你的字符串。这不需要复制,但不允许转义引号。

标签: csv io common-lisp


【解决方案1】:

我怀疑 I/O 是这里最慢的部分。如果您使用READ-SEQUENCE 而不是重复调用READ-LINE,您可能会获得更快的I/O。所以你的代码可能看起来像这样:

(with-open-file (s "my-file.csv")
  (let* ((len (file-length s))
         (data (make-array len)))
    (read-sequence data s)
     data))

然后用换行符拆分data 并添加您的逻辑。

不管有没有帮助,对您的代码进行概要分析都会有所帮助,例如:sb-sprof,查看大部分时间都花在了哪里。

【讨论】:

    【解决方案2】:

    要读取 csv 文件,我发现 cl-csv 包 (https://github.com/AccelerationNet/cl-csv) 非常有用且快速。例如,要解决您的问题,可以使用以下代码:

    (let ((data (cl-csv:read-csv #P"my-file.csv" :separator #\;)))
      (values (apply #'vector (first data))
              (apply #'vector (rest (mapcar #'first data)))
              (apply #'vector 
                 (mapcar #'read-from-string (loop :for row :in (rest data)
                                                  :append (rest row))))))
    

    cl-csv:read-csv 为每一行返回一个列表,其中包含作为单元格内容的字符串列表。

    【讨论】:

    • 感谢您的意见。我试了一下。不幸的是,它需要的时间是我编写的代码的 1.5 倍。我在这里寻找性能。
    • 你使用什么 CL 实现?
    • APPLY 不是一个好主意。见:CALL-ARGUMENTS-LIMITlispworks.com/documentation/HyperSpec/Body/v_call_a.htm
    • ABCL 说它的限制是 50。
    • @Alexandre,我发现 SBCL 中的文件 I/O 可能非常慢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多