# Strange allocations?

Consider these two function definitions:

``````function myhypot(x::Number...)
s = sum(abs2, float.(x))
r = sqrt(s)
if isnan(s) || isinf(s) || s ≤ floatmin(s) * (length(x) - 1)
println("here")
any(isinf, x) && return oftype(r, Inf)
ma = maximum(abs.(x))
iszero(ma) && return oftype(r, ma)
return oftype(r, ma * sqrt(sum(abs2, x ./ ma)))
else
return r
end
end

function h(x::Number...)
s = sum(abs2, float.(x))
r = sqrt(s)
return r
end
``````

The two functions are equivalent if the branch condition is not met. I added a `println` statement to make sure that this branch was not being executed.

Here is an example where the branch is not entered. However I get very different allocations:

``````julia> @btime myhypot(4., 5., 6., 7.)
1.314 μs (13 allocations: 400 bytes)
11.224972160321824

julia> @btime h(4., 5., 6., 7.)
4.878 ns (0 allocations: 0 bytes)
11.224972160321824
``````

Both calls should be executing the same code. Why one allocates and the other doesn’t?

Well, at least I discover that the allocations are coming from the `isinf` function. If I change to `isodd`, then nothing is allocated:

``````function myhypot_test(x::Number...)
s = sum(abs2, float.(x))
r = sqrt(s)
if isnan(s) || isinf(s) || s ≤ floatmin(s) * (length(x) - 1)
any(isodd, x) && return oftype(r, Inf)
ma = maximum(abs.(x))
iszero(ma) && return oftype(r, ma)
return oftype(r, ma )
else
return r
end
end

@btime myhypot_test(4.,5.,6.,7.)
6.101 ns (0 allocations: 0 bytes)
11.224972160321824
``````

Bizarre!

Even stranger is:

``````function myhypot(x::Number...)
s = sum(abs2, float.(x))
r = sqrt(s)
if isnan(s) || isinf(s) || isinf(s)
isinf.(x)
return r
else
return r
end
end

julia> @btime myhypot(4.,5.,6.,7.)
1.365 μs (12 allocations: 384 bytes)
11.224972160321824

function myhypot(x::Number...)
s = sum(abs2, float.(x))
r = sqrt(s)
if isnan(s) || isinf(s)
isinf.(x)
return r
else
return r
end
end

julia> @btime myhypot(4.,5.,6.,7.)
4.254 ns (0 allocations: 0 bytes)
11.224972160321824

function myhypot(x::Number...)
s = sum(abs2, float.(x))
r = sqrt(s)
if isnan(s) || isinf(s) || isinf(s)
return r
else
return r
end
end

julia> @btime myhypot(4.,5.,6.,7.)
3.783 ns (0 allocations: 0 bytes)
11.224972160321824
``````

This is completely unexpected to me.

That is, ehm, odd:

``````julia> isodd(3.0)
ERROR: MethodError: no method matching isodd(::Float64)
``````
1 Like

Not sure what fixed it, but it no longer happens on master:

``````julia> function myhypot(x::Number...)
s = sum(abs2, float.(x))
r = sqrt(s)
if isnan(s) || isinf(s) || isinf(s)
isinf.(x)
return r
else
return r
end
end
myhypot (generic function with 1 method)

julia> @btime myhypot(4.,5.,6.,7.)
5.025 ns (0 allocations: 0 bytes)
11.224972160321824
``````
2 Likes

For some reason the splatting penalty seems to be kicking in very early in this case. If you replace the splatted argument `x` with four positional arguments and recreate the tuple `x` inside the function, there is no performance problem. If you make the argument `x` a tuple, things are also fine.