Skip to content
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

Closed
JaredCrean2 opened this issue Aug 14, 2015 · 41 comments
Closed

Feature Request: Static Variables #12627

JaredCrean2 opened this issue Aug 14, 2015 · 41 comments

Comments

@JaredCrean2
Copy link
Contributor

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.

@johnmyleswhite
Copy link
Member

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.

@KristofferC
Copy link
Sponsor Member

This would remove a lot of my global consts sitting just above the method definition.

@vtjnash
Copy link
Sponsor Member

vtjnash commented Aug 14, 2015

macro static(var::Symbol, init)
  eval(current_module(), :($var = $init))
  var = esc(var)
  quote
    global $var
    $var
  end
end

@carnaval
Copy link
Contributor

don't listen to Jameson

@JaredCrean2
Copy link
Contributor Author

Does that solve the type instability problem if several version of the function are compiled?

@vtjnash
Copy link
Sponsor Member

vtjnash commented Aug 14, 2015

don't listen to Jameson

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.

@JaredCrean2
Copy link
Contributor Author

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)

@StefanKarpinski
Copy link
Sponsor Member

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 a is allocated when a method for a particular type of x is generated and then the same array is used whenever that method is called:

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.

@StefanKarpinski
Copy link
Sponsor Member

You can do the same thing with just eval, without generated functions if you only need a single static variable that doesn't depend on the method signature:

@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.

@JaredCrean2
Copy link
Contributor Author

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

@StefanKarpinski
Copy link
Sponsor Member

C doesn't have parametric methods so it's not really an apples to apples comparison.

@JaredCrean2
Copy link
Contributor Author

Fair point.

I'm ready to close the issue if you are.

@StefanKarpinski
Copy link
Sponsor Member

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).

@Keno
Copy link
Member

Keno commented Aug 15, 2015

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.

@StefanKarpinski
Copy link
Sponsor Member

Well, the @generated example shows that it could be a fairly straightforward syntactic translation given that we've already solved a harder problem. So if the static allocation is per full method signature, we can do this already. The question is if you want to allow making it specific to some subset (possibly empty) of the type parameters of a method and how to express that (implicitly, explicitly).

@JaredCrean2
Copy link
Contributor Author

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.

@StefanKarpinski
Copy link
Sponsor Member

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.

@vtjnash
Copy link
Sponsor Member

vtjnash commented Aug 15, 2015

In C, this would be easily done using a static variable

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

what happens if the macro is run twice with the same arguments

macros are only run once (after parsing)

@davidanthoff
Copy link
Contributor

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?

@StefanKarpinski
Copy link
Sponsor Member

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.

@JeffBezanson
Copy link
Sponsor Member

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.

@eschnett
Copy link
Contributor

A distinct static variable for each type signature is exactly what static does for C++ templates. C++ templated functions are also probably the closest thing to Julia's type signatures with which people are familiar. Thus the request doesn't seem strange and outlandish to me at all.

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.

@carnaval
Copy link
Contributor

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.

@JaredCrean2
Copy link
Contributor Author

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 static C++ templated function behavior described by @eschnett is what I am looking for.

@JaredCrean2
Copy link
Contributor Author

That being said, @davidanthoff 's threading point is also an important consideration

@vtjnash
Copy link
Sponsor Member

vtjnash commented Aug 20, 2015

If so, then you still need to provide a motivating use case for it.

@JeffBezanson
Copy link
Sponsor Member

A distinct static variable for each type signature is exactly what static does for C++ templates.

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.

@JaredCrean2
Copy link
Contributor Author

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 tmp array that is the right type and able to accumulate values across function calls.

@vtjnash
Copy link
Sponsor Member

vtjnash commented Aug 21, 2015

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.

@JeffBezanson
Copy link
Sponsor Member

@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 tmp variable gets zero'd massively influences the returned values. This is not per-type, but per norm-diff-accumulation session.

@JaredCrean2
Copy link
Contributor Author

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

@KristofferC
Copy link
Sponsor Member

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 ForwardDiff. If I could preallocate arrays of the correct type at the first call to the function then it should work with whatever T is in the Vector{T} that gets passed to the function.

Maybe Stephan's way of using a generated function to achieve this is a good idea?

@vtjnash
Copy link
Sponsor Member

vtjnash commented Nov 11, 2015

I already addressed both of those questions in #12627 (comment)

@KristofferC
Copy link
Sponsor Member

Thanks for the link. Glad to see it is in the pipeline.

@cossio
Copy link
Contributor

cossio commented Apr 2, 2016

Using @eval, all variables in the function scope behave as static, no? What if I want to have some non-static variables?

@vtjnash
Copy link
Sponsor Member

vtjnash commented Apr 2, 2016

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.

we now have one. the following expression lowers to the @eval form given above

let a = Array{Float64}(2)
global f
function f(x)
    a[2] = rand()
    a[1] = x
    return a
end
end

@mauro3
Copy link
Contributor

mauro3 commented Nov 7, 2017

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 tmp is fixed by the types of arr1 and arr2, its size would not be as that is fixed by the size of arr1 and arr2. Thus I don't see how it could work.

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

@vtjnash
Copy link
Sponsor Member

vtjnash commented Nov 7, 2017

#12627 (comment)

@StefanKarpinski
Copy link
Sponsor Member

Is that a link to a comment in which you link to another comment? Maybe a quote?

@vtjnash
Copy link
Sponsor Member

vtjnash commented Nov 7, 2017

"also, don't listen to Stefan, his suggestion is worse than mine :P" :P

😜

@StefanKarpinski
Copy link
Sponsor Member

😐

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests