-
Notifications
You must be signed in to change notification settings - Fork 102
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
use Ref, not preallocated arrays, for passing parameters by reference #44
Conversation
See also the As long as you're updating this, could you fix these to be thread-safe? Should be as simple as:
and then replace |
Thanks @stevengj for the suggestion, will do. |
One thing that worries me is how my suggestion will interact with precompiling, however: we don't want it to use We may have to do |
@stevengj The arrays are constant, not sure why those need to be set based on the thread size? I updated the code to get rid of the |
The current code is not thread-safe. Imagine that two threads call One solution is to make sure that each thread uses a separate memory location to pass variables by reference. To do this with a pre-allocated array, you have to allocate Another solution would be to not pre-allocate arrays, but rather to allocate new (An even cleaner solution would be possible if Julia had language-level support for thread-local storage, or if becomes cheaper to allocate |
src/bessel.jl
Outdated
Ptr{Float64}, Ptr{Float64}, Ptr{Int32}, Ptr{Int32}), | ||
rz1, rz2, rid, rkode, | ||
ai1, ai2, ae1, ae2) | ||
if ae2[] == 0 || ae2[] == 3 # ignore underflow and less than half machine accuracy loss |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hopefully, the performance penalty for allocating new Ref
variables for each call, rather than using pre-allocated arrays, is negligible. Have you benchmarked it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes on master they are equivalent (thanks to yuyichao's work) and this avoids the thread issues you have mentioned.
julia> @btime __airy(z,id,kode) # version in this PR
680.412 ns (1 allocation: 32 bytes)
-2.197881420594205e-53 - 1.6155839776764676e-53im
julia> @btime _airy(z,id,kode)
683.544 ns (1 allocation: 32 bytes)
-2.197881420594205e-53 - 1.6155839776764676e-53im
# Commit 952dc93489* (2017-08-02 23:54 UTC) (right before BenchmarkTools broke on master)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will update the rest of the ccall
's soon.
src/bessel.jl
Outdated
|
||
ccall((:zbiry_,openspecfun), Void, | ||
(Ptr{Float64}, Ptr{Float64}, Ptr{Int32}, Ptr{Int32}, | ||
Ptr{Float64}, Ptr{Float64}, Ptr{Int32}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These should be Ref
, not Ptr
. (Using Ptr
here is probably a relic of the days before Ref
.)
Once you change it to Ref
, then you will no longer need to explicitly create Ref
objects for parameters like kode
that are purely inputs. You can just pass kode
directly, and ccall
will create the Ref
for you. You only need to explicitly create Ref
objects for output parameters like ai1
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you. I have fixed them all up.
src/bessel.jl
Outdated
return z | ||
rx = Ref(x) | ||
rz = Ref(BigFloat()) | ||
ccall((:mpfr_ai, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Int32), rz, rx, ROUNDING_MODE[]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly, use Ref
here and pass x
, not rx
.
src/bessel.jl
Outdated
|
||
ccall((:zairy_,openspecfun), Void, | ||
(Ref{Float64}, Ref{Float64}, Ref{Int32}, Ref{Int32}, | ||
Ptr{Float64}, Ptr{Float64}, Ptr{Int32}, Ptr{Int32}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The last four arguments should be Ref
too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There doesn't seem to be any difference in doing so, but will change since it seems more consistent
julia> f(x::Float64) = Base.unsafe_convert(Ref{Float64}, Base.cconvert(Ref{Float64},Ref{Float64}(x)))
julia> g(x::Float64) = Base.unsafe_convert(Ptr{Float64}, Base.cconvert(Ptr{Float64},Ref{Float64}(x)))
julia> @code_llvm f(2.)
; Function f
; Location: REPL[18]
; Function Attrs: uwtable
define double* @julia_f_63230(double) #0 {
top:
%1 = alloca i64, align 8
%2 = bitcast i64* %1 to i8*
%3 = bitcast i64* %1 to %jl_value_t*
call void @llvm.lifetime.start(i64 8, i8* %2)
; Location: REPL[18]:1
%4 = bitcast %jl_value_t* %3 to double*
ret double* %4
}
julia> @code_llvm g(2.)
; Function g
; Location: REPL[19]
; Function Attrs: uwtable
define double* @julia_g_63231(double) #0 {
top:
%1 = alloca i64, align 8
%2 = bitcast i64* %1 to i8*
%3 = bitcast i64* %1 to %jl_value_t*
call void @llvm.lifetime.start(i64 8, i8* %2)
; Location: REPL[19]:1
%4 = bitcast %jl_value_t* %3 to double*
ret double* %4
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure If I should change this or not, in the example here @yuyichao uses a Ptr
for arg type for the Ref in the example JuliaLang/julia#22684
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both Ptr
and Ref
work, but Ref
is the recommended style for pass-by-reference arguments. I normally would only use Ptr{T}
for passing pointers to arrays (or raw pointers, of course).
@@ -499,7 +509,7 @@ Bessel function of the first kind of order 0, ``J_0(x)``. | |||
""" | |||
function besselj0(x::BigFloat) | |||
z = BigFloat() | |||
ccall((:mpfr_j0, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &x, ROUNDING_MODE[]) | |||
ccall((:mpfr_j0, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Int32), z, x, ROUNDING_MODE[]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why doesn't z
need to be a Ref
here, since, since it is modified by the ccall
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused z
is a Ref
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
z
is being passed by reference, and is modified by mpfr_j0
. z
is copied to a Ref(z)
container by ccall
when it is passed to the Ref{BigFloat}
argument, and the mpfr_j0
actually modifies this copy. I think the reason is the original z
is still modified is because a BigFloat
object contains a pointer to the actual "limb" data, and this raw pointer was copied over to Ref(z)
, so z
still sees the modified limbs.
Still, it seems like it would be clearer if we initialized z = Ref(BigFloat())
and then returned z[]
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed (yeah the logic is a bit confusing with all the cconverts and unsafe_converts that occur....)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So If I understand correctly the previous usage also had the same confusing behavior i.e. when passing &z
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
z is being passed by reference, and is modified by mpfr_j0 .
Yes.
z is copied to a Ref(z) container by ccall when it is passed to the Ref{BigFloat} argument, and the mpfr_j0 actually modifies this copy.
No.
I think the reason is the original z is still modified is because a BigFloat object contains a pointer to the actual "limb" data, and this raw pointer was copied over to Ref(z), so z still sees the modified limbs.
So No.
Still, it seems like it would be clearer if we initialized z = Ref(BigFloat()) and then returned z[].
The two do the same and both mutate the origional BigFloat
. Wrapping in Ref
makes this less clear.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see...so when you pass an instance z
of a mutable type for a Ref
argument then it actually passes a pointer to the original data in z
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct. It always pass an pointer to a memory with the same layout as the object being Ref
d (don't know how to describe this but hopefuly that's unambiguous). For !isbits
objects this is the object itself and for a copy for isbits
ones.
No description provided.