Based on #697 (comment):
Here is a simple implementation of the 2-norm that correctly scales in case of underflow, overflow, and subnormal quantities (updated, see below), but which seems to be way faster than our built-in norm function (which calls generic_norm2 for lengths < 32 and BLAS.nrm2 otherwise):
function mynorm2(x::AbstractArray) # could also work on iterators
isempty(x) && return float(sum(x))
norm² = sum(abs2 ∘ float, x)
if isfinite(norm²) & (norm² >= floatmin(norm²))
return sqrt(norm²) # fast path: no overflow/underflow or subnormals
else
scale = isinf(norm²) ? floatmin(sqrt(inv(norm²)))*4 : inv(floatmin(sqrt(norm²))*4)
return sqrt(sum(x -> abs2(x * scale), x)) / scale
end
end
Here is a benchmark, for both rand(n) arrays (no overflow or underflow) and randn(n) * 1e200 (overflow, which requires an extra pass over the array in mynorm2 but takes the same time for BLAS.nrm2 which apparently has no "fast path"). On my machine (Apple M4), it makes no difference whether I do BLAS.set_num_threads(1) or BLAS.set_num_threads(4). mynorm2 is nearly 10 times faster for many lengths!
Benchmark code
using BenchmarkTools, LinearAlgebra
n = sort!([5; 15; 25; 30; 10 .^ (0:7)])
t_mynorm2 = map(n) do n
@show n
a = rand(n)
@belapsed mynorm2($a)
end
t_mynorm2_overflow = map(n) do n
@show n
a = rand(n) * 1e200
@belapsed mynorm2($a)
end
BLAS.set_num_threads(1)
t_norm = map(n) do n
@show n
a = rand(n)
@belapsed norm($a)
end
t_norm_overflow = map(n) do n
@show n
a = rand(n)
@belapsed norm($a)
end
using Plots
plot(n, 1e-9 * n ./ t_mynorm2, label="mynorm2", marker=:star, color=:blue)
plot!(n, 1e-9 * n ./ t_mynorm2_overflow, label="mynorm2 (overflow)", marker=:star, color=:cyan)
plot!(n, 1e-9 * n ./ t_norm, label="norm", marker=:circle, color=:red)
plot!(n, 1e-9 * n ./ t_norm_overflow, label="norm (overflow)", marker=:circle, color=:magenta)
plot!(xaxis=:log10, xlabel="array length n", ylabel="speed (elements/ns)", legend=:outertopright,
size=(800,400), left_margin=2mm, bottom_margin=3mm)
Based on #697 (comment):
Here is a simple implementation of the 2-norm that correctly scales in case of underflow, overflow, and subnormal quantities (updated, see below), but which seems to be way faster than our built-in
normfunction (which callsgeneric_norm2for lengths< 32andBLAS.nrm2otherwise):Here is a benchmark, for both
rand(n)arrays (no overflow or underflow) andrandn(n) * 1e200(overflow, which requires an extra pass over the array inmynorm2but takes the same time forBLAS.nrm2which apparently has no "fast path"). On my machine (Apple M4), it makes no difference whether I doBLAS.set_num_threads(1)orBLAS.set_num_threads(4).mynorm2is nearly 10 times faster for many lengths!Benchmark code