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.

Glad it’s fixed on master.