Difference between m = function(m) and function!(m) where m is a JuMP Model instance

I’ve read the performance tips about building models using juMP and it suggests to use exclamatory mark instead of return value. Is there any performance difference between these two ways in terms of memory usage?

Where do we say this?

Note that ! isn’t special in Julia. It’s just a letter. (Although some people use it to mean that an argument is mutated.)

So naming a function foo! instead of foo does not make a difference. I could have named it foo_exclaimation and it would be the same thing.

A concrete example is maybe:

x = [1,2,3]

function square(x)
    return x .^ 2
end

function square!(x)
    @. x = x^2 
end

Look at the difference when calling

y = square(x)
println(x)
square!(x)
println(x)

the in-place version of a function should be marked with ! to tell others that you change the inputs. (But as noted, it is just a convention in Julia.) The in-place version also uses less memory, as it can reuse the memory of x. That’s why it is sometimes relevant.

See also: Style Guide

For the most part, the JuMP codebase does not use !. For example, we have set_lower_bound(x, value) instead of lower_bound!(x, value).

There is a valid question of: should I use a mutating function instead of one that returns a copy?

But that is a different question to the use of !.

1 Like

Taking my situation as an example, I created my JuMP model using return value with many functions, like

m = Model()
m = func1(m)
m = func2(m)
...
m = funcN(m)

Every function will take the model and modify it and return it. Will this consume more memory than the in-place version and is there an rough estimation about my memory overuse?

Provided you are not using copy(m) anywhere, there is no difference. They are exactly equivalent.

You can check that m1 = func1(m) and then m1===m.

Thanks for the reply. I used to regard the return version will create a copy and take more memory consumption than the in-place version. Since I’m trying to reduce the massive memory consumption of my models, it seems my previous thinking was in a wrong direction.

This is not true for any Julia code, regardless of JuMP. Julia does not pass by value.

The JuMP style guide does not recommend ! specifically because new users think it is special, when it really is just another letter, like a or Y. And the the “modifies its argument” rule is a heuristic that is not applied uniformly.

What is the message here?

f(x)=x.^2
a=rand(10)
b=f(a)
b===a
false
any(b.==a)
false

a and b are two different arrays with different elements. This is clearly different behavior than in-place modification.

This is clearly different behavior than in-place modification.

Yes. In your example, f(x) = x.^2 returns a new vector. It does not do in-place modification.

In @Betristor’s example, they are using in-place modifications on a JuMP.Model. No function in JuMP makes a copy of the model without calling copy(m). This is different to your x.^2 example.

I was thinking of code like this:

julia> function foo(x)
           x[1] = 2
           return x
       end
foo (generic function with 1 method)

julia> x = [1, 2, 3]
3-element Vector{Int64}:
 1
 2
 3

julia> y = foo(x)
3-element Vector{Int64}:
 2
 2
 3

julia> x
3-element Vector{Int64}:
 2
 2
 3

julia> x === y
true

My points were:

  • In-place modification has nothing to do with whether the function has a ! in its name.
  • Just having return x does not make a copy of the return value.

The in-place version of your f(x) example is:

julia> function foo_yvikhlya(x)
           x .^= 2
           return x
       end
foo_yvikhlya (generic function with 1 method)

julia> a = rand(10);

julia> b = foo_yvikhlya(a);

julia> b === a
true

julia> any(b .== a)
true
1 Like

Oh ok, got it now. It was not clear from the discussion above that only mutating functions are meant. Indeed, for mutating function x=f(x) and f(x) are equivalent, although if the name does not have !, it may be confusing for users who got used to julia naming convention.

1 Like