I have written a very basic algorithm in Julia Studio (Julia 0.2.0, OSX 10.8.2) that calculates the average mana left on each turn for a given mana curve in Hearthstone. When done with the algorithm I added type declarations to all the variables, thinking that this would help increase the overall speed. Suprise! The added type declarations made the code run more than 4x slower (from ~7s up to ~28s). What is causing this weird behaviour, and how can I fix it? It feels like adding types should help the compiler produce faster code, or at the very least make no difference.
Here is the code without type declarations (run time 6.76s):
function all_combinations(n)
result = Array{Int64}[]
for x in [1:n]
append!(result, collect(combinations(1:n,x)))
end
return result
end
curve = [2, 3, 4, 5, 5, 4, 3, 2, 1, 1]
games = Array{Int64}[]
function execute()
for game_n in [1:5000]
deck = mapreduce(
(x) -> fill(x[1], x[2]),
append!,
enumerate(curve))
function drawcard()
card = splice!(deck, rand(1:length(deck)))
end
hand = [drawcard() for n in [1:3]]
turn_leftovers = Int64[]
for mana in [1:10]
push!(hand, drawcard())
possible_plays = all_combinations(length(hand))
map!(
play -> map(i -> hand[i], play),
possible_plays)
filter!(x -> sum(x) <= mana, possible_plays)
if !isempty(possible_plays)
play = reduce(
(a, b) -> sum(a) > sum(b) ? a : b,
possible_plays)
for card in play
splice!(hand, findfirst(hand, card))
end
push!(turn_leftovers, mana - sum(play))
else
push!(turn_leftovers, mana)
end
end
push!(games, turn_leftovers)
end
end
println(@elapsed execute())
println("Averaging over $(length(games)) games")
for turn in [1:length(games[1])]
avrg = mean(map(game -> game[turn], games))
println("Left on turn $turn: $avrg")
end
println("Average mana leftover: $(mean(reduce(vcat, games)))")
println("Done")
And here is the code with type declarations (run time 28.48s):
function all_combinations(n)
result = Array{Int64}[]
for x in [1:n]
append!(result, collect(combinations(1:n,x)))
end
return result
end
curve::Array{Int64} = [2, 3, 4, 5, 5, 4, 3, 2, 1, 1]
games = Array{Int64}[]
function execute()
for game_n::Int64 in [1:5000]
deck::Array{Int64}
deck = mapreduce(
(x) -> fill(x[1], x[2]),
append!,
enumerate(curve))
function drawcard()
card::Int64 = splice!(deck, rand(1:length(deck)))
end
hand::Array{Int64}
hand = [drawcard() for n in [1:3]]
turn_leftovers::Array{Int64}
turn_leftovers = Int64[]
for mana::Int64 in [1:10]
push!(hand, drawcard())
possible_plays::Array{Array{Int64}} = all_combinations(length(hand))
map!(
play -> map(i::Int64 -> hand[i], play),
possible_plays)
filter!(x::Array{Int64} -> sum(x) <= mana, possible_plays)
if !isempty(possible_plays)
play::Array{Int64} = reduce(
(a::Array{Int64}, b::Array{Int64}) -> sum(a) > sum(b) ? a : b,
possible_plays)
for card::Int64 in play
splice!(hand, findfirst(hand, card))
end
push!(turn_leftovers, mana - sum(play))
else
push!(turn_leftovers, mana)
end
end
push!(games, turn_leftovers)
end
end
println(@elapsed execute())
println("Averaging over $(length(games)) games")
for turn in [1:length(games[1])]
avrg = mean(map(game -> game[turn], games))
println("Left on turn $turn: $avrg")
end
println("Average mana leftover: $(mean(reduce(vcat, games)))")
println("Done")
It could be worth noting that even the fastest version is a bit slower than the equivalent code written in JavaScript. This is probably only because of the lousy implementation, though. I have no doubt a better algorithm would outshine JS any day of the week.