Skip to content

Commit f8fa4c1

Browse files
almiloszpraasz
andauthored
[Core] Add parallel mapping population on Windows (#36586)
### Details: - Implement `ov::MappedMemory::hint_prefetch` for Windows os - Add forward-declared `populate_pages()` to extract common code and to avoid adding it as util tool for now. - Tests for `hint_prefetch()` are located in _src\core\tests\mmap_object.cpp_. Test for util::vm_prefetch was added to _memory_test.cpp_ - Add previously requested changes to _file_load_benchmark.cpp_ and more specific names for test columns: Size (MiB) | parallel loop sync | default mmap | ifstream -----------|--------------------|---------------|-------------- x | x | x | x Size (MiB) | parallel loop sync | loop touch -----------|--------------------|-------------- x | x | x ### Tickets: - [CVS-186579](https://jira.devtools.intel.com/browse/CVS-186579) ### AI Assistance: - *AI assistance used: yes: codebase analysis, brainstorming, fixing issues* --------- Co-authored-by: Pawel Raasz <pawel.raasz@intel.com>
1 parent 4039add commit f8fa4c1

8 files changed

Lines changed: 303 additions & 149 deletions

File tree

src/common/util/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ target_sources(${TARGET_NAME}
6969
${CMAKE_CURRENT_SOURCE_DIR}/src/file_path.cpp
7070
${CMAKE_CURRENT_SOURCE_DIR}/src/file_util.cpp
7171
${CMAKE_CURRENT_SOURCE_DIR}/src/log.cpp
72+
${CMAKE_CURRENT_SOURCE_DIR}/src/memory.cpp
7273
${CMAKE_CURRENT_SOURCE_DIR}/src/native_stream.cpp
7374
${CMAKE_CURRENT_SOURCE_DIR}/src/native_streambuf.cpp
7475
${CMAKE_CURRENT_SOURCE_DIR}/src/parallel_read_streambuf.cpp

src/common/util/include/openvino/util/memory.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212

1313
namespace ov::util {
1414

15+
/** @brief One mebibyte (1024 * 1024 bytes). */
16+
inline constexpr size_t one_mib = 1024 * 1024;
17+
1518
/** @brief Minimum guaranteed page alignment on all supported platforms (x86, ARM, RISC-V). */
1619
inline constexpr size_t min_page_alignment = 4096;
1720

src/common/util/src/memory.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright (C) 2018-2026 Intel Corporation
2+
// SPDX-License-Identifier: Apache-2.0
3+
//
4+
5+
#include "openvino/util/memory.hpp"
6+
7+
#include <algorithm>
8+
#include <cassert>
9+
#include <cstdint>
10+
#include <thread>
11+
#include <vector>
12+
13+
#include "openvino/util/common_util.hpp"
14+
#include "openvino/util/mmap_object.hpp"
15+
16+
namespace ov::util {
17+
void populate_pages(void* ptr, size_t size, size_t num_threads) noexcept;
18+
19+
namespace {
20+
21+
/**
22+
* @brief Functor that touches one byte per page over [m_begin, m_end) to force the pages resident.
23+
*
24+
* The volatile accumulator prevents the compiler from optimizing the read loop away.
25+
*/
26+
struct PageToucher {
27+
const uint8_t* m_begin;
28+
const uint8_t* m_end;
29+
const size_t m_page_size;
30+
31+
void operator()() const noexcept {
32+
volatile uint8_t local = 0; // prevents the compiler from optimizing the loop away
33+
for (auto begin = m_begin; begin < m_end; begin += m_page_size) {
34+
local += *begin;
35+
}
36+
}
37+
};
38+
39+
} // namespace
40+
41+
void populate_pages(void* ptr, size_t size, size_t num_threads) noexcept {
42+
// ptr and size are guaranteed page-aligned by vm_prefetch's precondition.
43+
const auto page_size = static_cast<size_t>(get_system_page_size());
44+
const auto chunk_size = std::max<size_t>(align_size_up(size / num_threads, page_size), one_mib);
45+
46+
std::vector<std::thread> threads;
47+
threads.reserve(ceil_div(size, chunk_size));
48+
49+
for (auto first = reinterpret_cast<const uint8_t*>(ptr), last = first + size; first < last; first += chunk_size) {
50+
threads.emplace_back(PageToucher{first, std::min(first + chunk_size, last), page_size});
51+
}
52+
for (auto& t : threads) {
53+
t.join();
54+
}
55+
}
56+
57+
} // namespace ov::util

src/common/util/src/os/lin/lin_memory.cpp

Lines changed: 3 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,14 @@
44

55
#include <sys/mman.h>
66

7-
#include <algorithm>
87
#include <cassert>
98
#include <cerrno>
109
#include <cstddef>
11-
#include <cstdint>
1210
#include <cstdlib>
1311
#include <cstring>
14-
#include <thread>
1512
#include <tuple>
16-
#include <vector>
1713

18-
#include "openvino/util/common_util.hpp"
1914
#include "openvino/util/memory.hpp"
20-
#include "openvino/util/mmap_object.hpp"
2115

2216
namespace ov::util {
2317

@@ -28,37 +22,10 @@ void madvise_hint(void* ptr, size_t size) noexcept {
2822
madvise(ptr, size, MADV_WILLNEED);
2923
}
3024

31-
struct PageToucher {
32-
const uint8_t* m_begin;
33-
const uint8_t* m_end;
34-
const size_t m_page_size;
35-
36-
void operator()() const noexcept {
37-
volatile uint8_t local = 0; // prevents the compiler from optimizing the loop away
38-
for (auto begin = m_begin; begin < m_end; begin += m_page_size) {
39-
local += *begin;
40-
}
41-
}
42-
};
43-
44-
void populate_pages(void* ptr, size_t size, size_t num_threads) noexcept {
45-
// ptr and size are guaranteed page-aligned by vm_prefetch's precondition.
46-
const auto page_size = static_cast<size_t>(get_system_page_size());
47-
const auto chunk_size = std::max<size_t>(align_size_up(size / num_threads, page_size), 1024 * 1024);
48-
49-
std::vector<std::thread> threads;
50-
threads.reserve(ceil_div(size, chunk_size));
51-
52-
for (auto first = reinterpret_cast<const uint8_t*>(ptr), last = first + size; first < last; first += chunk_size) {
53-
threads.emplace_back(PageToucher{first, std::min(first + chunk_size, last), page_size});
54-
}
55-
for (auto& t : threads) {
56-
t.join();
57-
}
58-
}
59-
6025
} // namespace
6126

27+
void populate_pages(void* ptr, size_t size, size_t num_threads) noexcept;
28+
6229
void* aligned_alloc(size_t size, size_t alignment) noexcept {
6330
if (alignment == 0) {
6431
alignment = alignof(std::max_align_t);
@@ -109,13 +76,10 @@ void vm_release(void* ptr, size_t size) noexcept {
10976

11077
void vm_prefetch(void* ptr, size_t size, size_t num_threads) noexcept {
11178
assert(ptr != nullptr && size > 0);
112-
// assert if region is not mmap-backed.
113-
11479
if (num_threads == 0) {
115-
// Option 1: OS advisory hints — async, low overhead.
11680
madvise_hint(ptr, size);
11781
} else {
118-
// Option 2: parallel synchronous touch — blocks until every page is resident.
82+
// blocks until every page has been faulted in.
11983
populate_pages(ptr, size, num_threads);
12084
}
12185
}

src/common/util/src/os/win/win_memory.cpp

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
namespace ov::util {
2020

21+
void populate_pages(void* ptr, size_t size, size_t num_threads) noexcept;
22+
2123
void* aligned_alloc(size_t size, size_t alignment) noexcept {
2224
if (alignment == 0) {
2325
alignment = alignof(std::max_align_t);
@@ -59,13 +61,12 @@ void vm_release(void* ptr, size_t) noexcept {
5961

6062
void vm_prefetch(void* ptr, size_t size, size_t num_threads) noexcept {
6163
assert(ptr != nullptr && size > 0);
62-
// CVS-186579
63-
// assert if region is not mmap-baked.
64-
6564
if (num_threads == 0) {
66-
// Option 1: OS advisory hints
65+
WIN32_MEMORY_RANGE_ENTRY entry{ptr, size};
66+
::PrefetchVirtualMemory(::GetCurrentProcess(), 1, &entry, 0);
6767
} else {
68-
// Option 2: parallel synchronous prefault & touch
68+
// blocks until every page has been faulted in.
69+
populate_pages(ptr, size, num_threads);
6970
}
7071
}
7172

src/common/util/src/os/win/win_mmap_object.cpp

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <mutex>
99
#include <shared_mutex>
1010
#include <stdexcept>
11+
#include <thread>
1112
#include <vector>
1213

1314
#include "openvino/util/common_util.hpp"
@@ -302,7 +303,7 @@ class MapHolder : public ov::MappedMemory {
302303

303304
void hint_evict(size_t offset, size_t size) noexcept override;
304305

305-
void hint_prefetch(size_t /*offset*/, size_t /*size*/) override {}
306+
void hint_prefetch(size_t offset, size_t size) override;
306307

307308
private:
308309
/**
@@ -743,6 +744,33 @@ bool MapHolder::try_remap_slot(uintptr_t fault_addr) {
743744
return remap_placeholder(proc, placeholder_base, placeholder_size);
744745
}
745746

747+
namespace {
748+
749+
// Clamps [offset, offset + size) to [0, mapping_size) and page-aligns the result.
750+
// Returns an empty region (m_length == 0) for a null/empty mapping, an offset at or past the end,
751+
// or a sub-page request.
752+
util::AlignedRegion clamp_align_region(const void* data, size_t mapping_size, size_t offset, size_t size) noexcept {
753+
const auto page_size = static_cast<size_t>(util::get_system_page_size());
754+
if (data == nullptr || mapping_size == 0 || offset >= mapping_size || size < page_size) {
755+
return {};
756+
}
757+
const auto available = mapping_size - offset;
758+
const auto raw_len = (size == auto_size) ? available : std::min(size, available);
759+
return util::align_region(reinterpret_cast<uintptr_t>(data) + offset, raw_len, page_size);
760+
}
761+
762+
} // namespace
763+
764+
void MapHolder::hint_prefetch(size_t offset, size_t size) {
765+
// Below 4 MiB the overhead of spawning threads exceeds the benefit; skip.
766+
if (const auto region = clamp_align_region(m_data, m_size, offset, size); region.m_length > 4 * util::one_mib) {
767+
const auto num_threads = std::min<size_t>(10, std::thread::hardware_concurrency());
768+
const auto aligned_size =
769+
util::align_size_up(region.m_length, static_cast<size_t>(util::get_system_page_size()));
770+
util::vm_prefetch(reinterpret_cast<void*>(region.m_address), aligned_size, num_threads);
771+
}
772+
}
773+
746774
std::pair<char*, char*> MapHolder::compute_evict_range(size_t offset, size_t size) const noexcept {
747775
// Require placeholder path, API availability, and a live VEH (eviction without VEH
748776
// would leave inaccessible placeholders on the next access).

0 commit comments

Comments
 (0)