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

Introduce AbstractWorkerPool, CachingPool #16808

Merged
merged 2 commits into from
Jun 26, 2016
Merged

Conversation

amitmurthy
Copy link
Contributor

@amitmurthy amitmurthy commented Jun 7, 2016

This PR does the following:

  • Introduce AbstractWorkerPool. WorkerPool is an implementation of AbstractWorkerPool
  • CachingPool <: AbstractWorkerPool provides two functionalities
    1. caches functions executed via remote, remotecall_fetch(f, cp::CachingPool.....) and others.
      This helps in efficiently executing closures with large closed data multiple times on remote workers.

    2. implements a define(wp::CachingPool; kwargs...) method which broadcasts all the arguments specified onto workers in the pool. These are bound under Main with the same name as the keyword arg.

  • empty!(wp::CachingPool) releases the closure cache as well as set all previously bound names to nothing. We cannot "unbind" a name in Julia yet.

Addresses #10438, #16345 and other issues on easily broadcasting variables.

Example of large closure:

const wp = Base.CachingPool(workers())

function foo(c, n)
    a = ones(n)
    f = x->sum(a) * x
    t = @elapsed results = pmap(wp, f, 1:c)
    @assert results == map(f, 1:c)
    t
end

for coll_sz in [10, 10^2, 10^4]
    for data_sz in [10, 10^4, 10^6]
        println("coll_sz:", coll_sz, ", data_sz:", data_sz)
        tt = foo(coll_sz, data_sz)
        println(tt)
    end
end

Example of broadcast (from the test file):

# CachingPool tests
wp = CachingPool(workers())

# test variable broadcasting
define(wp; wp_foo=1, wp_bar="foobar", wp_baz=ones(10^6))
for p in workers()
    @test remotecall_fetch(()->wp_foo, p) == 1
    @test remotecall_fetch(()->wp_bar, p) == "foobar"
    @test remotecall_fetch(()->wp_baz, p) == ones(10^6)
end

empty!(wp)

for p in workers()
    @test remotecall_fetch(()->wp_foo, p) == nothing
    @test remotecall_fetch(()->wp_bar, p) == nothing
    @test remotecall_fetch(()->wp_baz, p) == nothing
end

Doc updates are pending.

Edit: Means of broadcasting globals across workers have been removed

@tkelman tkelman added the needs docs Documentation for this change is required label Jun 7, 2016
ClusterManager,
default_worker_pool,
define,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a pretty generic name to start exporting for this. Is it more like a remoteset! or remotedefine maybe?

Copy link
Contributor

