Would broadcast!
be an important one? Should have the same issue as map!
pointed out before, and I recall another aliasing frustration thread proposing automatic intermediate copies because they were broadcasting a reduction over axes including previous iterations. The tricky thing about this is in-place broadcasting is more often done with .=
.+=
expressions that are lowered to other function calls, not broadcast!
, and it’s a function without a !
, broadcasted
, that needs an alias check.
Was thinking about having a version without aliasing checks and a version that checks for aliasing then calls the first version. But then it occurs to me, can alias-handling even be a general call? As pointed out, sometimes aliasing doesn’t cause a different result from nonaliased inputs, depending on the function (and I can’t rule out the input types), and NumPy uses a heuristic to reduce some (not all) unnecessary copying rather than a straightforward deep copy upon detecting aliasing. But it doesn’t seem good to implement alias handling by call signature in a language with easy custom functions, types, and composition.
So I’m thinking maybe we can get away with simpler deep copies upon detecting aliasing, at least that doesn’t depend on the function, though the alias check depends on the input types. If incorporating this into alias-handling versions is too expensive (like if the call is in a loop), maybe it should just stay a separate manual step like sum!(unalias(a, a)...)
or a1, a2 = unalias(a, a); for _ in 1:1000 sum!(a1, a2) end
. This should probably exist anyway as an option if alias-handing versions don’t exist, yet or ever.
Tricky aspect of the first call is how to specify the results to not copy. It’s usually the first argument, but a mutating function can write to >1 inputs. If multiple destinations alias, it doesn’t even make sense to write results to them. Here’s a NumPy example of what happens if result arrays are aliased:
import numpy as np
# make a (slow) +- ufunc that broadcasts over arguments
def plusminus(x,y): return (x-y,x+y)
uplusminus = np.frompyfunc(plusminus, 2, 2) # 2 in, 2 out
# write 1+-1 to 2 aliased vectors
a = np.zeros(3)
a01 = a[:2]
a12 = a[1:]
uplusminus(1, 1, out = (a01, a12))
print((a01, a12))
# (array([0., 2.]), array([2., 2.]))
# write 1+-1 to 2 non-aliased vectors
b01 = np.zeros(2)
c01 = np.zeros(2)
uplusminus(1, 1, out = (b01, c01))
print((b01, c01))
# (array([0., 0.]), array([2., 2.]))
# ^ aliasing overwrites this element
As much as NumPy copies to avoid the effects of aliasing, it can’t do anything if you specify aliased result arrays.