Hello,
I found I use statements like this a lot:
A = func(A)
with
func
a function returning a array of the same size.
Is this efficient? Or there is a better way to write this kind of things?
Thanks.
Hello,
I found I use statements like this a lot:
A = func(A)
with
func
a function returning a array of the same size.
Is this efficient? Or there is a better way to write this kind of things?
Thanks.
if type of A
changed, please use let A = func(A)
to update symbol for better performance.
Also, if original A is not used any more and you want a same-length and same-type array with specific initial value, use fill!
and no need to assign here.
There is no need for this anymore.
It is just as effective as B = func(A)
. If you want to reuse the memory of A
you need to update it in place, in Juila functions like that conventionally end with an exclamation mark, e.g.
function func!(A)
# modify A
end
A = ... # initialize
func!(A)
func!(A) # reuse memory of A
Incredible. If it also applys when A
is a free variable shared to others, I think this optimization is very advanced and cool.
Thank you.
I use Python to do numerical computation.
Trying to learn Julia, I found some basics are quite different.
So this may be stupid question.
If I define a function like
function func!(a)
# change a
end
and call func!(A)
, then if A
is an array, the values in A
get changed.
But if A
is just a variable, the value is not changed.
Is that right?
First, there’s no distinction between A “is an array” vs A “is just a variable”.
If you call func!(A)
and you mutate A
, the change will be reflected in the A
in the caller.
If you call func!(A)
and you assigned to A
in func!
, the change will not be reflected in the caller.
This is exactly the same as python.
Here’s an example showing how Julia’s behavior is exactly the same as Python’s when it comes to assignment vs. mutation:
Python:
In [1]: def f(x):
...: x = [1, 2, 3]
...:
In [2]: x = [0, 0, 0]
In [3]: f(x)
In [4]: x
Out[4]: [0, 0, 0]
In [5]: def g(x):
...: x[0] = 1
...: x[1] = 2
...: x[2] = 3
...:
In [6]: g(x)
In [7]: x
Out[7]: [1, 2, 3]
Julia:
julia> function f(x)
x = [1, 2, 3]
end
f (generic function with 1 method)
julia> x = [0, 0, 0]
3-element Array{Int64,1}:
0
0
0
julia> f(x)
3-element Array{Int64,1}:
1
2
3
julia> x
3-element Array{Int64,1}:
0
0
0
julia> function g(x)
x[1] = 1
x[2] = 2
x[3] = 3
end
g (generic function with 1 method)
julia> g(x)
3
julia> x
3-element Array{Int64,1}:
1
2
3
In both languages, the function f(x)
assigns a new value to the name x
within the function. This has no effect on the value x
which was passed in. The function g(x)
, on the other hand, actually mutates the value, and we can see that mutation in the value x
which was passed in.
As @yuyichao said, this has nothing to do with the fact that x
is an array, and is only affected by the difference between assigning a new value vs. mutating an existing value.
The above replies by @yuyichao and @rdeits have already given you a good explanation of what happens in each case. Here is a concrete example to answer your question directly.
julia> function func(A)
B = sin.(A ./ 3)
end
func (generic function with 1 method)
julia> function func!(A)
@. A = sin(A / 3)
end
func! (generic function with 1 method)
julia> using BenchmarkTools
julia> @btime func($A);
5.428 ms (2 allocations: 7.63 MiB)
julia> @btime func!($A);
2.761 ms (0 allocations: 0 bytes)
Thank you for all the clarification.
Now I know the difference between assignment and mutation.
Can I ask what @. A = sin(A / 3)
means?
Thank you.
@.
is a macro which replaces every function with an element-wise version of the function. So this replaces every element of the stay with the sin of 1/3rd of the element.
See More Dots: Syntactic Loop Fusion in Julia for more on @.
and broadcasting.
Is it equivalent to A .= sin.(A ./ 3)
and thus is a mutation instead of assignment?
Yes
If you ever wonder what a macro is doing, two approaches can help.
The first is typing ?
in the repl, to enter help mode.
help?> @.
@. expr
Convert every function call or operator in expr into a "dot call" (e.g. convert f(x) to f.(x)), and convert every assignment in expr to a "dot assignment" (e.g. convert += to .+=).
If you want to avoid adding dots for selected function calls in expr, splice those function calls in with $. For example, @. sqrt(abs($sort(x))) is equivalent to sqrt.(abs.(sort(x))) (no dot for sort).
(@. is equivalent to a call to @__dot__.)
Examples
≡≡≡≡≡≡≡≡≡≡
julia> x = 1.0:3.0; y = similar(x);
julia> @. y = x + 3 * sin(x)
3-element Array{Float64,1}:
3.5244129544236893
4.727892280477045
3.4233600241796016
This works for anything that someone wrote documentation for. That includes most things in Base Julia and many libraries (including the standard libraries). For example:
julia> using LinearAlgebra
help?> mul!
search: mul! rmul! lmul! accumulate! muladd vmul vmuladd widemul accumulate module Module mutable struct @__MODULE__ baremodule parentmodule NamedTuple isimmutable promote_rule SegmentationFault
mul!(Y, A, B) -> Y
Calculates the matrix-matrix or matrix-vector product AB and stores the result in Y, overwriting the existing value of Y. Note that Y must not be aliased with either A or B.
For macros specifically, there is also the helpful macro @macroexpand
:
julia> @macroexpand @. A = sin(A / 3)
:(A .= sin.((/).(A, 3)))
Which expands any macros so you can see what they’re doing to the code. It’ll expand all macros in the expression that follows.
julia> @macroexpand @time @. A = sin(A / 3)
quote
#= util.jl:158 =#
local var"#7#stats" = Base.gc_num()
#= util.jl:159 =#
local var"#9#elapsedtime" = Base.time_ns()
#= util.jl:160 =#
local var"#8#val" = (A .= sin.((/).(A, 3)))
#= util.jl:161 =#
var"#9#elapsedtime" = Base.time_ns() - var"#9#elapsedtime"
#= util.jl:162 =#
local var"#10#diff" = Base.GC_Diff(Base.gc_num(), var"#7#stats")
#= util.jl:163 =#
Base.time_print(var"#9#elapsedtime", (var"#10#diff").allocd, (var"#10#diff").total_time, Base.gc_alloc_count(var"#10#diff"))
#= util.jl:165 =#
Base.println()
#= util.jl:166 =#
var"#8#val"
end
In case you find this hard to read, the library MacroTools
has (among many other useful tools) the function prettify
, which makes them much more readable:
julia> using MacroTools
julia> prettify(@macroexpand @time @. A = sin(A / 3))
quote
local tapir = Base.gc_num()
local camel = Base.time_ns()
local guanaco = (A .= sin.((/).(A, 3)))
camel = Base.time_ns() - camel
local hippopotamus = Base.GC_Diff(Base.gc_num(), tapir)
Base.time_print(camel, hippopotamus.allocd, hippopotamus.total_time, Base.gc_alloc_count(hippopotamus))
Base.println()
guanaco
end
Thanks for all replies. They are very helpful.
Julia has an excellent community.