与Python相比,在Julia中读取csv的速度较慢



与Python相比,在Julia中读取大型text.csv文件需要很长时间。以下是读取大小为486.6MB、行数为153895、列数为644的文件的时间。

python 3.3示例

import pandas as pd
import time
start=time.time()
myData=pd.read_csv("C:\myFile.txt",sep="|",header=None,low_memory=False)
print(time.time()-start)
Output: 19.90

R 3.0.2 示例

system.time(myData<-read.delim("C:/myFile.txt",sep="|",header=F,
stringsAsFactors=F,na.strings=""))
Output:
User    System  Elapsed
181.13  1.07    182.32

Julia 0.2.0(Julia Studio 0.4.4)示例#1

using DataFrames
timing = @time myData = readtable("C:/myFile.txt",separator='|',header=false)
Output:
elapsed time: 80.35 seconds (10319624244 bytes allocated)

Julia 0.2.0(Julia Studio 0.4.4)示例#2

timing = @time myData = readdlm("C:/myFile.txt",'|',header=false)
Output:
elapsed time: 65.96 seconds (9087413564 bytes allocated)
  1. Julia比R快,但与Python相比相当慢。我可以采取哪些不同的措施来加快读取大型文本文件的速度?

  2. 另一个问题是内存中的大小是Julia中硬盘文件大小的18倍,但python只有2.5倍。在我发现对大文件来说内存效率最高的Matlab中,它的大小是硬盘文件大小的2倍。Julia内存中文件大小过大,有什么特别的原因吗?

最好的答案可能是我不是一个像Wes那样优秀的程序员。

一般来说,DataFrames中的代码比Pandas中的代码优化得差得多。我相信我们能赶上,但这需要一些时间,因为我们需要首先实现很多基本功能。由于Julia中需要构建的内容太多,我倾向于将重点放在三个部分:(1)构建任何版本,(2)构建正确的版本,(3)构建快速、正确的版本。对于我所做的工作,Julia通常不提供任何版本的基本功能,所以我的工作集中在(1)和(2)上。随着我需要的工具越来越多,关注性能将变得更加容易。

至于内存使用,我认为答案是,我们在解析表格数据时使用了一组数据结构,其效率远低于Pandas使用的数据结构。如果我更了解Pandas的内部结构,我可以列出我们效率较低的地方,但目前我只推测一个明显的失败是,我们将整个数据集读取到内存中,而不是从磁盘中获取块。这当然是可以避免的,而且这样做还有一些问题。这只是时间问题。

注意,readtable代码非常容易阅读。让readtable更快的最确定的方法是快速生成Julia探查器,并开始修复它发现的性能缺陷。

Jacob Quinn有一个相对较新的julia包,名为CSV.jl,它提供了一个更快的CSV解析器,在许多情况下与panda不相上下:https://github.com/JuliaData/CSV.jl

请注意,@time输出的"n个已分配字节"是所有已分配对象的总大小,忽略其中可能已释放的数量。这个数字通常远高于内存中活动对象的最终大小。我不知道这是否是你对内存大小的估计,但我想指出这一点。

我发现了一些可以部分帮助这种情况的东西。

  1. 在Julia中使用readdlm()函数似乎比readtable()工作得快得多(例如,在最近的试验中为3x)。当然,如果您想要DataFrame对象类型,则需要转换为它,这可能会占用大部分或全部速度改进。

  2. 指定文件的大小可以在速度和内存分配方面产生很大的差异。我在磁盘上258.7MB的文件中运行了这个试用读取:

    julia> @time Data = readdlm("MyFile.txt", 't', Float32, skipstart = 1);
    19.072266 seconds (221.60 M allocations: 6.573 GB, 3.34% gc time)
    julia> @time Data = readdlm("MyFile.txt", 't', Float32, skipstart = 1, dims = (File_Lengths[1], 62));
    10.309866 seconds (87 allocations: 528.331 MB, 0.03% gc time)
    
  3. 对象的类型规范非常重要。例如,如果您的数据中有字符串,那么您读取的数组的数据将是Any类型,这在内存方面是昂贵的。如果内存真的是一个问题,您可能需要考虑通过先将字符串转换为整数、进行计算,然后再转换回来来预处理数据。此外,如果你不需要大量的精度,使用Float32而不是Float64可以节省大量空间。您可以在中读取文件时指定此项,例如:

    Data = readdlm("file.csv", ',', Float32)

  4. 关于内存使用,我特别发现,如果数据有很多重复值,PooledDataArray类型(来自DataArrays包)可以帮助减少内存使用。转换为这种类型的时间相对较大,因此这本身并不能节省时间,但至少有助于在一定程度上减少内存使用。例如,当加载一个包含1900万行和36列的数据集时,其中8列表示用于统计分析的分类变量,这将对象在磁盘上的内存分配从其大小的5倍减少到其大小的4倍。如果有更多的重复值,那么内存减少可能会更加显著(我曾经遇到过PooledDataArray将内存分配减半的情况)。

  5. 有时,在加载和格式化数据后运行gc()(垃圾收集器)函数以清除任何不需要的ram分配也会有所帮助,尽管Julia通常会自动很好地完成这项工作。

尽管如此,我仍期待着Julia的进一步开发,以实现更快的加载和更高效的大数据集内存使用。

让我们首先创建一个您正在讨论的文件,以提供再现性:

open("myFile.txt", "w") do io
foreach(i -> println(io, join(i+1:i+644, '|')), 1:153895)
end

现在我在Julia 1.4.2和CSV.jl 0.7.1中阅读了这个文件。

单螺纹:

julia> @time CSV.File("myFile.txt", delim='|', header=false);
4.747160 seconds (1.55 M allocations: 1.281 GiB, 4.29% gc time)
julia> @time CSV.File("myFile.txt", delim='|', header=false);
2.780213 seconds (13.72 k allocations: 1.206 GiB, 5.80% gc time)

并且使用例如4个线程:

julia> @time CSV.File("myFile.txt", delim='|', header=false);
4.546945 seconds (6.02 M allocations: 1.499 GiB, 5.05% gc time)
julia> @time CSV.File("myFile.txt", delim='|', header=false);
0.812742 seconds (47.28 k allocations: 1.208 GiB)

在R中是:

> system.time(myData<-read.delim("myFile.txt",sep="|",header=F,
+                                stringsAsFactors=F,na.strings=""))
user  system elapsed 
28.615   0.436  29.048 

在Python(Pandas)中,它是:

>>> import pandas as pd
>>> import time
>>> start=time.time()
>>> myData=pd.read_csv("myFile.txt",sep="|",header=None,low_memory=False)
>>> print(time.time()-start)
25.95710587501526

现在,如果我们从R测试fread(这很快),我们得到:

> system.time(fread("myFile.txt", sep="|", header=F,
stringsAsFactors=F, na.strings="", nThread=1))
user  system elapsed 
1.043   0.036   1.082 
> system.time(fread("myFile.txt", sep="|", header=F,
stringsAsFactors=F, na.strings="", nThread=4))
user  system elapsed 
1.361   0.028   0.416 

因此,在这种情况下,总结是:

  • 尽管第一次运行Julia中的CSV.File时编译成本很高,但它明显快于基本R或Python
  • 它在速度上与R中的fread相当(在这种情况下稍微慢一点,但这里制作的其他基准显示了它更快的情况)

EDIT:根据请求,我为一个小文件添加了一个基准:10列,100000行Julia vs Pandas。

数据准备步骤:

open("myFile.txt", "w") do io
foreach(i -> println(io, join(i+1:i+10, '|')), 1:100_000)
end

CSV.jl,单螺纹:

julia> @time CSV.File("myFile.txt", delim='|', header=false);
1.898649 seconds (1.54 M allocations: 93.848 MiB, 1.48% gc time)
julia> @time CSV.File("myFile.txt", delim='|', header=false);
0.029965 seconds (248 allocations: 17.037 MiB)

熊猫:

>>> import pandas as pd
>>> import time
>>> start=time.time()
>>> myData=pd.read_csv("myFile.txt",sep="|",header=None,low_memory=False)
>>> print(time.time()-start)
0.07587623596191406

结论:

  • 编译成本是必须支付的一次性成本,并且是恒定的(大致上不取决于您想要读取的文件的大小)
  • 对于小文件,CSV.jl比Pandas更快(如果我们排除编译成本)

现在,如果您想避免在每次新的Julia会话中支付编译成本,这是可行的https://github.com/JuliaLang/PackageCompiler.jl.

根据我的经验,如果你在做数据科学工作,例如你阅读了数千个CSV文件,我不需要等待2秒钟来编译,如果以后我可以节省几个小时的话。写入读取文件的代码需要2秒钟以上的时间。

当然,如果您编写的脚本几乎不起作用,并且在完成后终止,那么这是一个不同的用例,因为编译时间实际上是计算成本的大部分。在本例中,使用PackageCompiler.jl是我使用的一种策略。

根据我的经验,处理较大文本文件的最佳方法不是将它们加载到Julia中,而是对它们进行流式处理。这种方法有一些额外的固定成本,但通常运行速度极快。一些伪代码是这样的:

function streamdat()
mycsv=open("/path/to/text.csv", "r")   # <-- opens a path to your text file
sumvec = [0.0]                # <-- store a sum  here
i = 1
while(!eof(mycsv))            # <-- loop through each line of the file
row = readline(mycsv) 
vector=split(row, "|")     # <-- split each line by |
sumvec+=parse(Float64, vector[i]) 
i+=1
end
end
streamdat()

上面的代码只是一个简单的和,但这个逻辑可以扩展到更复杂的问题。

using CSV
@time df=CSV.read("C:/Users/hafez/personal/r/tutorial for students/Book2.csv")

最近我尝试了Julia 1.4.2。我发现了不同的反应,一开始,我不理解茱莉亚。然后我在Julia论坛上发布了同样的内容。然后我明白了这段代码将只提供编译时间。在这里你可以找到基准

最新更新