I am doing some work to optimize a slow function call.
I have reduced this function down to a simple demonstration example.
Here is the “original function”.
# alloc: 668.59k, 393.649 MiB
function demo(
dict::Dict{Symbol, Vector},
datetime_first::DateTime,
datetime_last::DateTime,
)
# 1: unneccessary copy
# 2: no type hint to destination variable, despite type hint on constructor
time = Vector{DateTime}(dict[:time])
data = Vector{Float32}(dict[:data])
# println("typeof(time)=$(typeof(time))")
# println("typeof(data)=$(typeof(data))")
sum_value = 0.0f0
sum_count = 0
for ii in 1:length(time)
if time[ii] >= datetime_first
if time[ii] < datetime_last
sum_value += data[ii]
sum_count += 1
end
end
end
average_value = sum_value / sum_count
return average_value
end
And here is the “fixed” version
# alloc: 615.09 k, 9.386 MiB
function demo_fixed(
dict::Dict{Symbol, Vector},
datetime_first::DateTime,
datetime_last::DateTime,
)
# 1: unneccessary copy
# 2: no type hint to destination variable, despite type hint on constructor
time = dict[:time]
data = dict[:data]
# println("typeof(time)=$(typeof(time))")
# println("typeof(data)=$(typeof(data))")
sum_value = 0.0f0
sum_count = 0
for ii in 1:length(time)
if time[ii] >= datetime_first
if time[ii] < datetime_last
sum_value += data[ii]
sum_count += 1
end
end
end
average_value = sum_value / sum_count
return average_value
end
Here’s a main function which calls both.
function (@main)(args)
time = collect(DateTime("2024-01-01T00:00:00"):Dates.Second(1):DateTime("2024-01-01T23:59:59"))
data = rand(Float32, 100000000)
println(typeof(data)) # Vector{DateTime}
println(typeof(time)) # Vector{Float32}
dict = Dict(:time => time, :data => data)
datetime_first = DateTime("2024-01-01T07:00:00")
datetime_last = DateTime("2024-01-01T23:00:00")
println("demo")
@time demo(dict, datetime_first, datetime_last)
println("demo_fixed")
@time demo_fixed(dict, datetime_first, datetime_last)
return nothing
In terms of time, the difference is about 2.3 seconds vs 45 milliseconds.
I thought most of this time might be used by memory allocations, and some latency in asking the OS for memory. However the number of allocations is pretty much the same, so this does not make sense.
I added two further @time
statements to the original demo
function.
function demo(
dict::Dict{Symbol, Vector},
datetime_first::DateTime,
datetime_last::DateTime,
)
@time time = Vector{DateTime}(dict[:time])
@time data = Vector{Float32}(dict[:data])
The first of these takes almost zero time to run. It performs 3 allocations, a total of less than 1 MB.
The second of these is very slow. It also performs 3 allocations, but a total of 380 MB is copied.
This seems slightly strange. Both elements of the Dict are already in the correct memory layout. (Vector{DateTime}
and Vector{Float32}
.) However copying one is slow while the other is fast.