Is it necessary for sleep to allocate?

I was optimizing a complex block of multithreaded code and discovered that sleep was allocating.

julia> @btime sleep(0)
  1.720 μs (5 allocations: 144 bytes)

I was pretty surprised, so checked the source which is basically wait(Timer(sec)), so it’s allocating a mutable struct for that Timer. Is there a more efficient alternative to that to sleep for a very short time?

I found a reference to usleep but I’m on Windows so that’s not an option for me.

I need a thread to sleep briefly (milliseconds) waiting for more work to come in. I think using yield instead might work for me, but it would be nice to have an efficient sleep option (or improve my understanding if it’s not possible).

If sleep(0) only takes 2 microseconds then sleeping for a millisecond (500x longer) should have negligible overhead.

1 Like

How about using Libc.systemsleep()?
https://docs.julialang.org/en/v1/base/libc/#Base.Libc.systemsleep

image

This is on Windows.

I confirmed Libc.systemsleep() works on Linux too, and neither allocates there. So I’m puzzled why sleep() just doesn’t call it.

Anyway what I was digging into, until I saw that answer:

I saw a reason for some of the allocations, and was thinking of making a PR to fix:

@edit Timer(1)
        timeout ≥ 0 || throw(ArgumentError("timer cannot have negative timeout of $timeout seconds"))
        interval ≥ 0 || throw(ArgumentError("timer cannot have negative repeat interval of $interval seconds"))
..
        this = new(Libc.malloc(_sizeof_uv_timer), ThreadSynchronizer(), true, false)

LazyString was added in Julia 1.8, so that you could change like this I believe:

timeout ≥ 0 || throw(ArgumentError(lazy"timer cannot have negative timeout of $timeout seconds"))

rather than:

timeout ≥ 0 || throw(ArgumentError(LazyString("timer cannot have negative timeout of $timeout seconds")))

It’s good to know about the LazyString, because untaken throw exceptions can allocate and slow down your code (when string interpolation is used).

I don’t have a fix for the malloc.

https://github.com/JuliaLang/julia/pull/33711

1 Like

What kind of sleep is Libc.systemsleep? The regular (allocating) version has the benefit of allowing other julia tasks to run in the background, since it’s yielding to the scheduler.

4 Likes

Thanks for the mention of Libc.systemsleep. Unfortunately, the doc states: “This function does not yield to Julia’s scheduler and therefore blocks the Julia thread that it is running on for the duration of the sleep time.” This sounds like it won’t be useful. I thought I would test it. I ran the code below and it does hang for 4 seconds (when running Julia with only one thread), so… it appears it won’t do what I need (if the test is correct). The output for duration seems a little odd to me, though.

start 1
end 1
start 1
end 1
duration: 0.004000186920166016
function slong()
  println("start $(Threads.threadid())")
  Libc.systemsleep(2.0)
  println("end $(Threads.threadid())") )
end

function run()
  t1 = time()
  Threads.@spawn slong()
  yield()
  Threads.@spawn slong()
  yield()
  t2 = time()
  println("duration: $(t2 - t1)")
end