Skip to content

norm(a) could be a lot faster #1650

Description

@stevengj

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!

Image
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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Fields

    No fields configured for Enhancement.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions