| // This file is part of Eigen, a lightweight C++ template library |
| // for linear algebra. |
| // |
| // Copyright (C) 2026 Eigen Authors |
| // |
| // This Source Code Form is subject to the terms of the Mozilla |
| // Public License v. 2.0. If a copy of the MPL was not distributed |
| // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| // Tests for internal::DeviceBufferPool: a released block is recycled only once |
| // the device has retired the work enqueued before the release. The pool is |
| // driven directly, since DeviceBuffer only routes through it on devices without |
| // memory pools. |
| |
| #define EIGEN_USE_GPU |
| #include "main.h" |
| #include <contrib/Eigen/GPU> |
| #include <atomic> |
| #include <chrono> |
| #include <thread> |
| |
| #include "./gpu_test_helpers.h" |
| |
| using namespace Eigen; |
| |
| namespace { |
| |
| using Pool = gpu::internal::DeviceBufferPool<>; |
| constexpr size_t kBytes = Pool::kSmallBufferThreshold / 4; |
| |
| void spin_until(const std::atomic<bool>& flag) { |
| while (!flag.load(std::memory_order_acquire)) std::this_thread::yield(); |
| } |
| |
| // A host function parked on a blocking stream keeps everything ordered after |
| // it, including the pool's release events on the legacy default stream, |
| // pending until the gate opens. |
| struct StreamGate { |
| std::atomic<bool> entered{false}; |
| std::atomic<bool> release{false}; |
| }; |
| |
| void CUDART_CB wait_for_gate(void* data) { |
| StreamGate* gate = static_cast<StreamGate*>(data); |
| gate->entered.store(true, std::memory_order_release); |
| spin_until(gate->release); |
| } |
| |
| // An idle device recycles a released block on the next allocation that fits. |
| void test_idle_reuse() { |
| Pool& pool = Pool::threadLocal(); |
| void* const p = pool.allocate(kBytes); |
| VERIFY(p != nullptr); |
| pool.deallocate(p, kBytes); |
| EIGEN_CUDA_RUNTIME_CHECK(cudaDeviceSynchronize()); |
| void* const q = pool.allocate(kBytes); |
| VERIFY_IS_EQUAL(q, p); |
| pool.deallocate(q, kBytes); |
| } |
| |
| // A block released while a blocking stream is still busy stays out of |
| // circulation until that stream drains; afterwards it is recycled again. |
| void test_reuse_waits_for_in_flight_work() { |
| Pool& pool = Pool::threadLocal(); |
| EIGEN_CUDA_RUNTIME_CHECK(cudaDeviceSynchronize()); |
| cudaStream_t stream = nullptr; |
| EIGEN_CUDA_RUNTIME_CHECK(cudaStreamCreate(&stream)); |
| |
| void* const p = pool.allocate(kBytes); |
| |
| StreamGate gate; |
| EIGEN_CUDA_RUNTIME_CHECK(cudaLaunchHostFunc(stream, wait_for_gate, &gate)); |
| std::thread release_thread([&gate]() { |
| spin_until(gate.entered); |
| std::this_thread::sleep_for(std::chrono::milliseconds(200)); |
| gate.release.store(true, std::memory_order_release); |
| }); |
| |
| // Released while `stream` is parked: the release event cannot have completed. |
| pool.deallocate(p, kBytes); |
| void* const q = pool.allocate(kBytes); |
| VERIFY(q != p); |
| |
| release_thread.join(); |
| EIGEN_CUDA_RUNTIME_CHECK(cudaDeviceSynchronize()); |
| |
| // Both blocks are free and retired; the older release is recycled first. |
| pool.deallocate(q, kBytes); |
| void* const r = pool.allocate(kBytes); |
| VERIFY_IS_EQUAL(r, p); |
| pool.deallocate(r, kBytes); |
| |
| EIGEN_CUDA_RUNTIME_CHECK(cudaStreamDestroy(stream)); |
| } |
| |
| } // namespace |
| |
| EIGEN_DECLARE_TEST(gpu_device_buffer_pool) { |
| gpu_test::require_cuda_device(); |
| CALL_SUBTEST_1(test_idle_reuse()); |
| CALL_SUBTEST_1(test_reuse_waits_for_in_flight_work()); |
| } |