Skip to content

Implement CUDA event pool to minimize runtime resource allocation overhead (libcurl multi poll-based backend 2/n)#919

Merged
rapids-bot[bot] merged 26 commits into
rapidsai:mainfrom
kingcrimsontianyu:event-pool
May 26, 2026
Merged

Implement CUDA event pool to minimize runtime resource allocation overhead (libcurl multi poll-based backend 2/n)#919
rapids-bot[bot] merged 26 commits into
rapidsai:mainfrom
kingcrimsontianyu:event-pool

Conversation

@kingcrimsontianyu

@kingcrimsontianyu kingcrimsontianyu commented Feb 2, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR adds a CUDA event pool, a thread-safe singleton that caches reusable CUDA events organized per CUDA context. This eliminates the per-acquisition cuEventCreate / cuEventDestroy cost in hot paths that need short-lived events (e.g. bounce-buffer recycling).

This PR facilitates the implementation of device buffer support for the remote I/O multiplexing backend.

Related PR

Depends on #917
Addresses part of #914

@kingcrimsontianyu kingcrimsontianyu added improvement Improves an existing functionality non-breaking Introduces a non-breaking change c++ Affects the C++ API of KvikIO labels Feb 2, 2026
@copy-pr-bot

copy-pr-bot Bot commented Feb 2, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@kingcrimsontianyu kingcrimsontianyu changed the title Implement CUDA event pool to minimize runtime resource allocation overhead [WIP] Implement CUDA event pool to minimize runtime resource allocation overhead Feb 2, 2026
Comment thread cpp/src/detail/event.cpp
Comment on lines +70 to +75
if (event == nullptr) {
// Create an event outside the lock to improve performance.
// The pool is not updated here; the returned Event object will automatically return the event
// to the pool when it goes out of scope
CUDA_DRIVER_TRY(cudaAPI::instance().EventCreate(&event, CU_EVENT_DISABLE_TIMING));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

question: Should we have a max size on this event pool? Since we never destroy events, could the pool unboundedly grow in a long-running application? I suppose it will depend on the maximum concurrency of threads issuing reads?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think it is probably fine to let it grow unbounded. The idea I'm trying to implement in #921 is that each pread() builds a "pread context" that gets a num_threads number of events from the pool, i.e. a single event for each thread. Each 4-MiB chunked read() originating from a specific pread() performs the following in sequence:

This event is reused for all the chunks on the same thread originating from the same pread() call. So the space complexity overall is O(num_threads * num_concurrent_pread), which I think is not likely to blow up the RAM in a long running application.

But I do think that in the future a limitation on the resource pools (currently we have bounce buffer pool, this event pool, and libcurl easy handle pool) is a good feature.

Comment thread cpp/src/detail/event.cpp Outdated
_pools[context].push_back(event);
} catch (...) {
// If returning to pool fails, destroy the event
cudaAPI::instance().EventDestroy(event);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

question: Not checking the error code because this is called from ~Event()?

I think we should at least log an error in that case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks. Agreed. I've added the error logging.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The exception message is now properly logged.

Comment thread cpp/src/detail/event.cpp Outdated
Comment thread cpp/src/detail/event.cpp Outdated
Comment on lines +94 to +101
std::size_t EventPool::num_free_events(CUcontext context) const
{
std::lock_guard const lock(_mutex);
auto it = _pools.find(context);
return (it != _pools.end()) ? it->second.size() : 0;
}

std::size_t EventPool::total_free_events() const

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

question: Do you need these for correct usage, or are they just going to be introspection facilities?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

They are just going to be introspection facilities. I plan to include them in the unit test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

These utility functions are now being used in the unit test.

@kingcrimsontianyu kingcrimsontianyu changed the title [WIP] Implement CUDA event pool to minimize runtime resource allocation overhead Implement CUDA event pool to minimize runtime resource allocation overhead Feb 4, 2026
@kingcrimsontianyu kingcrimsontianyu changed the title Implement CUDA event pool to minimize runtime resource allocation overhead Implement CUDA event pool to minimize runtime resource allocation overhead (libcurl multi poll-based backend 2/n) May 21, 2026
@kingcrimsontianyu
kingcrimsontianyu marked this pull request as ready for review May 22, 2026 20:49
@kingcrimsontianyu
kingcrimsontianyu requested review from a team as code owners May 22, 2026 20:49

@KyleFromNVIDIA KyleFromNVIDIA left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approved trivial CMake changes

@kingcrimsontianyu
kingcrimsontianyu requested a review from wence- May 22, 2026 21:01

@madsbk madsbk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good. Do you see any performance gain? Do you have a sense of the overhead of event creation/destruction?

Comment thread cpp/include/kvikio/detail/event.hpp Outdated
* https://docs.nvidia.com/cuda/cuda-programming-guide/02-basics/intro-to-cuda-cpp.html#runtime-initialization
* The OS reclaims process memory at exit regardless.
*/
class EventPool {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Consider naming them CudaEventPool and CudaEvent.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sure! Done!

Comment thread cpp/src/detail/event.cpp

try {
std::lock_guard const lock(_mutex);
_pools[cuda_context].push_back(event);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This note may not hold for pooled events:

/**
 * @note An event that has never been recorded reports `is_done() == true`,
 * since CUDA's `cuEventQuery` returns `CUDA_SUCCESS` when there is no
 * captured work.
 */

If an Event goes out of scope without synchronize() being called, it is returned to the pool while its recorded work may still be pending on the stream. A subsequent pool.get() can then hand that same event to a new caller, for whom is_done() may report false even though they have never called record() themselves.

Either tighten the note to acknowledge that pooled events may retain pending work from a previous user, or make put() reject (or synchronize) non-idle events before returning them to the pool.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That makes sense. I've updated the comment.

@kingcrimsontianyu

Copy link
Copy Markdown
Contributor Author

I haven't done a direct comparison between event pool vs on-the-fly event creation/destruction, but Mark (@harrism) did this benchmark before: https://github.com/harrism/cuda_event_benchmark

@kingcrimsontianyu
kingcrimsontianyu requested a review from madsbk May 26, 2026 15:31

@madsbk madsbk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good. Please report back when you get some performance numbers

@kingcrimsontianyu

Copy link
Copy Markdown
Contributor Author

Sure. Thanks for the review!

@kingcrimsontianyu

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit 0da1e74 into rapidsai:main May 26, 2026
64 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++ Affects the C++ API of KvikIO improvement Improves an existing functionality non-breaking Introduces a non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants