Difference between 'x.=y' and 'x=y'

I see codes like this

x.= x./maximum(abs.(x))

Here x is a vector. Personally, I would write

x = x./maximum(abs.(x))

Is there any significant difference between the two?

First one is broadcasted/elementwise assignment, which mutates the instance assigned to x, second one reassigns x entirely. You can use Meta.@lower to see how the expressions are lowered, and it tells you a bit of what is happening.
Before that, you should read a pretty good explanation of dots here:

And this gets into improved design and some of the implementation, though if you don’t have to care too much about it, you could just take away Meta.@lower for seeing the broadcast loops, and the fact that broadcasting can be customized away from the default of instantiating a full Array when some types allow for it, like elementwise addition of ranges.

4 Likes

Just to make it very clear to future readers that are novices in the language:

They are two completely different things.

The first ( x.= ...) changes each element of the already existing object denoted by x one-by-one with what is being computed. If you use multiple elements of x to compute each new value in x and you need to compute the new values solely based in the old values (without the new ones mixed in) then you may end up with bugs.

The second (x = ...) just creates a completely new object and starts using the name x for it.

EDIT: as others pointed out, if both are correct for you (i.e., there is no problem in overwriting the object denoted by x during the computation) then x .= ... is often preferable because it allocates less memory (reuses the existing object) but I would urge a novice to not care about premature optimization in this context (do it only if it is safe AND memory allocation shows to be a bottleneck).

9 Likes

It’s like altering your laptop to add the features that you want, versus buying a new model with those features inbuilt. Both get you the same result, but one is significantly more resource intensive than the other.

2 Likes
julia> Meta.@lower   x = f1.(x)
:($(Expr(:thunk, CodeInfo(
    @ none within `top-level scope`
1 ─ %1 = Base.broadcasted(f1, x)
β”‚   %2 = Base.materialize(%1)
β”‚   %3 = Core.get_binding_type(Main, :x)
β”‚   %4 = Base.convert(%3, %2)
β”‚   %5 = Core.typeassert(%4, %3)
β”‚        x = %5
└──      return %2
))))

julia> Meta.@lower   x .= f1.(x)
:($(Expr(:thunk, CodeInfo(
    @ none within `top-level scope`
1 ─ %1 = x
β”‚   %2 = Base.broadcasted(f1, x)
β”‚   %3 = Base.materialize!(%1, %2)
└──      return %3
))))

More dots aren’t always better. Keep then one in .=, but change the call to maximum like this:

x .= x ./ maximum(abs, x)

maximum(abs.(x)) will first create a temporary array and then find the max of that array, while maximum(abs, x) does not.

9 Likes

Could also be reduced to a nice single dot :slight_smile: :

x ./= maximum(abs, x)
5 Likes