Julia文档为Array
描述了pre-allocating memory,以通过避免垃圾回收来提高性能。毕竟这可能与String
这是一个Vector有关吗? String
源代码为here。
我的用例是我正在处理大型文本转储,使用readuntil()
获取一个块,然后执行正则表达式match()
或matchall()
或replace()
。我已经用Perl编写了它,但想知道Julia是否可以更快。我已经知道了我必须处理的最长字符串的长度。
fs=open(fn,"r")
while !eof(fs)
text = readuntil(fs, "</tag>")
text = match(r"pattern"s, text).match
text = replace(text, r"badpattern", "goodpattern")
text = replace(text, r"anotherbadpattern", "betterpattern")
... (dozens more replacements)
end
close(fs)
我希望磁盘I / O成为主要的瓶颈,但我有兴趣了解任何有用的东西。我欢迎任何关于加快这一进程的可能方法的建议。
答案 0 :(得分:5)
Julia中的字符串是不可变的,因此预先定位的概念不起作用。
julia> a = "aaaa";
julia> pointer(a)
Ptr{UInt8} @0x0000000119628f50
julia> a = "bbbb";
julia> pointer(a)
Ptr{UInt8} @0x000000011963a030
答案 1 :(得分:3)
首先,
“过早优化是所有邪恶的根源” - Donald Knuth
话虽如此,Base中的replace
代码显示它读取源String并找到要替换的匹配,并将结果String的部分写入IOBuffer。处理后,它从IOBuffer读取输出String。基本上,这会执行源String的两个副本。如果已知输入字符串的最大长度(以及输出),则可以重用replace
中的IOBuffer而不是分配。
要将此分析放入代码中,请定义以下replace!
(基于replace
):
function replace!(out::IOBuffer, str::String, pattern, repl, limit::Integer)
n = 1
e = endof(str)
i = a = start(str)
r = search(str,pattern,i)
j, k = first(r), last(r)
Base.ensureroom(out, floor(Int, 1.2sizeof(str)))
while j != 0
if i == a || i <= k
Base.write_sub(out, str.data, i, j-i)
Base._replace(out, repl, str, r, pattern)
end
if k<j
i = j
k = nextind(str, j)
else
i = k = nextind(str, k)
end
if j > e
break
end
r = search(str,pattern,k)
j, k = first(r), last(r)
n == limit && break
n += 1
end
write(out, SubString(str,i))
takebuf_string(out)
end
replace!(out::IOBuffer,s::AbstractString, pat, f, n::Integer) =
replace!(out, String(s), pat, f, n)
replace!(out::IOBuffer,s::AbstractString, pat, r) =
replace!(out, s, pat, r, 0)
现在,
const string1 = "the quick brown fox jumped over the lazy dog"
const out = IOBuffer(100)
replace!(out,string1,"quick","slow")
将在不分配IOBuffer的情况下执行替换。在replace!
之后,out
可以重复使用(takebuf_string
中的replace!
将其重置为空。基准测试显示这会将分配减少50%并加快流程速度如果使用了很多replace
语句,正如您在问题中所建议的那样,这应该会提高30%或更多。
如果你试试这个,听听现实世界的基准测试结果会很高兴。也许某些版本的replace!
应放入Base。
答案 2 :(得分:1)
在Julia 0.6中,尝试更优化的版本更容易。具体来说,我们可以让IOBuffer和String共享相同的内存。 replace
函数写入IOBuffer并处理String,因此这使我们有机会在没有任何分配的情况下执行replace!
。
一系列replace!
语句可以在两个预先分配的缓冲区之间交替,并且(几乎)避免任何分配。
更具体地说:
# version 0.6.0-pre.alpha.220
function replace!(out::IOBuffer, str::AbstractString,
pattern, repl, limit::Integer)
n = 1
e = endof(str)
i = a = start(str)
r = search(str,pattern,i)
j, k = first(r), last(r)
out.size = 0
out.ptr = 1
while j != 0
if i == a || i <= k
Base.unsafe_write(out, pointer(str, i), UInt(j-i))
Base._replace(out, repl, str, r, pattern)
end
if k<j
i = j
k = nextind(str, j)
else
i = k = nextind(str, k)
end
if j > e
break
end
r = search(str,pattern,k)
j, k = first(r), last(r)
n == limit && break
n += 1
end
write(out, SubString(str,i))
return out.ptr-1
end
为了驱动这个功能并预先分配缓冲区,我们做了:
const string1 = "."^1000
const string2 = "."^1000
const io1 = IOBuffer(convert(Vector{UInt8},string1),true,true,length(string1))
const io2 = IOBuffer(convert(Vector{UInt8},string2),true,true,length(string2))
write(io1,"hello world. "^30) # test string to manipulate
function switcheroo!()
replace!(io2,SubString(string1,1,io1.ptr-1),"hello","what's up",0)
replace!(io1,SubString(string2,1,io2.ptr-1),"what's up","hello",0)
end
switcheroo!()
功能switcheroo!
将hello
替换为whats up
,然后再返回。修改代码以处理一系列replace
很简单,因为问题表明是必需的。
注意:
这与Julia String内部结构更加紧密相关,因此未来Julia版本可能不会稳定(但可能会保留有效的替代品)。
对于创建的两个SubString值,每replace!
仍有两个(pesky!)分配。这些分配是64字节,不会随字符串大小增长。