-
Notifications
You must be signed in to change notification settings - Fork 32
Add FixedSizedHostBuffer
#859
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 4 commits
a9d8b42
b9363cb
9948837
a999ef2
ee58be4
48091b1
566ef1a
8e7e7e6
d055b34
ebdb514
c810e13
fba90d6
1f5e3e4
b52c4e6
d152fed
fd17f5e
9a17f9e
6075dfb
67c61c5
1c9f08b
a9a6ad2
08d4ccb
0df229f
c585aa7
a1aa601
dbaf1d9
8657337
355e7c5
d447a88
168ee3a
6975951
5322bc9
93f606a
5012417
9e7c0d7
3dc7550
0d8233e
2ea4806
8cd7046
77bf023
fdb9d47
5c32c78
5d4370b
20413cc
5bf25a9
e90841d
ec6e36e
5182515
67fcd02
8087c49
734d5a7
f88d605
4b743e3
719d21d
ee85b95
ef71eab
eb47413
7e5c857
882bf76
579e1df
145a622
68844d6
647bbf7
e6c1161
cd5f989
8fa078a
be1ac13
5a7653a
ef80ef3
4c816bb
8abe390
b2d6fca
474e6d0
030b198
10db289
5a8613d
91dbe8c
861b212
90df014
6b0b4f4
eb1d3f0
30bea79
3a995a3
2b0b47b
5178878
a26fc03
822a247
94ae1b2
93ede18
0152761
0343712
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,202 @@ | ||||||
| /** | ||||||
| * SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||||||
| * SPDX-License-Identifier: Apache-2.0 | ||||||
| */ | ||||||
| #pragma once | ||||||
|
|
||||||
| #include <cstddef> | ||||||
| #include <cstdint> | ||||||
| #include <functional> | ||||||
| #include <memory> | ||||||
| #include <span> | ||||||
| #include <stdexcept> | ||||||
| #include <utility> | ||||||
| #include <vector> | ||||||
|
|
||||||
| #include <cucascade/memory/fixed_size_host_memory_resource.hpp> | ||||||
|
|
||||||
| namespace rapidsmpf { | ||||||
|
|
||||||
| /** | ||||||
| * @brief Buffer of fixed-size host memory blocks with type-erased storage. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: Nothing about this object requires that the memory is host memory. Do we need to it to be?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It doesn't really. Since we discussed that we won't be needing a fixed-sized buffers in device memory, I thought limiting this to host. |
||||||
| * | ||||||
| * Holds a total size in bytes, a block size, and a span of block start pointers. | ||||||
| * Storage is type-erased via `unique_ptr<void, deleter>`, so different backends | ||||||
| * can be used: a single vector (split into blocks), a vector of vectors, or | ||||||
| * e.g. cucascade's multiple_blocks_allocation. | ||||||
| */ | ||||||
| class FixedSizedHostBuffer { | ||||||
| public: | ||||||
| /// Type-erased deleter invoked with the storage pointer on destruction. | ||||||
| using storage_deleter_type = std::function<void(void*)>; | ||||||
|
|
||||||
| /// @brief Default block size of 1 MiB. | ||||||
| static constexpr size_t default_block_size = size_t(1) << 20; | ||||||
|
|
||||||
| /** | ||||||
| * @brief Construct an empty buffer with a given block size. | ||||||
| * @param block_size Size of each block in bytes. | ||||||
| */ | ||||||
| explicit FixedSizedHostBuffer(size_t block_size = default_block_size) | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be good to avoid a default block size.
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: Why is this a useful ctor? We can't set up a buffer with a block size and then later add the storage, so it seems like the object you get back here is not useful.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah! This was to handle empty data cases. I thought it should be valid to pass empty containers to the factory methods. So, at that point, we need a "default state" for the buffer.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I changed this now to use the default constructor and overloaded the equality operator. I think that can achieve the same effect |
||||||
| : block_size_(block_size) {} | ||||||
|
|
||||||
| /** | ||||||
| * @brief Construct from a single contiguous vector split into fixed-size blocks. | ||||||
| * | ||||||
| * Takes ownership of @p vec by moving it into internal storage. | ||||||
| * | ||||||
| * @param vec Contiguous bytes (moved from). | ||||||
| * @param block_size Size of each block in bytes. | ||||||
| * @return A buffer with blocks covering the vector. | ||||||
| */ | ||||||
| static FixedSizedHostBuffer from_vector( | ||||||
| std::vector<std::byte> vec, std::size_t block_size | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| ); | ||||||
|
|
||||||
| /** | ||||||
| * @brief Construct from a vector of vectors (one block per inner vector). | ||||||
| * | ||||||
| * Takes ownership of @p vecs. Each inner vector becomes one block; all must | ||||||
| * have the same size. | ||||||
| * | ||||||
| * @param vecs Vector of byte vectors (moved from). | ||||||
| * @return A buffer with one block per inner vector. | ||||||
| */ | ||||||
| static FixedSizedHostBuffer from_vectors(std::vector<std::vector<std::byte>> vecs); | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| /** | ||||||
| * @brief Construct from a cucascade multiple_blocks_allocation. | ||||||
| * | ||||||
| * Takes ownership of @p allocation. When the buffer is destroyed, blocks are | ||||||
| * returned to the memory resource via the allocation's destructor. | ||||||
| * | ||||||
| * @param allocation Unique pointer to the allocation (moved from). | ||||||
| * @return A buffer backed by the allocation's blocks. | ||||||
| */ | ||||||
| static FixedSizedHostBuffer from_multi_blocks_alloc( | ||||||
| cucascade::memory::fixed_multiple_blocks_allocation allocation | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| ); | ||||||
|
|
||||||
| FixedSizedHostBuffer(FixedSizedHostBuffer const&) = delete; | ||||||
| FixedSizedHostBuffer& operator=(FixedSizedHostBuffer const&) = delete; | ||||||
|
|
||||||
| /** | ||||||
| * @brief Move constructor; the moved-from buffer is left empty. | ||||||
| * @param other Buffer to move from. | ||||||
| */ | ||||||
| FixedSizedHostBuffer(FixedSizedHostBuffer&& other) noexcept; | ||||||
|
|
||||||
| /** | ||||||
| * @brief Move assignment; the moved-from buffer is left empty. | ||||||
| * @param other Buffer to move from. | ||||||
| * @return Reference to this buffer. | ||||||
| */ | ||||||
| FixedSizedHostBuffer& operator=(FixedSizedHostBuffer&& other) noexcept; | ||||||
|
|
||||||
| /** | ||||||
| * @brief Total size in bytes across all blocks. | ||||||
| * @return Total number of bytes. | ||||||
| */ | ||||||
| [[nodiscard]] constexpr std::size_t total_size() const noexcept { | ||||||
| return total_size_; | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * @brief Size of each block in bytes. | ||||||
| * @return Block size in bytes. | ||||||
| */ | ||||||
| [[nodiscard]] constexpr std::size_t block_size() const noexcept { | ||||||
| return block_size_; | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * @brief Number of blocks. | ||||||
| * @return Number of blocks. | ||||||
| */ | ||||||
| [[nodiscard]] constexpr std::size_t num_blocks() const noexcept { | ||||||
| return block_ptrs_.size(); | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * @brief Span of block start pointers (mutable). | ||||||
| * @return Span of block start pointers. | ||||||
| */ | ||||||
| [[nodiscard]] constexpr std::span<std::byte*> blocks() noexcept { | ||||||
| return block_ptrs_; | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * @brief Span of block start pointers (const). | ||||||
| * @return Span of block start pointers. | ||||||
| */ | ||||||
| [[nodiscard]] constexpr std::span<std::byte* const> blocks() const noexcept { | ||||||
| return block_ptrs_; | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * @brief True if there are no blocks. | ||||||
| * @return True if empty, false otherwise. | ||||||
| */ | ||||||
| [[nodiscard]] constexpr bool empty() const noexcept { | ||||||
| return block_ptrs_.empty(); | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * @brief Reset to empty state (release storage, zero sizes, clear block span). | ||||||
| */ | ||||||
| void reset() noexcept; | ||||||
|
|
||||||
| /** | ||||||
| * @brief The i-th block as a span of bytes. | ||||||
| * | ||||||
| * @param i Block index in [0, num_blocks()). | ||||||
| * @return Span of length block_size() over the block's bytes. | ||||||
| * @throws std::out_of_range if i >= num_blocks(). | ||||||
| */ | ||||||
| [[nodiscard]] std::span<std::byte> block_data(std::size_t i); | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, we can do that. If we decide to remove |
||||||
|
|
||||||
| /** | ||||||
| * @brief The i-th block as a span of bytes. | ||||||
| * | ||||||
| * @param i Block index in [0, num_blocks()). | ||||||
| * @return Span of length block_size() over the block's bytes. | ||||||
| * @throws std::out_of_range if i >= num_blocks(). | ||||||
| */ | ||||||
| [[nodiscard]] std::span<std::byte const> block_data(std::size_t i) const; | ||||||
|
|
||||||
| private: | ||||||
| /** | ||||||
| * @brief Type-erased constructor: take ownership of storage and block metadata. | ||||||
| * | ||||||
| * The deleter is invoked with the storage pointer when this buffer is destroyed. | ||||||
| * @p block_ptrs must refer to memory that remains valid for the lifetime of this | ||||||
| * buffer (typically inside the storage), e.g. from get_blocks() on | ||||||
| * multiple_blocks_allocation. | ||||||
| * | ||||||
| * @param size Total size in bytes. | ||||||
| * @param block_size Size of each block in bytes. | ||||||
| * @param block_ptrs View of block start pointers (not copied; must outlive this | ||||||
| * buffer). | ||||||
| * @param storage Type-erased pointer to the storage (e.g. vector, allocation | ||||||
| * wrapper). | ||||||
| * @param deleter Called with @p storage on destruction. | ||||||
| */ | ||||||
| FixedSizedHostBuffer( | ||||||
| std::size_t size, | ||||||
| std::size_t block_size, | ||||||
| std::span<std::byte*> block_ptrs, | ||||||
| void* storage, | ||||||
| storage_deleter_type deleter | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||||||
| ) | ||||||
| : storage_(storage, std::move(deleter)), | ||||||
| total_size_(size), | ||||||
| block_size_(block_size), | ||||||
| block_ptrs_(block_ptrs) {} | ||||||
|
|
||||||
| std::unique_ptr<void, storage_deleter_type> storage_{}; | ||||||
| std::size_t total_size_{0}; | ||||||
| std::size_t block_size_{default_block_size}; | ||||||
| std::span<std::byte*> block_ptrs_{}; | ||||||
| }; | ||||||
|
|
||||||
| } // namespace rapidsmpf | ||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,146 @@ | ||||||
| /** | ||||||
| * SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||||||
|
nirandaperera marked this conversation as resolved.
Outdated
|
||||||
| * SPDX-License-Identifier: Apache-2.0 | ||||||
| */ | ||||||
|
|
||||||
|
|
||||||
| #include <algorithm> | ||||||
| #include <ranges> | ||||||
|
|
||||||
| #include <rapidsmpf/error.hpp> | ||||||
| #include <rapidsmpf/memory/fixed_sized_host_buffer.hpp> | ||||||
|
|
||||||
| #include <cucascade/memory/fixed_size_host_memory_resource.hpp> | ||||||
|
|
||||||
| namespace { | ||||||
|
|
||||||
| template <typename T> | ||||||
| struct VectorStorage { | ||||||
| std::vector<std::byte*> block_ptrs; | ||||||
| T storage; | ||||||
| }; | ||||||
| } // namespace | ||||||
|
|
||||||
| namespace rapidsmpf { | ||||||
|
|
||||||
| FixedSizedHostBuffer FixedSizedHostBuffer::from_vector( | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I am not sure I like this name. I cannot immediately think of a good name.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about |
||||||
| std::vector<std::byte> vec, std::size_t block_size | ||||||
| ) { | ||||||
| if (vec.empty()) { | ||||||
| return FixedSizedHostBuffer(0, block_size, {}, nullptr, {}); | ||||||
| } | ||||||
|
|
||||||
| std::size_t total_size = vec.size(); | ||||||
| auto shared = std::make_shared<VectorStorage<std::vector<std::byte>>>(); | ||||||
| shared->block_ptrs.reserve((total_size + block_size - 1) / block_size); | ||||||
| for (std::size_t i = 0; i < total_size; i += block_size) { | ||||||
| shared->block_ptrs.push_back(vec.data() + i); | ||||||
| } | ||||||
| shared->storage = std::move(vec); | ||||||
| std::span<std::byte*> blocks_span(shared->block_ptrs); | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With ? |
||||||
| return FixedSizedHostBuffer( | ||||||
| total_size, | ||||||
| block_size, | ||||||
| blocks_span, | ||||||
| shared.get(), | ||||||
| [shared_ = std::move(shared)](void*) mutable { shared_.reset(); } | ||||||
| ); | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: Why do you need a shared ptr for the storage?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. its because |
||||||
| } | ||||||
|
|
||||||
| FixedSizedHostBuffer FixedSizedHostBuffer::from_multi_blocks_alloc( | ||||||
| cucascade::memory::fixed_multiple_blocks_allocation allocation | ||||||
| ) { | ||||||
| if (!allocation || allocation->size() == 0) { | ||||||
| return FixedSizedHostBuffer( | ||||||
| allocation && allocation->block_size() > 0 ? allocation->block_size() | ||||||
| : default_block_size | ||||||
| ); | ||||||
| } | ||||||
| auto shared = std::shared_ptr< | ||||||
| cucascade::memory::fixed_size_host_memory_resource::multiple_blocks_allocation>( | ||||||
| std::move(allocation) | ||||||
| ); | ||||||
| std::span<std::byte*> blocks = shared->get_blocks(); | ||||||
| std::size_t total_bytes = shared->size_bytes(); | ||||||
| std::size_t block_sz = shared->block_size(); | ||||||
| return FixedSizedHostBuffer( | ||||||
| total_bytes, | ||||||
| block_sz, | ||||||
| blocks, | ||||||
| shared.get(), | ||||||
| [shared_ = std::move(shared)](void*) mutable { shared_.reset(); } | ||||||
| ); | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, use |
||||||
| } | ||||||
|
|
||||||
| FixedSizedHostBuffer FixedSizedHostBuffer::from_vectors( | ||||||
| std::vector<std::vector<std::byte>> vecs | ||||||
| ) { | ||||||
| if (vecs.empty()) { | ||||||
| return FixedSizedHostBuffer(); | ||||||
| } | ||||||
|
|
||||||
| size_t const block_sz = vecs[0].size(); | ||||||
| size_t const total_size = block_sz * vecs.size(); | ||||||
| RAPIDSMPF_EXPECTS( | ||||||
| std::ranges::all_of(vecs, [&](auto const& v) { return v.size() == block_sz; }), | ||||||
| "all vectors must be of the same size" | ||||||
| ); | ||||||
|
|
||||||
| auto shared = std::make_shared<VectorStorage<std::vector<std::vector<std::byte>>>>(); | ||||||
|
|
||||||
| shared->block_ptrs.reserve(shared->storage.size()); | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| std::ranges::transform(vecs, std::back_inserter(shared->block_ptrs), [](auto& v) { | ||||||
| return v.data(); | ||||||
| }); | ||||||
| shared->storage = std::move(vecs); | ||||||
| std::span<std::byte*> blocks_span(shared->block_ptrs); | ||||||
| return FixedSizedHostBuffer( | ||||||
| total_size, | ||||||
| block_sz, | ||||||
| std::move(blocks_span), | ||||||
| shared.get(), | ||||||
| [shared_ = std::move(shared)](void*) mutable { shared_.reset(); } | ||||||
| ); | ||||||
| } | ||||||
|
|
||||||
| void FixedSizedHostBuffer::reset() noexcept { | ||||||
| storage_.reset(); | ||||||
| total_size_ = 0; | ||||||
| block_size_ = default_block_size; | ||||||
| block_ptrs_ = {}; | ||||||
| } | ||||||
|
|
||||||
| FixedSizedHostBuffer::FixedSizedHostBuffer(FixedSizedHostBuffer&& other) noexcept | ||||||
| : storage_(std::move(other.storage_)), | ||||||
| total_size_(other.total_size_), | ||||||
| block_size_(other.block_size_), | ||||||
| block_ptrs_(other.block_ptrs_) { | ||||||
| other.reset(); | ||||||
| } | ||||||
|
|
||||||
| FixedSizedHostBuffer& FixedSizedHostBuffer::operator=( | ||||||
| FixedSizedHostBuffer&& other | ||||||
| ) noexcept { | ||||||
| storage_ = std::move(other.storage_); | ||||||
| total_size_ = other.total_size_; | ||||||
| block_size_ = other.block_size_; | ||||||
| block_ptrs_ = other.block_ptrs_; | ||||||
| other.reset(); | ||||||
| return *this; | ||||||
| } | ||||||
|
|
||||||
| std::span<std::byte> FixedSizedHostBuffer::block_data(std::size_t i) { | ||||||
| RAPIDSMPF_EXPECTS( | ||||||
| i < num_blocks(), "FixedSizedHostBuffer::block_data", std::out_of_range | ||||||
| ); | ||||||
| return std::span<std::byte>{block_ptrs_[i], block_size_}; | ||||||
| } | ||||||
|
|
||||||
| std::span<std::byte const> FixedSizedHostBuffer::block_data(std::size_t i) const { | ||||||
| RAPIDSMPF_EXPECTS( | ||||||
| i < num_blocks(), "FixedSizedHostBuffer::block_data", std::out_of_range | ||||||
| ); | ||||||
| return std::span<std::byte const>{block_ptrs_[i], block_size_}; | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue: If the size of the owned data is not evenly divisible by the block size then, depending on the backing storage, the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I intentionally did this TBH. I thought callers can unwrap
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Anyway, since you brought it up, I reverted it. Maybe its best to keep the API simple |
||||||
| } | ||||||
|
|
||||||
| } // namespace rapidsmpf | ||||||
Uh oh!
There was an error while loading. Please reload this page.