Repository navigation
Rework memory pinning and speed up async ops on unpinned memory - #760
Merged
Merged
Conversation
Member
Author
Cost: around 100ns.
HtoD may be async, DtoH should always synchronize afterwards.
Codecov Report
@@ Coverage Diff @@
## master #760 +/- ##
==========================================
+ Coverage 78.20% 78.26% +0.06%
==========================================
Files 121 122 +1
Lines 7245 7280 +35
==========================================
+ Hits 5666 5698 +32
- Misses 1579 1582 +3
Continue to review full report at Codecov.
|
Member
Author
|
Let's go ahead and merge this, 3.0 is some weeks away anyway so please let me know if there's missing features. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #242, closes #735.
With the recent stream-per-task execution model, we can start executing a whole bunch of operations asynchronously (because synchronization is cheaper now that it can yield back to the Julia scheduler). There was one exception though: memory copies. Basically, when transferring memory to or from the CPU, that memory should not be paged out. You can avoid that from happening by pinning or page-locking memory. If you don't, the operation will still execute synchronously, and worse, it will do so without yielding back to the Julia scheduler.
Demo for the rest of this PR:
Similar example to #662, but with one crucial difference: a memory copy is performed within the computational task (
Array(compute(a,b,c))). That's bad, and results in the following profile:The copies behave synchronously, which results in the first task blocking outside of the scheduler so that the second task can't get scheduled. One solution, as sneakily used in #662, is to
synchronize()in each task such that we can start work on the other task before we perform a memory operation. That results in the following trace:Much better. The void before each memory copy is because of allocating memory. A better solution is to pre-allocate that memory and pin it (so
results[...] = Mem.pin(Array{eltype(c)}(undef, size(c)))in the main task, and acopyto!on the results instead of just calling theArrayconstructor), not only avoiding that 'blank space' but also making sure the memory operations can happen asynchronously. That gives a best-case profile trace:Note that the copies themselves are also a lot faster. So you might think, why not eagerly pin every CPU buffer before a memory operation? Alas, pinning isn't free:
So, that brings me to the final design of this PR, where we try and figure out whether a CPU buffer is pinned before a memory operation, and if it isn't, eagerly synchronize execution. By doing so, we can at least finish the current work on the GPU concurrently to work queued up by other Julia tasks, even though we'll still end up synchronizing in the driver afterwards:
The overhead of these operations are minimal, couple of 10s of ns is barely visible on the profiler trace.
cc @staticfloat