@oxinabox oxinabox Jun 7, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is generic, but that is the beauty of multiple dispatch, no?
(That said it still might be too generic, I'm not sure either way)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we can't say "this is what this verb should mean" then maybe it's not a name we should be exporting as a generic function yet

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with @tkelman ; if we have this function it should be called something like remoteset!. Modifying global variables, especially in other processes, is not the kind of thing that gets a nice name.

@JeffBezanson
Copy link
Sponsor Member

I don't think global variables can be the way to do broadcasting. It has significant problems for composability and performance.

@amitmurthy
Copy link
Contributor Author

I don't think global variables can be the way to do broadcasting. It has significant problems for composability and performance.

I suspect people end up doing this anyways since we don't have an easy way to achieve this. Storing variable data in remote references and using fetch all over your code isn't very pretty to look at.

Declaring them as const would mean memory cannot be freed.

Wrapping them in getter functions is slightly better - the functions could be named get_<symbol>(), for example get_foo() would return the value of foo. Still ugly but a little better than fetch(ref_foo)

@JeffBezanson
Copy link
Sponsor Member

It's well accepted that one shouldn't use global variables for everything. We need to provide a good alternative so that people don't have to do it. Not being able to combine two functions because they both have a variable called x isn't supposed to happen in the 21st century.

@amitmurthy
Copy link
Contributor Author

I understand. Maybe we can leave it unexported for now? While an inefficient practice there are enough requests on the forums for just this, especially for smaller and simpler programs. And people end up defining via @everywhere or eval on the workers.

@ViralBShah ViralBShah added the domain:parallelism Parallel or distributed computation label Jun 7, 2016
@amitmurthy
Copy link
Contributor Author

The PR has been updated with an alternate implementation for remoteset! :

remoteset!(pool::CachingPool, isconst::Bool=true, mod=Symbol(:Mod_, randstring()); kwargs...)

  • By default all globals are declared as const. The assumption here is variables defined thus are required for the duration of the program run. Can override via arg isconst
  • By default variables are defined as globals in a new module. This avoids namespace clashes. remoteset! returns the module. Users can specify an existing (or a specific name for the new module) via arg mod.

@amitmurthy
Copy link
Contributor Author

Added docs.

I propose we either add both CachingPool and method remoteset! or neither. The reason being while the type implements caching of closures and is useful when large amount of data is captured, closures only keep a reference to global variables and not the data itself. remoteset! is useful for the latter case.

@tkelman tkelman removed the needs docs Documentation for this change is required label Jun 14, 2016
# An AbstractWorkerPool should implement
#
# `push!` - add a new worker into the pool
# `put!` - put back a worker to the pool
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the difference between push! and put! here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My penchant for reusing verbs. take! and put! remove and add back workers from the list of workers available for remote execution - this is maintained in pool.channel.

push! adds a new worker to the pool itself. Maintained in pool.workers

@amitmurthy
Copy link
Contributor Author

Have addressed feedback comments.

Have also changed empty!(pool) to clear(pool) and exported the same.

clear here would mean "clear the cache" and in #16774 to mean "clear the state of the serializer".

@tkelman
Copy link
Contributor

tkelman commented Jun 15, 2016

clear certainly has side effects here

@amitmurthy
Copy link
Contributor Author

Changed to clear!.

Will leave it open for a couple more days. Would like some feedback on the interface and how it can be improved. I believe Jeff's reservations have been partially addressed with defining variables as consts and in their own namespaces by default.

In case of any strong reservations I am open to have only AbstractWorkerPool defined in Base, with CachingPool added as an example or as a separate package.

@amitmurthy amitmurthy changed the title RFC: Introduce AbstractWorkerPool, CachingPool and a means of broadcasting variables Introduce AbstractWorkerPool, CachingPool Jun 26, 2016
@amitmurthy
Copy link
Contributor Author

amitmurthy commented Jun 26, 2016

Have removed the hack to broadcast globals. Large globals can be captured in closures by wrapping them with a let block.

For global foo of 10^8 floats with a caching pool:

julia> let foo=foo
           @time pmap(wp, i->sum(foo)+i, 1:100);
       end;
  4.932819 seconds (403.47 k allocations: 19.095 MB, 0.37% gc time)

Without

julia> let foo=foo
           @time pmap(i->sum(foo)+i, 1:100);
       end;
 46.053112 seconds (1.35 M allocations: 57.517 MB, 0.05% gc time)

For very small closures, the regular workerpool is faster by around 10-20%. The crossover is around 8MB (10^6 floats) when they are equal.

Closes #10438



"""
remotecall_wait(f, pool::WorkerPool, args...; kwargs...)
remotecall_wait(f, pool::AbstractWorkerPool, args...; kwargs...)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this worker-pool signature of remotecall_wait doesn't look like it's present in the rst docs

@amitmurthy
Copy link
Contributor Author

Merging in a short while.

@amitmurthy amitmurthy merged commit d4e7881 into master Jun 26, 2016
@amitmurthy amitmurthy deleted the amitm/cachingpool branch June 26, 2016 17:29
oscardssmith pushed a commit that referenced this pull request Jul 28, 2023
…ool (#33892)

Once upon a time, there was a young julia user first getting started
with parallelism.
And she found it fearsomely slow.
And so she did investigate, and she did illuminate upon her issue.
Her closures, they were being reserialized again and again.
And so this young woman, she openned an issue #16345.
Lo and behold, a noble soul did come and resolve it,
by making the glorious `CachingPool()` in #16808.

3 long years a later this julia user did bravely return to the world of
parallism, with many battle worn scars.
and once more she did face the demon that is `pmap` over closures.
But to her folly, she felt no fear, for she believed the demon to be
crippled and chained by the glorious `CachingPool`.
Fearlessly, she threw his closure over 2GB of data into the maw of the
demon `pmap`.
But alas, alas indeed, she was wrong.
The demon remained unbound, and it slew her, and slew her again.
100 times did it slay her for 101 items was the user iterating upon. 
For the glorious chains of the the `CachingPool()` remains unused, left
aside in the users tool chest, forgotten.
vchuravy pushed a commit to JuliaLang/Distributed.jl that referenced this pull request Oct 6, 2023
…ool (JuliaLang/julia#33892)

Once upon a time, there was a young julia user first getting started
with parallelism.
And she found it fearsomely slow.
And so she did investigate, and she did illuminate upon her issue.
Her closures, they were being reserialized again and again.
And so this young woman, she openned an issue JuliaLang/julia#16345.
Lo and behold, a noble soul did come and resolve it,
by making the glorious `CachingPool()` in JuliaLang/julia#16808.

3 long years a later this julia user did bravely return to the world of
parallism, with many battle worn scars.
and once more she did face the demon that is `pmap` over closures.
But to her folly, she felt no fear, for she believed the demon to be
crippled and chained by the glorious `CachingPool`.
Fearlessly, she threw his closure over 2GB of data into the maw of the
demon `pmap`.
But alas, alas indeed, she was wrong.
The demon remained unbound, and it slew her, and slew her again.
100 times did it slay her for 101 items was the user iterating upon. 
For the glorious chains of the the `CachingPool()` remains unused, left
aside in the users tool chest, forgotten.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
domain:parallelism Parallel or distributed computation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants