1010#include < cstddef>
1111#include < cstdint>
1212#include < cstdlib>
13- #include < cstring>
1413#include < functional>
1514#include < future>
1615#include < list>
@@ -34,22 +33,6 @@ void madvise_hint(void* ptr, size_t size) noexcept {
3433 madvise (ptr, size, MADV_WILLNEED );
3534}
3635
37- // Touches one byte per page over [m_begin, m_end) to force the pages resident. The volatile
38- // accumulator keeps the compiler from eliminating the read loop.
39- struct PageToucher {
40- const uint8_t * m_begin;
41- const uint8_t * m_end;
42- const size_t m_page_size;
43-
44- void operator ()() const noexcept {
45- volatile uint8_t local = 0 ;
46- for (auto begin = m_begin; begin < m_end; begin += m_page_size) {
47- local += *begin;
48- }
49- }
50- };
51-
52- // Thread-safe queue of population jobs. Owns only the queueing/notification concern.
5336class TaskQueue {
5437public:
5538 void push (std::list<std::function<void ()>>&& batch) noexcept {
@@ -89,8 +72,6 @@ class TaskQueue {
8972 bool m_stop = false ;
9073};
9174
92- // Process-wide, bounded pool of worker threads that drain a shared TaskQueue. Keeping the worker
93- // count bounded means repeated prefetch calls reuse the same threads instead of spawning new ones.
9475class ThreadPool {
9576public:
9677 static ThreadPool& instance () {
@@ -101,9 +82,6 @@ class ThreadPool {
10182 ThreadPool (const ThreadPool&) = delete ;
10283 ThreadPool& operator =(const ThreadPool&) = delete ;
10384
104- // Enqueueing is all-or-nothing: every throwing operation (task/future creation) happens on
105- // local state first, and the finished batch is spliced into the queue with a non-throwing
106- // splice. If preparation throws, nothing is enqueued and no job runs.
10785 std::vector<std::future<void >> submit (std::vector<std::function<void ()>>&& jobs) {
10886 std::vector<std::future<void >> futures;
10987 futures.reserve (jobs.size ());
@@ -151,19 +129,23 @@ class ThreadPool {
151129 std::vector<std::thread> m_workers;
152130};
153131
154- // Splits [ ptr, ptr + size) into num_threads page-toucher jobs and submits them to the shared pool.
155- std::vector<std::future< void >> submit_page_toucher_tasks ( void * ptr, size_t size, size_t num_threads) {
156- const auto page_size = static_cast <size_t >(get_system_page_size ());
157- const auto chunk_size =
158- std::max<size_t >(align_size_up (size / num_threads, page_size), default_parallel_io_min_chunk);
132+ std::vector<std::future< void >> submit_page_toucher_tasks ( void * ptr, size_t size, size_t num_threads) noexcept {
133+ try {
134+ const auto page_size = static_cast <size_t >(get_system_page_size ());
135+ const auto chunk_size =
136+ std::max<size_t >(align_size_up (size / num_threads, page_size), default_parallel_io_min_chunk);
159137
160- std::vector<std::function<void ()>> jobs;
161- jobs.reserve (ceil_div (size, chunk_size));
138+ std::vector<std::function<void ()>> jobs;
139+ jobs.reserve (ceil_div (size, chunk_size));
162140
163- for (auto first = reinterpret_cast <const uint8_t *>(ptr), last = first + size; first < last; first += chunk_size) {
164- jobs.emplace_back (PageToucher{first, std::min (first + chunk_size, last), page_size});
141+ for (auto first = reinterpret_cast <const uint8_t *>(ptr), last = first + size; first < last;
142+ first += chunk_size) {
143+ jobs.emplace_back (PageToucher{first, std::min (first + chunk_size, last), page_size});
144+ }
145+ return ThreadPool::instance ().submit (std::move (jobs));
146+ } catch (...) {
147+ return {};
165148 }
166- return ThreadPool::instance ().submit (std::move (jobs));
167149}
168150} // namespace
169151
@@ -215,29 +197,14 @@ void vm_release(void* ptr, size_t size) noexcept {
215197
216198void vm_prefetch (void * ptr, size_t size, size_t num_threads) noexcept {
217199 if (num_threads == 0 ) {
218- // Advisory-only: let the OS decide, no page touching.
219200 madvise_hint (ptr, size);
220201 return ;
221202 }
222- // Parallel synchronous touch, blocking until every page is resident. Must not run on a pool
223- // worker thread (it would deadlock waiting on the pool). Prefetching is best-effort, so on
224- // allocation failure fall back to an advisory hint rather than escaping this noexcept function.
225- try {
226- PrefetchToken (submit_page_toucher_tasks (ptr, size, num_threads)).wait ();
227- } catch (...) {
228- madvise_hint (ptr, size);
229- }
203+ PrefetchToken (submit_page_toucher_tasks (ptr, size, num_threads)).wait ();
230204}
231205
232206PrefetchToken vm_prefetch_async (void * ptr, size_t size) noexcept {
233- // Ownership of the futures is transferred to the token; it waits on them on destruction or via
234- // an explicit wait(). If submission fails, fall back to an advisory hint and return an empty token.
235- try {
236- return PrefetchToken (submit_page_toucher_tasks (ptr, size, prefetch_thread_count (size)));
237- } catch (...) {
238- madvise_hint (ptr, size);
239- return PrefetchToken{};
240- }
207+ return PrefetchToken (submit_page_toucher_tasks (ptr, size, prefetch_thread_count (size)));
241208}
242209
243210} // namespace ov::util
0 commit comments