Skip to content

Performance of LinearAlgebra.dot #132

@matbesancon

Description

@matbesancon

TL;DR: using dot or sum to construct a SAF leads to significant performance degradation compared to manually constructing the SAF from a loop comprehension. The "manual" version below does not use linear algebra functions, the non-manual one does, and hence calls MutableArithmetics.

Setting:

  [87dc4568] HiGHS v0.3.1
  [b8f27783] MathOptInterface v0.10.7

MWE:

using BenchmarkTools, SparseArrays
import MathOptInterface
const MOI = MathOptInterface
using HiGHS
using LinearAlgebra
using Random

function build_moi_model(n, optimizer = HiGHS.Optimizer())
    MOI.set(optimizer, MOI.Silent(), true)
    MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MIN_SENSE)
    (x, _) = MOI.add_constrained_variables(optimizer, fill(MOI.Interval(0.0, 1.0), n * n))
    xmat = reshape(x, n, n)
    for idx in 1:n
       # column constraint
       MOI.add_constraint(
           optimizer,
           sum(1.0 * xmat[:, idx]),
           MOI.EqualTo(1.0),
       )
       # row constraint
       MOI.add_constraint(
           optimizer,
           sum(1.0 * xmat[idx, :]),
           MOI.EqualTo(1.0),
       )
    end
    return optimizer
end

function moi_lmo_manual(direction, optimizer)
    n = size(direction, 1)
    variables = reshape(MOI.get(optimizer, MOI.ListOfVariableIndices()), n, n)
    obj = MOI.ScalarAffineFunction(
        [MOI.ScalarAffineTerm(direction[i], variables[i]) for i in eachindex(direction)],
        0.0,
    )
    MOI.set(optimizer, MOI.ObjectiveFunction{typeof(obj)}(), obj)
    MOI.optimize!(optimizer)
    v = spzeros(n, n)
    @inbounds for i in 1:n
        for j in 1:n
            vij = MOI.get(optimizer, MOI.VariablePrimal(), variables[i,j])
            if vij  1
                v[i,j] = vij
            end
        end
    end
    return v
end

function moi_lmo(direction, optimizer)
           n = size(direction, 1)
           variables = reshape(MOI.get(optimizer, MOI.ListOfVariableIndices()), n, n)
           obj = dot(direction, variables)
           MOI.set(optimizer, MOI.ObjectiveFunction{typeof(obj)}(), obj)
           MOI.optimize!(optimizer)
           v = spzeros(n, n)
           @inbounds for i in 1:n
               for j in 1:n
                   vij = MOI.get(optimizer, MOI.VariablePrimal(), variables[i,j])
                   if vij  1
                       v[i,j] = vij
                   end
               end
           end
           return v
end

function f(optimizer, manual)
           direction = zeros(n, n)
           if manual
               moi_lmo_manual(randn!(direction), optimizer)
           else
               moi_lmo(randn!(direction), optimizer)
           end
end

julia> @btime f($optimizer, true)
  15.907 ms (27 allocations: 552.14 KiB)

julia> @btime f($optimizer, false)
  24.955 ms (20034 allocations: 1.33 MiB)

Manual version:
image

The left 2/3 is on MA:

packages/MutableArithmetics/UtY4H/src/linear_algebra.jl:427, operate [inlined]
packages/MutableArithmetics/UtY4H/src/reduce.jl:34, MethodInstance for MutableArithmetics.fused_map_reduce(::typeof(MutableArithmetics.add_dot), ::Matrix{Float64}, ::Matrix{MathOptInterface.VariableIndex})

The split above is spent roughly 50-50 on these two:

/home/mbesancon/.julia/packages/MutableArithmetics/UtY4H/src/interface.jl:496, buffered_operate_fallback!! [inlined]
/home/mbesancon/.julia/packages/MutableArithmetics/UtY4H/src/interface.jl:188, mutability [inlined]

All the the time in the manual example is spent within the HiGHS lib call

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions