-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature Request: Static Variables #12627
Comments
I imagine the official way to do this is with closures, although that currently incurs a performance penalty if you're not careful. That said, I have sometimes found myself wanting this feature, even if it might be redundant with other existing features. |
This would remove a lot of my global consts sitting just above the method definition. |
macro static(var::Symbol, init)
eval(current_module(), :($var = $init))
var = esc(var)
quote
global $var
$var
end
end |
don't listen to Jameson |
Does that solve the type instability problem if several version of the function are compiled? |
haha. good advice (as Oscar points out, I didn't every mark it const, gensym, or type-assert) whether a function is compiled is orthogonal to its type signature. |
What I meant (and didn't say very well), was what happens if the macro is run twice with the same arguments. For example: function do_work(val::Number)
@static(:work_vec, Array(typeof(val), 50) )
... do calculation here...
end
do_work(1)
do_work(1.0) |
Can be done pretty easily with generated functions: @generated f(x) = quote
a = $(Array{x}(2))
a[2] = rand(typeof(x))
a[1] = x
return a
end Here julia> a = f(1)
2-element Array{Int64,1}:
1
-6333601391516269775
julia> b = f(2)
2-element Array{Int64,1}:
2
-5064160828035242615
julia> c = f(1.5)
2-element Array{Float64,1}:
1.5
0.596989
julia> d = f(2.5)
2-element Array{Float64,1}:
2.5
0.339789
julia> a === b
true
julia> c === d
true
julia> a === c
false Given that it's so easy to simulate, it seems like adding a specific feature isn't warranted. |
You can do the same thing with just @eval function f(x)
a = $(Array{Float64}(2))
a[2] = rand()
a[1] = x
return a
end This allocates a single shared array: julia> a = f(1)
2-element Array{Float64,1}:
1.0
0.401537
julia> b = f(2)
2-element Array{Float64,1}:
2.0
0.392203
julia> c = f(3.5)
2-element Array{Float64,1}:
3.5
0.229555
julia> a === b === c
true Given that this is fairly common, and differs significantly in exact implementation depending on what parts of a method signature the type of the object to be statically allocated depends on, perhaps it would make sense to have a language feature for it. |
Works for me, although it does make the language a bit less approachable if you have to get into generated functions to do something that is both conceptually simple and easy to do in C |
C doesn't have parametric methods so it's not really an apples to apples comparison. |
Fair point. I'm ready to close the issue if you are. |
There may be enough here to warrant a feature – the trick would be to make the static value specific to precisely the parts of the methods signature that it depends on and not the others. Let's see what some other people think (@JeffBezanson and @Keno are traveling, but may well have opinions). |
One thing you can do is use a let block and define the generic function inside. But yeah something that is different for every specialization is both conceptually and technically tricky. |
Well, the |
I would make make the allocation per full method signature. If the variable is meant to be shared between different methods, then the caller should allocate it and pass it as an argument. |
That does seem simplest. I worry that it will be surprising if the variable doesn't depend on the signature and you get different ones with different signatures. But then you could always do the let thing. |
In C, you would not do this with a static variable, you would do it with a stack-allocated variable. This is something that will done automatically by the compiler (see PR #8134 and #12205), probably in the near future. also, don't listen to Stefan, his suggestion is worse than mine :P
macros are only run once (after parsing) |
If such a feature was added, it would be worth considering how that would interact with threads. Would there be options to make this thread-local, or would it be allocated once and then the function has to manually synch access, or something else? |
This is a good point. With threads, we will want to avoid static allocation and should prefer stack allocation anyway. So probably best not to add features that make it easier to write thread unsafe code. |
It's not fully clear whether this discussion is about an optimization (avoiding "allocate a new one every time"), or static variables in the C sense where the value persists and is shared by all function invocations. A feature that gives you a separate static variable for each compilation of a method (as described in the OP) is extremely strange and ill-advised. A generated function is probably the only reasonable way to get that, but it's still a very bad idea to have generated functions allocate persistent state. |
A distinct static variable for each type signature is exactly what The fact that Julia compiles methods with different type signatures separately is unrelated and probably just a red herring. @JaredCrean2, if you really care about this feature then you should re-open this issue and clarify the issues discussed above. |
I don't like the idea of making too much specialization behavior "user-visible". After all, we want to keep the possibility of dialing down the degree to which we specialize functions in the future. |
Jeff's distinction between optimization and and variables associated with function compilations (C style static variables) is a good one, and the two points were mixed in the original post. Lets say this issue is about C style static variables. The |
That being said, @davidanthoff 's threading point is also an important consideration |
If so, then you still need to provide a motivating use case for it. |
Wow, that's awful. I kind of see why it makes sense for C++, especially when the type of the static variable depends on template parameters. However, I don't see why you would want to share persistent state only among function invocations with the same template parameters. That just seems crazy. |
Here is a use case: function accDiffNorm{T}(arr1::AbstractArray{T}, arr2::AbstractArray{T}, tmp::AbstractArray{T})
# accumulate a running difference of two vectors
# works for any data type
for i=1:length(arr1)
tmp[i] += arr1[i] - arr2[i] # running sum
end
return norm(tmp)
end
function getVecs(t::DataType)
return rand(t, 4), rand(t,4)
end
t = Float64
tmp = zeros(t, 4) # the caller has no interest in this variable
nrm = zero(t)
for i=1:3
a,b = getVecs(t)
nrm = accDiffNorm(a, b, tmp)
end
println("nrm = ", nrm)
t = Complex128
tmp2 = zeros(t, 4) # need a different temporary variable
nrm2 = zero(t)
for i=1:3
a,b = getVecs(t)
nrm2 = accDiffNorm(a, b, tmp2)
end
println("nrm2 = ", nrm2) Using static variables, each compiled version of the function would have its own |
changing to static allocation would be completely wrong there. each version of the function is not a sensible distinction since the three arguments could be any subtype of AbstractArray so it is not well defined what calls are being accumulated. a better formulation of that example would be track the state in a Type or a closure if the goal is to hide the temporary array. |
@vtjnash is right. The caller might not want to think about the temporary variable, but to use this function you have to think about it. In particular, when the |
I still think there exists a good use case for static variables, but until I can come up with one I'm set to close the issue |
I have a use case for this but if there is a better way, do tell. I want to use preallocated temporary arrays in a function. However, I also want to be able to do automatic differentiation using Maybe Stephan's way of using a generated function to achieve this is a good idea? |
I already addressed both of those questions in #12627 (comment) |
Thanks for the link. Glad to see it is in the pipeline. |
Using |
we now have one. the following expression lowers to the let a = Array{Float64}(2)
global f
function f(x)
a[2] = rand()
a[1] = x
return a
end
end |
I would find this potentially useful. The use case I have in mind is fairly similar to above example. Although, one problem with that example (and many of my use cases and also Kristoffer's) is that even if the array-type of A few notes: The let-block example of the previous post does not work when the array type depends on the argument types. Stack allocated would probably be ideal for smaller arrays but not so good for big ones (although my knowledge of those things is very limited). Stefan's generated function probably works, apart from the issue with the array size, provided it still passes Jameson's razor. Last, a x-ref: https://discourse.julialang.org/t/const-static-function-parameters/1803 |
Is that a link to a comment in which you link to another comment? Maybe a quote? |
"also, don't listen to Stefan, his suggestion is worse than mine :P" :P 😜 |
😐 |
As I have been programming in Julia, I have run into a situation where I want to use a temporary (usually an array) for use every time a function, but don't want to allocate a new one every time. In C, this would be easily done using a
static
variable. In Julia, something similar could be accomplished using global variables, but if the type of the static variable depends on the type of the function arguments, this causes type instability.So, the request is for static variables where each compiled method of the function has its own variable, whose type is determined by type inference.
The text was updated successfully, but these errors were encountered: