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

Grisu (floating point printing) not thread-safe #25727

Closed
oschulz opened this issue Jan 24, 2018 · 11 comments · Fixed by #26562
Closed

Grisu (floating point printing) not thread-safe #25727

oschulz opened this issue Jan 24, 2018 · 11 comments · Fixed by #26562
Labels
multithreading Base.Threads and related functionality

Comments

@oschulz
Copy link
Contributor

oschulz commented Jan 24, 2018

It looks like string interpolation is currently not threadsafe. Running the following script "string_interpolation_threadsafe_test.jl"

using Base.Threads


function test_with_mutex()
	a = rand(1000)
	m = Mutex()
	@threads for i=1:1000
		y = lock(m) do
			"$(a[i])"
		end
		@assert a[i] == parse(Float64, y)
	end
end


function test_without_mutex()
	a = rand(1000)
	@threads for i=1:1000
		y = "$(a[i])"
		@assert a[i] == parse(Float64, y)
	end
end


function test_without_mutex_repeated()
	for i in 1:1000
		a = rand(1000)
		@threads for i=1:1000
			y = "$(a[i])"
		end
	end
end


@info "test_with_mutex()"
test_with_mutex()

@info "test_without_mutex()"
test_without_mutex()

@info "test_without_mutex_repeated()"
test_without_mutex_repeated()

via

JULIA_NUM_THREADS=4 julia string_interpolation_threadsafe_test.jl

with Julia 0.7.0-DEV.3551 (x86_64, official binary, on Ubuntu 16.04) results in

[ Info: test_with_mutex()
[ Info: test_without_mutex()

Error thrown in threaded loop on thread 1: AssertionError(msg="a[i] == parse(Float64, y)")
Error thrown in threaded loop on thread 0: AssertionError(msg="a[i] == parse(Float64, y)")
Error thrown in threaded loop on thread 3: AssertionError(msg="a[i] == parse(Float64, y)")
Error thrown in threaded loop on thread 2: AssertionError(msg="a[i] == parse(Float64, y)")

[ Info: test_without_mutex_repeated()

Error thrown in threaded loop on thread 0: InexactError(func=:trunc, T=UInt8, val=-1)
Error thrown in threaded loop on thread 3: DivideError()
Error thrown in threaded loop on thread 0: DivideError()
Error thrown in threaded loop on thread 1: DivideError()
Error thrown in threaded loop on thread 0: InexactError(func=:trunc, T=UInt8, val=-1)
Error thrown in threaded loop on thread 1: InexactError(func=:trunc, T=UInt8, val=-1)

test_without_mutex_repeated() sometimes ends in a segfault.

The same problem occurs with Julia v0.6.

As string interpolation may be used deep inside a third-party function/dependency, and is therefore hard to control/fence, this is a serious problem when writing multi-threaded code.

@oschulz oschulz changed the title String interpolation not threadsafe String interpolation not thread-safe Jan 24, 2018
@oschulz
Copy link
Contributor Author

oschulz commented Jan 24, 2018

Discovered by @RCS-77.

@yuyichao yuyichao changed the title String interpolation not thread-safe Floating point printing not thread-safe Jan 24, 2018
@yuyichao
Copy link
Contributor

String interpolation is fine, the global buffer for floating point formatting in grisu is not.

@vtjnash vtjnash added the multithreading Base.Threads and related functionality label Jan 24, 2018
@vtjnash vtjnash changed the title Floating point printing not thread-safe Grisu (floating point printing) not thread-safe Jan 24, 2018
@oschulz
Copy link
Contributor Author

oschulz commented Jan 24, 2018

Ah, thanks for the clarification, @yuyichao!

@oschulz
Copy link
Contributor Author

oschulz commented Jan 24, 2018

Could we simply do something like

const DIGITS = [Vector{UInt8}(uninitialized, 309+17) for _ in 1:Base.Threads.nthreads()]

[...]

function grisu(v::AbstractFloat,mode,requested_digits,buffer=DIGITS[Base.Threads.threadid()],bignums=BIGNUMS)
    if signbit(v)

[...]
...

in "grisu.jl"?

@Keno
Copy link
Member

Keno commented Jan 24, 2018

We have more allocation elision for buffers now, so we should probably just move it inside the function. The one wrinkle is that I believe this buffer is shared across quite a number of large functions, so our local escape analysis is probably not powerful enough. I wonder whether we can just start doing inter-procedural escape analysis in inference.

@vtjnash
Copy link
Member

vtjnash commented Jan 24, 2018

I wonder whether we can just start doing inter-procedural escape analysis in inference.

Hopefully soon. I'm not sure if that'll work for our Arrays right now as well. Might need to change this to a Ref{NTuple{309 + 17, UInt8}}() to make it a fixed size, but that brings about its own complications. Currently there's also the additional complication though that BigInt calls resize! on this buffer too, which requires that it be heap allocated.

@JeffBezanson
Copy link
Member

Yes, I think the uses in grisu are going to be very hard to optimize automatically.

@oschulz
Copy link
Contributor Author

oschulz commented Jan 24, 2018

Since it's a fixed size buffer - could we simply stack allocate 309+17 bytes and get the pointer - or does it have to be a real Julia array?

@oschulz
Copy link
Contributor Author

oschulz commented Jan 24, 2018

If stack allocation is not possible and allocation elision won't work in this scenario yet, could we adopt the "one array per thread" approach for now, to get thread safety? Maybe also as a backport to v0.6? Non-threadsafe floating point formatting is kinda painful to work around ...

@KlausC
Copy link
Contributor

KlausC commented Jan 31, 2018

Please keep in mind, that also printf.jl is accessing DIGITS directly.

stevengj added a commit that referenced this issue Mar 28, 2018
* make generic matmul thread-safe (fixes #22581)

* use per-thread DIGITS buffer in grisu and printf (fixes #25727)

* refactor resize_nthreads into an unexported Threads function
@oschulz
Copy link
Contributor Author

oschulz commented Mar 28, 2018

Thanks, @stevengj !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
multithreading Base.Threads and related functionality
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants