arena_memory_resource.hpp
前往此文件的文档。
1 /*
2  * 版权所有 (c) 2020-2025, NVIDIA CORPORATION。
3  *
4  * 根据 Apache 许可证 2.0 版(“许可证”)获得许可;
5  * 除非符合许可证的规定,否则您不得使用此文件。
6  * 您可以获取许可证的副本,网址为
7  *
8  * https://apache.ac.cn/licenses/LICENSE-2.0
9  *
10  * 除非适用法律要求或书面同意,根据许可证分发的软件按“原样”分发,
11  * 不附带任何明示或暗示的保证或条件。
12  * 请参阅许可证以了解管理权限和限制的特定语言。
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #pragma once
17 
18 #include <rmm/aligned.hpp>
19 #include <rmm/detail/error.hpp>
20 #include <rmm/detail/export.hpp>
21 #include <rmm/detail/format.hpp>
22 #include <rmm/detail/logging_assert.hpp>
23 #include <rmm/logger.hpp>
24 #include <rmm/mr/device/detail/arena.hpp>
26 #include <rmm/resource_ref.hpp>
27 
28 #include <cuda_runtime_api.h>
29 
30 #include <cstddef>
31 #include <map>
32 #include <shared_mutex>
33 #include <thread>
34 
35 namespace RMM_NAMESPACE {
36 namespace mr {
82 template <typename Upstream>
84  public
93  /// 构造一个 arena_memory_resource。
95  std::optional<std::size_t> arena_size = std::nullopt,
96  bool dump_log_on_failure = false)
97  : global_arena_{upstream_mr, arena_size}, dump_log_on_failure_{dump_log_on_failure}
98  {
99  if (dump_log_on_failure_) {
100  logger_ =
101  std::make_shared<rapids_logger::logger>("arena_memory_dump", "rmm_arena_memory_dump.log");
102  // Set the level to `debug` for more detailed output.
103  logger_->set_level(rapids_logger::level_enum::info);
104  }
105  }
116  /// 构造一个 arena_memory_resource。
117  explicit arena_memory_resource(Upstream* upstream_mr,
118  std::optional<std::size_t> arena_size = std::nullopt,
119  bool dump_log_on_failure = false)
121  to_device_async_resource_ref_checked(upstream_mr), arena_size, dump_log_on_failure}
122  {
123  }
124 
125  ~arena_memory_resource() override = default;
126 
127  // 禁用复制(和移动)语义。
129  arena_memory_resource& operator=(arena_memory_resource const&) = delete;
130  arena_memory_resource(arena_memory_resource&&) noexcept = delete;
131  arena_memory_resource& operator=(arena_memory_resource&&) noexcept = delete;
132 
133  private
134  using global_arena = rmm::mr::detail::arena::global_arena;
135  using arena = rmm::mr::detail::arena::arena;
147  /// 从资源分配大小为 `bytes` 的内存。
148  void* do_allocate(std::size_t bytes, cuda_stream_view stream) override
149  {
150  if (bytes <= 0) { return nullptr; }
151 #ifdef RMM_ARENA_USE_SIZE_CLASSES
152  bytes = rmm::mr::detail::arena::align_to_size_class(bytes);
153 #else
155 #endif
156  auto& arena = get_arena(stream);
157 
158  {
159  std::shared_lock lock(mtx_);
160  void* pointer = arena.allocate(bytes);
161  if (pointer != nullptr) { return pointer; }
162  }
163 
164  {
165  std::unique_lock lock(mtx_);
166  defragment();
167  void* pointer = arena.allocate(bytes);
168  if (pointer == nullptr) {
169  if (dump_log_on_failure_) { dump_memory_log(bytes); }
170  auto const msg = std::string("超出最大池大小(分配失败") +
171  rmm::detail::format_bytes(bytes) + "): 竞技场中没有空间。";
172  RMM_FAIL(msg.c_str(), rmm::out_of_memory);
173  }
174  return pointer;
175  }
176  }
180  /// 碎片整理所有竞技场。
181  void defragment()
182  {
183  RMM_CUDA_TRY(cudaDeviceSynchronize());
184  for (auto& thread_arena : thread_arenas_) {
185  thread_arena.second->clean();
186  }
187  for (auto& stream_arena : stream_arenas_) {
188  stream_arena.second.clean();
189  }
190  }
199  /// 释放指向 `ptr` 的大小为 `bytes` 的内存。
200  void do_deallocate(void* ptr, std::size_t bytes, cuda_stream_view stream) override
201  {
202  if (ptr == nullptr || bytes <= 0) { return; }
203 #ifdef RMM_ARENA_USE_SIZE_CLASSES
204  bytes = rmm::mr::detail::arena::align_to_size_class(bytes);
205 #else
207 #endif
208  auto& arena = get_arena(stream);
209 
210  {
211  std::shared_lock lock(mtx_);
212  // 如果要释放的内存不属于当前竞技场,则以下操作将返回 false。
213  if (arena.deallocate(ptr, bytes, stream)) { return; }
214  }
215 
216  {
217  // 由于我们将此内存返回到另一个流,因此需要确保当前流
218  // 已完成。
219  stream.synchronize_no_throw();
220 
221  std::unique_lock lock(mtx_);
222  deallocate_from_other_arena(ptr, bytes, stream);
223  }
224  }
233  /// 从不属于调用线程/流的竞技场中释放内存。
234  void deallocate_from_other_arena(void* ptr, std::size_t bytes, cuda_stream_view stream)
235  {
236  if (use_per_thread_arena(stream)) {
237  for (auto const& thread_arena : thread_arenas_) {
238  if (thread_arena.second->deallocate(ptr, bytes)) { return; }
239  }
240  } else {
241  for (auto& stream_arena : stream_arenas_) {
242  if (stream_arena.second.deallocate(ptr, bytes)) { return; }
243  }
244  }
245 
246  if (!global_arena_.deallocate(ptr, bytes)) {
247  // 可以同时使用每个线程的默认流和另一个流池。
248  // 这意味着分配有可能从线程或流竞技场移动
249  // 在碎片整理期间回到全局竞技场,然后再移至另一个竞技场
250  // 类型。例如,线程竞技场 -> 全局竞技场 -> 流竞技场。如果发生这种情况并且
251  // 在它还是线程竞技场时曾有分配来自它,我们现在需要检查
252  // 分配是否属于流竞技场,反之亦然。
253  // 仅在特殊情况下执行此操作,以免影响性能并必须检查所有
254  // 竞技场。
255  if (use_per_thread_arena(stream)) {
256  for (auto& stream_arena : stream_arenas_) {
257  if (stream_arena.second.deallocate(ptr, bytes)) { return; }
258  }
259  } else {
260  for (auto const& thread_arena : thread_arenas_) {
261  if (thread_arena.second->deallocate(ptr, bytes)) { return; }
262  }
263  }
264  RMM_FAIL("未找到分配");
265  }
266  }
273  /// 返回指定流的竞技场。
274  arena& get_arena(cuda_stream_view stream)
275  {
276  if (use_per_thread_arena(stream)) { return get_thread_arena(); }
277  return get_stream_arena(stream);
278  }
284  /// 返回当前线程的竞技场。
285  arena& get_thread_arena()
286  {
287  auto const thread_id = std::this_thread::get_id();
288  {
289  std::shared_lock lock(map_mtx_);
290  auto const iter = thread_arenas_.find(thread_id);
291  if (iter != thread_arenas_.end()) { return *iter->second; }
292  }
293  {
294  std::unique_lock lock(map_mtx_);
295  auto thread_arena = std::make_shared<arena>(global_arena_);
296  thread_arenas_.emplace(thread_id, thread_arena);
297  thread_local detail::arena::arena_cleaner cleaner{thread_arena};
298  return *thread_arena;
299  }
300  }
306  /// 返回指定流的竞技场。
307  arena& get_stream_arena(cuda_stream_view stream)
308  {
309  RMM_LOGGING_ASSERT(!use_per_thread_arena(stream));
310  {
311  std::shared_lock lock(map_mtx_);
312  auto const iter = stream_arenas_.find(stream.value());
313  if (iter != stream_arenas_.end()) { return iter->second; }
314  }
315  {
316  std::unique_lock lock(map_mtx_);
317  stream_arenas_.emplace(stream.value(), global_arena_);
318  return stream_arenas_.at(stream.value());
319  }
320  }
326  /// 转储全局竞技场的内存日志。
327  void dump_memory_log(size_t bytes)
328  {
329  logger_->info("**************************************************");
330  logger_->info("尝试分配 %s 时内存不足。", rmm::detail::format_bytes(bytes));
331  logger_->info("**************************************************");
332  logger_->info("全局竞技场:");
333  global_arena_.dump_memory_log(logger_);
334  logger_->flush();
335  }
342  /// 如果流是每线程默认流,则返回 true。
343  static bool use_per_thread_arena(cuda_stream_view stream)
344  {
345  return stream.is_per_thread_default();
346  }
348  /// 全局竞技场。
351  /// 从线程 ID 到线程竞技场的映射。
354  /// 从流到流竞技场的映射。
356  /// 失败时转储日志。
358  /// 用于内存转储的记录器。
360  /// 用于映射访问的互斥锁。
362  /// 用于竞技场访问的互斥锁。
363 };
364  // 组结束
366 } // namespace mr
367 } // namespace RMM_NAMESPACE
带默认构造函数的 CUDA 流的强类型非拥有包装器。
定义: cuda_stream_view.hpp:39
一种强调避免碎片和可扩展并发支持的子分配器。
定义: arena_memory_resource.hpp:83
arena_memory_resource(Upstream *upstream_mr, std::optional< std::size_t > arena_size=std::nullopt, bool dump_log_on_failure=false)
构造 arena_memory_resource。
定义: arena_memory_resource.hpp:116
arena_memory_resource(device_async_resource_ref upstream_mr, std::optional< std::size_t > arena_size=std::nullopt, bool dump_log_on_failure=false)
构造 arena_memory_resource。
定义: arena_memory_resource.hpp:93
所有 librmm 设备内存分配的基类。
定义: device_memory_resource.hpp:92
RMM 内存不足时抛出的异常。
定义: error.hpp:87
cuda::mr::async_resource_ref< cuda::mr::device_accessible > device_async_resource_ref
具有属性 cuda::mr::device_accessible 的 cuda::mr::async_resource_ref 的别名。
定义: resource_ref.hpp:40
device_async_resource_ref to_device_async_resource_ref_checked(Resource *res)
将内存资源指针转换为 device_async_resource_ref,并检查是否为 nullptr
定义: resource_ref.hpp:78
static constexpr std::size_t CUDA_ALLOCATION_ALIGNMENT
用于 CUDA 内存分配的默认对齐方式。
定义: aligned.hpp:43
constexpr std::size_t align_up(std::size_t value, std::size_t alignment) noexcept
向上对齐到指定 2 的幂的最近倍数。
定义: aligned.hpp:77