我正在維護一個 Julia 庫,其中包含一個函式,用于在長字串中每 80 個字符后插入一個新行。
當字串超過 100 萬個字符時,此函式會變得非常慢(秒或更多)。時間似乎不是線性增加的,也許是二次的。我不明白為什么。有人可以解釋嗎?
這是一些可重現的代碼:
function chop(s; nc=80)
nr = ceil(Int64, length(s)/nc)
l(i) = 1 (nc*(i-1))
r(i) = min(nc*i, length(s))
rows = [String(s[l(i):r(i)]) for i in 1:nr]
return join(rows,'\n')
end
s = "A"^500000
chop(s)
似乎這一行是花費大部分時間的地方:rows = [String(s[l(i):r(i)]) for i in 1:nr]
這是否意味著初始化一個新的需要很長時間String?這并不能真正解釋超線性運行時間。
我知道構建字串的規范快速方法是使用IOBuffer或更高級別的StringBuilders包:https ://github.com/davidanthoff/StringBuilders.jl
有人可以幫我理解為什么上面的代碼還是這么慢嗎?
奇怪的是,下面的速度要快得多,只需添加s = collect(s):
function chop(s; nc=80)
s = collect(s) #this line is new
nr = ceil(Int64, length(s)/nc)
l(i) = 1 (nc*(i-1))
r(i) = min(nc*i, length(s))
rows = [String(s[l(i):r(i)]) for i in 1:nr]
return join(rows,'\n')
end
更新:我分析了代碼,顯然最終減慢了很多的是length(s)函式中的呼叫r(i)。預先計算這個常數會大大加快速度。奇怪的。會不會是嵌套函式阻止了編譯器優化?畢竟,length(s)本身不應該是一個緩慢的操作。
這是更快的代碼:
function columns(s; nc=80)
L = length(s) #this line is new
nr = ceil(Int64, L/nc)
l(i) = 1 (nc*(i-1))
r(i) = min(nc*i, L)
rows = [String(s[l(i):r(i)]) for i in 1:nr]
return join(rows,'\n')
end
uj5u.com熱心網友回復:
String在 Julia 中是不可變的,如果您需要以這種方式處理字串,最好先創建一個Vector{Char},以避免重復分配新的大字串。
uj5u.com熱心網友回復:
您可以對位元組進行操作
function chop2(s; nc=80)
b = transcode(UInt8, s)
nr = ceil(Int64, length(b)/nc)
l(i) = 1 (nc*(i-1))
r(i) = min(nc*i, length(b))
dat = UInt8[]
for i in 1:nr
append!(dat, @view(b[l(i):r(i)]))
i < nr && push!(dat, UInt8('\n'))
end
String(dat)
end
和基準測驗(大約快 5000 倍):
@btime chop($s);
1.531 s (6267 allocations: 1.28 MiB)
julia> @btime chop2($s);
334.100 μs (13 allocations: 1.57 MiB)
筆記:
- 通過預分配,這段代碼仍然可以稍微快一點,
dat但我試圖與原始代碼相似。 - 當有 unicode 字符時,您的方法和這種方法都不起作用,因為您不能在中間剪切 unicode 字符
uj5u.com熱心網友回復:
我的偏好是使用通用的單線解決方案,即使它比 Przemys?aw 提出的要慢一些(我為了簡單而不是速度而對其進行了優化):
chop_and_join(s::Union{String,SubString{String}}; nc::Integer=80) =
join((SubString(s, r) for r in findall(Regex(".{1,$nc}"), s)), '\n')
好處是它可以正確處理所有 Unicode 字符,并且還可以與SubString{String}.
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/477273.html
