@CameronBieganek @kristoffer.carlsson Thank you for your help, but I am afraid we still do not get the true reason. Let’s play it together. I tried to inspect the code by “lower” it. Something interesting happened.
@code_lowered m[:, 1] = x
1 ─ nothing
│ %2 = Base.IndexStyle(A)
│ %3 = Core.tuple(%2, A)
│ Core._apply_iterate(Base.iterate, Base.error_if_canonical_setindex, %3, I)
│ %5 = Base.IndexStyle(A)
│ %6 = Core.tuple(%5, A, v)
│ %7 = Base.to_indices(A, I)
│ %8 = Core._apply_iterate(Base.iterate, Base._setindex!, %6, %7)
└── return %8
However, the “dot” one cannot be inspected as above.
@code_lowered m[:, 1] .= x
Error: expression is not a function call, or is too complex for @code_lowered to analyze; break it down to simpler parts if possible. In some cases, you may want to use Meta.@lower.
As suggested by the error message, I then used the
Meta.@lower m[:, 1] .= x
@ none within `top-level scope'
1 ─ %1 = Base.dotview(m, :, 1)
│ %2 = Base.broadcasted(Base.identity, x)
│ %3 = Base.materialize!(%1, %2)
└── return %3
Now it becomes clear that the dot syntax triggered broadcasting, which was then translated to totally different operations internally. Nevertheless, I thought the compiler should be smart enough to treat the two syntaxes identically.
In summary, the non-dot version resorts to
Core._apply_iterate, while the dot one turns to Base.broadcasted. The time difference implies some magic behind broadcasting that leads to more optimized machine code. However, I do not know any further details in the deeper level behind the two. Hope this finding can provide some clues.