No, not at all. With few exceptions, the function signature has no effect on the performance of the code within that function.
No, it’s not like that. Julia does just-in-time (JIT) complilation. This means that the compilation of functions does not happen when you define them, but when you run them. And when you run anyfunc(a)
, the type of a
is always known!
So, in your first example:
function anyfunc(a::Int64)
...
end
that won’t be compiled until it is called for the first time. It’s exactly the same with:
function anyfunc(a)
...
end
The difference is that in this second version, if you do anyfunc(1.0)
it will compile a new, different code for Float64
arguments, while in the first version it will throw an error – unless you defined a different method that allows that type.
In general, performance is more affected by the code inside the function (e.g. ensuring type stability) than by the prior knowledge of the arguments types.
In addition to the excellent answers by others: I think you misunderstand how Julia’s compilation model works, which will make it difficult to write performant code. I would recommend reading the whole of
https://docs.julialang.org/en/v1/manual/performance-tips/
and then
IMO it is really worth investing in this.
Thank you, Tamas! I will certainly do so. My codes are already very performant, but one can always improve.
At the moment, I work completely within functions, do pre-allocation, use the “.” operator as needed, etc, all based on discourse discussions and reading documentation. But of course, there is understanding and there is grokking.
I do agree that I do not have a good understanding of how compilation work in Julia, but I do understand lazy compiling, late binding, etc
I will return …
I have not tested it yet, but I think that Dicts for parameters are slower than (concretely typed) structs:
- For each Dict access, the key needs to be hashed and a lookup performed. Lookups of values in structs should be much faster.
- If you have multiple parameter types, the Dict will no longer have a concrete value type (worst case Any), thus the type of any elements in this Dict cannot be determined at Compile-time anymore.
“2. If you have multiple parameter types, the Dict will no longer have a concrete value type (worst case Any), thus the type of any elements in this Dict
cannot be determined at Compile-time anymore.”
In that case, one solution is to have multiple dictionaries, one for each type. But it is still not fully clear.
If the function accesses specific elements of the dictionary in the function, wouldn’t the compiler know the types?
If access to the variables were via a loop, then the knowledge is lost. Ultimately, it is all about the level of sophistication of the compiler. I recognize that these issues can be quite difficult.
param = Dict(:a => 2.3, :b => 5)
function tst(p)
a = p[:a]
b = p[:b]
end
tst(param)
Doesn’t the compiler know the types of the variables a
and b
?
No. p
is a Dict{Symbol, Real}
, so the compiler can only assume that a
and b
are both some subtype of the abstract type Real
. This is easy to check for yourself:
julia> @code_warntype tst(param)
Variables
#self#::Core.Compiler.Const(tst, false)
p::Dict{Symbol,Real}
a::Real
b::Real
Body::Real
1 ─ (a = Base.getindex(p, :a))
│ %2 = Base.getindex(p, :b)::Real
│ (b = %2)
└── return %2
Given the entire program as you’ve written it, a hypothetical compiler could perform enough constant propagation to turn your entire code into return 5
, but the compiler we actually have isn’t going to do that for you.
Got it. Thanks! It does all make sense.
Gordon
Base.@kwdef
is really useful for the definition of parameter structures, it allows to define default values directly in the structure definition.
Sorry, my home lost electricity for the last eight hours. When I said:
This is a little crude, but why do you not just gives every method a last parameter called
params
[…]
I meant that my suggestion was a little crude, because it was vague and did not point the difference between each option. I was not referring to your comment.
I worry a little about your example:
a = 35
function anyfunc(a)
...
end
Seems like you think that there is a connection between the global variable a
and the parameter a
, the two of them only share the same name (and therefore, when you reference a
in your function it will always refer to the parameter, not the global variable), but they are two completely distinct things. They only become the same thing if you call anyfunc
passing as parameter the global a
.
Yes. I am absolutely sure of it. However, in an prototype phase, it can be more convenient to use a Dict
and do not worry about adding and removing fields (and changing the construction of the object) each time something changes. I was just suggesting things that work, with different degrees of flexibility and performance. I use a Dict
myself, because I get it from the argument parser, and the performance of accessing the parameters is completely irrelevant in my case (the code takes minutes to hours, most time from Gurobi solver, the access to parameters do not account for 0.01% of the total time, probably orders of magnitude less).
As already answered this is not guaranteed. However, if you use a Dict
you should probably never access a parameter repeatedly inside a loop, but instead do: param_name :: TypeOfParam = params_dict[:param_name]
before such heavy use of the parameter. As I said above, use a Dict
only if the convenience matters more, and you do not have to use a lot of the parameters each iteration of tight loops (you can unpack all values as I suggested above but at this point is probably better to just use an immutable struct
and access the fields directly).
6 posts were split to a new topic: Typeconst proposal