1

I'd like to run heavy computations in Julia for a fixed duration, for example 10 seconds. I tried this:

timer = Timer(10.0)
while isopen(timer)
    computation()
end

But this does not work, since the computations never let Julia's task scheduler take control. So I added yield() in the loop:

timer = Timer(10.0)
while isopen(timer)
    yield()
    computation()
end

But now there is significant overhead from calling yield(), especially when one call to computation() is short. I guess I could call yield() and isopen() only every 1000 iterations or so, but I would prefer a solution where I would not have to tweak the number of iterations every time I change the computations. Any ideas?

2
  • Do you expect each execution of computation to take approximately the same duration ? Commented Jul 11, 2020 at 12:26
  • Yes, roughly the same time. Commented Jul 12, 2020 at 0:28

1 Answer 1

1

This pattern below uses threads and on my laptop has a latency of around 35ms for each 1,000,000 calls which is more than acceptable for any job.

Tested on Julia 1.5 release candidate:

function should_stop(timeout=10)
    handle = Threads.Atomic{Bool}(false)
    mytask = Threads.@spawn begin
        sleep(timeout)
        Threads.atomic_or!(handle, true)
    end
    handle
end


function do_some_job_with_timeout()
    handle = should_stop(5)
    res = BigInt() # save results to some object
    mytask = Threads.@spawn begin
        for i in 1:10_000_000
            #TODO some complex computations here
            res += 1 # mutate the result object
            handle.value && break
        end
    end
    wait(mytask) # wait for the job to complete
    res
end

You can also used Distributed instead. The code below seems to have a much better latency - only about 1ms for each 1,000,000 timeout checks.

using Distributed
using SharedArrays
addprocs(1)
function get_termination_handle(timeout=5,workerid::Int=workers()[end])::SharedArray{Bool}
    handle = SharedArray{Bool}([false])
    proc = @spawnat workerid begin
        sleep(timeout)
        handle[1]=true
    end
    handle
end


function fun_within_timeout()
    res = 0
    h = get_termination_handle(0.1)
    for i = 1:100_000_000
        res += i % 2 == 0 ? 1 : 0
        h[1] && break
    end
    res
end

Sign up to request clarification or add additional context in comments.

8 Comments

Thanks Przemyslaw Szufel, but unfortunately I cannot get this to work. If I run it as is, the computations are finished after 2 seconds on my computer. If I bump up the iteration to 1:10^9, the computations do not stop after 5 seconds. If I add yield() in the loop, then it works. I suspect that both sleep & loop tasks get scheduled in the same thread. Note that I did set JULIA_NUM_THREADS=16 before starting Julia. Also note that I tried running the job in the main thread rather than a second Thread.@spawn, but it made no difference.
What Julia version you have? I have been testing it in Julia 1.5 rc1. This code worked for me only when these are two separate threads (hence that is why I spawn twice). You could perhaps try to add another variable and store Threads.threadid() to see if they get spawned separately. As fat as I know there is no method to select threadid so if this does not help I will propose code using Distributed.
I'm using 1.4.2. I'll give 1.5 rc1 a try.
I added the Distributed code. It also seems to have much lower latency.
Thanks for the distributed version, it seems to work in Julia 1.4.2. Also, I can confirm that the 1st solution works for me when using 1.5rc1. However, the distributed version does not work for me in 1.5rc1, this is weird.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.