【发布时间】:2020-10-30 06:20:09
【问题描述】:
XYZ 格式的文本文件在化学中很常见。这些可以是非常大的纯文本文件,其中包含许多原子的坐标序列。这是一个两帧的例子,但一般来说可能有一百万个:
9
This is always a comment line
O -0.47895617997126 -2.66640293337835 -1.49681666046534
H 0.44977991701355 -2.60732102342602 -1.12187699450036
H -0.47021693081323 -3.17753067276769 -2.30844425585481
O 1.77621192889291 -0.57453626042269 2.55501382514084
H 1.89122710803544 -0.41488971167761 3.50755810540804
H 1.63216983845037 0.32650413720929 2.13442202830578
O 2.09888701915665 -2.00704728106228 -0.54677363666735
H 1.97657049717652 -1.12593867861129 -0.98496874845379
H 2.42388964325074 -1.74615001969454 0.33580032962549
[there might be new lines here but everything before the next frame is ignored]
9
Again, always a comment line
O -0.46951624407696 -2.67861471918262 -1.48616321880993
H 0.4349320578306 -2.59324562286125 -1.15158631652204
H -0.50124399561371 -3.21321032298656 -2.30894245367388
O 1.77946051665009 -0.57508897951565 2.54905851620706
H 1.84047894070088 -0.38790923183093 3.49683088026427
H 1.62214984757904 0.28130906816753 2.15144936348414
O 2.1037484479049 -2.00698121080077 -0.53624845433526
H 2.00667363126475 -1.10227312432539 -1.00287595161132
H 2.40066596189013 -1.75997079252843 0.34324093661981
目标是将其读入三个不同的Arrays。什么类型的数组并不重要,但它们应该包含所有标题,其中包括框架中的原子数和注释行。下一个应该包含每一帧的原子标签,最后所有的坐标都应该读入Array{Float64, 3} 或Array{Arrray{Float64, 2}, 1。我更喜欢后者,但如果使用前者更有效,那就没问题了。
我尝试了两种三种不同的方法,它们的性能几乎相同。一种是我逐行读取文件并在执行过程中将每一行解析为适当的数组,使用push! 在执行过程中分配新内存。我将整个文件读入一个字符串,然后再次解析这个字符串,动态分配内存。最后,我尝试将整个文件读入一个字符串,然后使用第一帧中的原子数来预分配所有带有零或空字符串的数组。令我惊讶的是,对于一个包含 400,000 个帧(每个帧有 30 个原子)的 650MB 文件,预分配方法并没有更快,或者可能有点慢。
这是我的版本代码,它首先将文件读入字符串,然后解析该字符串。
function read_xyz(ifile::String)
"""
Reads in an xyz file of possibly multiple geometries, returning the header, atom labels,
and coordinates as arrays of strings and Float64s for the coordinates.
"""
@time file_contents = readlines(ifile)
header = Array{String, 1}()
atom_labels = Array{Array{String, 1}, 1}()
geoms = Array{Array{Float64, 2}, 1}()
for (i, line) in enumerate(file_contents)
if isa(tryparse(Int, line), Int)
# allocate the geometry for this frame
N = parse(Int, line)
head = string(N)
labels = String[]
# store the header for this frame
head = string(line, file_contents[i+1])
i += 1
push!(header, head)
# loop through the geometry storing the vectors and atom labels as you go
geom = zeros((3, N))
for j = 1:N
coords = split(file_contents[i+1])
i += 1
push!(labels, coords[1])
geom[:,j] = parse.(Float64, coords[2:end])
end
push!(geoms, geom)
push!(atom_labels, labels)
end
end
return header, atom_labels, geoms
end
这是在一个包含 400,000 帧的 650MB 文件上使用此函数的时间,每个帧有 30 个原子。
julia> @time header, labels, geoms = read_xyz("data/1body_forces.dat");
6.804485 seconds (25.22 M allocations: 1.226 GiB, 11.78% gc time)
33.436732 seconds (88.82 M allocations: 7.368 GiB, 23.17% gc time)
julia> @time header, labels, geoms = read_xyz("data/1body_forces.dat");
9.216689 seconds (25.22 M allocations: 1.226 GiB, 28.39% gc time)
36.765633 seconds (88.82 M allocations: 7.368 GiB, 29.41% gc time)
所以,很明显,有很多垃圾收集正在进行,这需要大量时间,而且速度非常慢。将文件读入字符串的部分似乎分配了两倍的内存。我不确定这是为什么。
我想接下来要尝试的是将文件读入字符串,然后以并行块的形式解析文件。我希望一些输入至少能帮助解决我的解析似乎导致大量中间内存必须收集垃圾的事实,而我实际上不确定如何解决这个问题。
非常感谢任何优化此功能的帮助。
【问题讨论】: