OpenVDB 9.0.0
HostBuffer.h
Go to the documentation of this file.
1// Copyright Contributors to the OpenVDB Project
2// SPDX-License-Identifier: MPL-2.0
3
4/*!
5 @file HostBuffer.h
6
7 @date April 20, 2021
8
9 @brief HostBuffer - a buffer that contains a shared or private bump
10 pool to either externally or internally managed host memory.
11
12 @details This HostBuffer can be used in multiple ways, most of which are
13 demonstrated in the examples below. Memory in the pool can
14 be managed or unmanged (e.g. internal or external) and can
15 be shared between multiple buffers or belong to a single buffer.
16
17 Example that uses HostBuffer::create inside io::readGrids to create a
18 full self-managed buffer, i.e. not shared and without padding, per grid in the file.
19 @code
20 auto handles = nanovdb::io::readGrids("file.nvdb");
21 @endcode
22
23 Example that uses HostBuffer::createFull. Assuming you have a raw pointer
24 to a NanoVDB grid of unknown type, this examples shows how to create its
25 GridHandle which can be used to enquire about the grid type and meta data.
26 @code
27 void *data;// pointer to a NanoVDB grid of unknown type
28 uint64_t size;// byte size of NanoVDB grid of unknown type
29 auto buffer = nanovdb::HostBuffer::createFull(size, data);
30 nanovdb::GridHandle<> gridHandle(std::move(buffer));
31 @endcode
32
33 Example that uses HostBuffer::createPool for internally managed host memory.
34 Suppose you want to read multiple grids in multiple files, but reuse the same
35 fixed sized memory buffer to both avoid memory fragmentation as well as
36 exceeding the fixed memory ceiling!
37 @code
38 auto pool = nanovdb::HostBuffer::createPool(1 << 30);// 1 GB memory pool
39 std::vector<std::string>> frames;// vector of grid names
40 for (int i=0; i<frames.size(); ++i) {
41 auto handles = nanovdb::io::readGrids(frames[i], 0, pool);// throws if grids in file exceed 1 GB
42 ...
43 pool.reset();// clears all handles and resets the memory pool for reuse
44 }
45 @endcode
46
47 Example that uses HostBuffer::createPool for externally managed host memory.
48 Note that in this example @c handles are allowed to outlive @c pool since
49 they internally store a shared pointer to the memory pool. However @c data
50 MUST outlive @c handles since the pool does not own its memory in this example.
51 @code
52 const size_t poolSize = 1 << 30;// 1 GB
53 uint8_t *data = static_cast<uint8_t*>(std::malloc(size));// 1 GB buffer
54 auto pool = nanovdb::HostBuffer::createPool(poolSize, data);
55 auto handles1 = nanovdb::io::readGrids("file1.nvdb", 0, pool);
56 auto handles2 = nanovdb::io::readGrids("file2.nvdb", 0, pool);
57 ....
58 std::free(data);
59 @endcode
60
61 Example that uses HostBuffer::createPool for externally managed host memory.
62 Note that in this example @c handles are allowed to outlive @c pool since
63 they internally store a shared pointer to the memory pool. However @c array
64 MUST outlive @c handles since the pool does not own its memory in this example.
65 @code
66 const size_t poolSize = 1 << 30;// 1 GB
67 std::unique_ptr<uint8_t[]> array(new uint8_t[size]);// scoped buffer of 1 GB
68 auto pool = nanovdb::HostBuffer::createPool(poolSize, array.get());
69 auto handles = nanovdb::io::readGrids("file.nvdb", 0, pool);
70 @endcode
71*/
72
73#ifndef NANOVDB_HOSTBUFFER_H_HAS_BEEN_INCLUDED
74#define NANOVDB_HOSTBUFFER_H_HAS_BEEN_INCLUDED
75
76#include <stdint.h> // for types like int32_t etc
77#include <cstdio> // for fprintf
78#include <cstdlib> // for std::malloc/std::reallow/std::free
79#include <memory>// for std::make_shared
80#include <mutex>// for std::mutex
81#include <unordered_set>//for std::unordered_set
82#include <cassert>// for assert
83#include <sstream>// for std::stringstream
84#include <cstring>// for memcpy
85
86#define checkPtr(ptr, msg) \
87 { \
88 ptrAssert((ptr), (msg), __FILE__, __LINE__); \
89 }
90
91namespace nanovdb {
92
93template<typename BufferT>
95{
96 static const bool hasDeviceDual = false;
97};
98
99// ----------------------------> HostBuffer <--------------------------------------
100
101/// @brief This is a buffer that contains a shared or private pool
102/// to either externally or internally managed host memory.
103///
104/// @note Terminology:
105/// Pool: 0 = buffer.size() < buffer.poolSize()
106/// Buffer: 0 < buffer.size() < buffer.poolSize()
107/// Full: 0 < buffer.size() = buffer.poolSize()
108/// Empty: 0 = buffer.size() = buffer.poolSize()
110{
111 struct Pool;// forward declaration of private pool struct
112 std::shared_ptr<Pool> mPool;
113 uint64_t mSize; // total number of bytes for the NanoVDB grid.
114 uint8_t* mData; // raw buffer for the NanoVDB grid.
115
116#if defined(DEBUG) || defined(_DEBUG)
117 static inline void ptrAssert(void* ptr, const char* msg, const char* file, int line, bool abort = true)
118 {
119 if (ptr == nullptr) {
120 fprintf(stderr, "NULL pointer error: %s %s %d\n", msg, file, line);
121 if (abort)
122 exit(1);
123 }
124 }
125#else
126 static inline void ptrAssert(void*, const char*, const char*, int, bool = true)
127 {
128 }
129#endif
130
131public:
132 /// @brief Return a full buffer or an empty buffer
133 HostBuffer(uint64_t bufferSize = 0);
134
135 /// @brief Move copy-constructor
136 HostBuffer(HostBuffer&& other);
137
138 /// @brief Custom descructor
139 ~HostBuffer() { this->clear(); }
140
141 /// @brief Move copy assignment operation
143
144 /// @brief Disallow copy-construction
145 HostBuffer(const HostBuffer&) = delete;
146
147 /// @brief Disallow copy assignment operation
148 HostBuffer& operator=(const HostBuffer&) = delete;
149
150 /// @brief Return a pool buffer which satisfies: buffer.size == 0,
151 /// buffer.poolSize() == poolSize, and buffer.data() == nullptr.
152 /// If data==nullptr, memory for the pool will be allocated.
153 ///
154 /// @throw If poolSize is zero.
155 static HostBuffer createPool(uint64_t poolSize, void *data = nullptr);
156
157 /// @brief Return a full buffer which satisfies: buffer.size == bufferSize,
158 /// buffer.poolSize() == bufferSize, and buffer.data() == data.
159 /// If data==nullptr, memory for the pool will be allocated.
160 ///
161 /// @throw If bufferSize is zero.
162 static HostBuffer createFull(uint64_t bufferSize, void *data = nullptr);
163
164 /// @brief Return a buffer with @c bufferSize bytes managed by
165 /// the specified memory @c pool. If none is provided, i.e.
166 /// @c pool == nullptr or @c pool->poolSize() == 0, one is
167 /// created with size @c bufferSize, i.e. a full buffer is returned.
168 ///
169 /// @throw If the specified @c pool has insufficient memory for
170 /// the requested buffer size.
171 static HostBuffer create(uint64_t bufferSize, const HostBuffer* pool = nullptr);
172
173 /// @brief Initialize as a full buffer with the specified size. If data is NULL
174 /// the memory is internally allocated.
175 void init(uint64_t bufferSize, void *data = nullptr);
176
177 //@{
178 /// @brief Retuns a pointer to the raw memory buffer managed by this allocator.
179 ///
180 /// @warning Note that the pointer can be NULL if the allocator was not initialized!
181 const uint8_t* data() const { return mData; }
182 uint8_t* data() { return mData; }
183 //@}
184
185 //@{
186 /// @brief Returns the size in bytes associated with this buffer.
187 uint64_t bufferSize() const { return mSize; }
188 uint64_t size() const { return this->bufferSize(); }
189 //@}
190
191 /// @brief Returns the size in bytes of the memory pool shared with this instance.
192 uint64_t poolSize() const;
193
194 /// @brief Return true if memory is managed (using std::malloc and std:free) by the
195 /// shared pool in this buffer. Else memory is assumed to be managed externally.
196 bool isManaged() const;
197
198 //@{
199 /// @brief Returns true if this buffer has no memory associated with it
200 bool isEmpty() const { return !mPool || mSize == 0 || mData == nullptr; }
201 bool empty() const { return this->isEmpty(); }
202 //@}
203
204 /// @brief Return true if this is a pool, i.e. an empty buffer with a nonempty
205 /// internal pool, i.e. this->size() == 0 and this->poolSize() != 0
206 bool isPool() const { return mSize == 0 && this->poolSize() > 0; }
207
208 /// @brief Return true if the pool exists, is nonempty but has no more available memory
209 bool isFull() const;
210
211 /// @brief Clear this buffer so it is empty.
212 void clear();
213
214 /// @brief Clears all existing buffers that are registered against the memory pool
215 /// and resets the pool so it can be reused to create new buffers.
216 ///
217 /// @throw If this instance is not empty or contains no pool.
218 ///
219 /// @warning This method is not thread-safe!
220 void reset();
221
222 /// @brief Total number of bytes from the pool currently in use by buffers
223 uint64_t poolUsage() const;
224
225 /// @brief resize the pool size. It will attempt to resize the existing
226 /// memory block, but if that fails a deep copy is performed.
227 /// If @c data is not NULL it will be used as new externally
228 /// managed memory for the pool. All registered buffers are
229 /// updated so GridHandle::grid might return a new address (if
230 /// deep copy was performed).
231 ///
232 /// @note This method can be use to resize the memory pool and even
233 /// change it from internally to externally managed memory or vice versa.
234 ///
235 /// @throw if @c poolSize is less than this->poolUsage() the used memory
236 /// or allocations fail.
237 void resizePool(uint64_t poolSize, void *data = nullptr);
238
239}; // HostBuffer class
240
241// --------------------------> Implementation of HostBuffer::Pool <------------------------------------
242
243// This is private struct of HostBuffer so you can safely ignore the API
245{
246 using HashTableT = std::unordered_set<HostBuffer*>;
247 std::mutex mMutex;// mutex for updating mRegister and mFree
249 uint8_t* mData;
250 uint8_t* mFree;
251 uint64_t mSize;
253
254 /// @brief External memory ctor
255 Pool(uint64_t size = 0, void *data = nullptr) : mData((uint8_t*)data), mFree(mData), mSize(size), mManaged(data==nullptr)
256 {
257 if (mManaged) {
258 mData = mFree = static_cast<uint8_t*>(std::malloc(size));
259 if (mData == nullptr) {
260 throw std::runtime_error("Pool::Pool malloc failed");
261 }
262 }
263 }
264
265 /// @brief Custom destructor
267 {
268 assert(mRegister.empty());
269 if (mManaged) {
270 std::free(mData);
271 }
272 }
273
274 /// @brief Disallow copy-construction
275 Pool(const Pool&) = delete;
276
277 /// @brief Disallow move-construction
278 Pool(const Pool&&) = delete;
279
280 /// @brief Disallow copy assignment operation
281 Pool& operator=(const Pool&) = delete;
282
283 /// @brief Disallow move assignment operation
284 Pool& operator=(const Pool&&) = delete;
285
286 /// @brief Return the total number of bytes used from this Pool by buffers
287 uint64_t usage() const { return static_cast<uint64_t>(mFree - mData); }
288
289 /// @brief Allocate a buffer of the specified size and add it to the register
290 void add(HostBuffer *buffer, uint64_t size)
291 {
292 if (mFree + size > mData + mSize) {
293 std::stringstream ss;
294 ss << "HostBuffer::Pool: insufficient memory\n"
295 << "\tA buffer requested " << size << " bytes from a pool with "
296 << mSize <<" bytes of which\n\t" << (mFree-mData)
297 << " bytes are used by " << mRegister.size() << " other buffer(s). "
298 << "Pool is " << (mManaged ? "internally" : "externally") << " managed.\n";
299 //std::cerr << ss.str();
300 throw std::runtime_error(ss.str());
301 }
302 buffer->mSize = size;
303 const std::lock_guard<std::mutex> lock(mMutex);
304 mRegister.insert(buffer);
305 buffer->mData = mFree;
306 mFree += size;
307 }
308
309 /// @brief Remove the specified buffer from the register
310 void remove(HostBuffer *buffer)
311 {
312 const std::lock_guard<std::mutex> lock(mMutex);
313 mRegister.erase(buffer);
314 }
315
316 /// @brief Replaces buffer1 with buffer2 in the register
317 void replace(HostBuffer *buffer1, HostBuffer *buffer2)
318 {
319 const std::lock_guard<std::mutex> lock(mMutex);
320 mRegister.erase( buffer1);
321 mRegister.insert(buffer2);
322 }
323
324 /// @brief Reset the register and all its buffers
325 void reset()
326 {
327 for (HostBuffer *buffer : mRegister) {
328 buffer->mPool.reset();
329 buffer->mSize = 0;
330 buffer->mData = nullptr;
331 }
332 mRegister.clear();
333 mFree = mData;
334 }
335
336 /// @brief Resize this Pool and update registered buffers as needed. If data is no NULL
337 /// it is used as externally managed memory.
338 void resize(uint64_t size, void *data = nullptr)
339 {
340 const uint64_t memUsage = this->usage();
341 if (memUsage > size) {
342 throw std::runtime_error("Pool::resize: insufficient memory");
343 }
344 const bool managed = (data == nullptr);
345 if (mManaged && managed && size != mSize) {// managed -> manged
346 data = std::realloc(mData, size);// performs both copy and free of mData
347 } else if (!mManaged && managed) {// un-managed -> managed
348 data = std::malloc(size);
349 }
350 if (data == nullptr) {
351 throw std::runtime_error("Pool::resize: allocation failed");
352 } else if (data != mData) {
353 if (!(mManaged && managed)) {// no need to copy if managed -> managed
354 memcpy(data, mData, memUsage);
355 }
356 for (HostBuffer *buffer : mRegister) {// update registered buffers
357 buffer->mData = static_cast<uint8_t*>(data) + ptrdiff_t(buffer->mData - mData);
358 }
359 mFree = static_cast<uint8_t*>(data) + memUsage;// update the free pointer
360 if (mManaged && !managed) {// only free if managed -> un-managed
361 std::free(mData);
362 }
363 mData = static_cast<uint8_t*>(data);
364 }
365 mSize = size;
366 mManaged = managed;
367 }
368 /// @brief Return true is all the memory in this pool is in use.
369 bool isFull() const
370 {
371 assert(mFree <= mData + mSize);
372 return mSize > 0 ? mFree == mData + mSize : false;
373 }
374};// struct HostBuffer::Pool
375
376// --------------------------> Implementation of HostBuffer <------------------------------------
377
378inline HostBuffer::HostBuffer(uint64_t size) : mPool(nullptr), mSize(size), mData(nullptr)
379{
380 if (size>0) {
381 mPool = std::make_shared<Pool>(size);
382 mData = mPool->mFree;
383 mPool->mRegister.insert(this);
384 mPool->mFree += size;
385 }
386}
387
388inline HostBuffer::HostBuffer(HostBuffer&& other) : mPool(other.mPool), mSize(other.mSize), mData(other.mData)
389{
390 if (mPool && mSize != 0) {
391 mPool->replace(&other, this);
392 }
393 other.mPool.reset();
394 other.mSize = 0;
395 other.mData = nullptr;
396}
397
398inline void HostBuffer::init(uint64_t bufferSize, void *data)
399{
400 if (bufferSize == 0) {
401 throw std::runtime_error("HostBuffer: invalid buffer size");
402 }
403 if (mPool) {
404 mPool.reset();
405 }
406 if (!mPool || mPool->mSize != bufferSize) {
407 mPool = std::make_shared<Pool>(bufferSize, data);
408 }
409 mPool->add(this, bufferSize);
410}
411
413{
414 if (mPool) {
415 mPool->remove(this);
416 }
417 mPool = other.mPool;
418 mSize = other.mSize;
419 mData = other.mData;
420 if (mPool) {
421 mPool->replace(&other, this);
422 other.mPool.reset();
423 }
424 other.mSize = 0;
425 other.mData = nullptr;
426 return *this;
427}
428
429inline uint64_t HostBuffer::poolSize() const
430{
431 return mPool ? mPool->mSize : 0u;
432}
433
434inline uint64_t HostBuffer::poolUsage() const
435{
436 return mPool ? mPool->usage(): 0u;
437}
438
439inline bool HostBuffer::isManaged() const
440{
441 return mPool ? mPool->mManaged : false;
442}
443
444inline bool HostBuffer::isFull() const
445{
446 return mPool ? mPool->isFull() : false;
447}
448
449inline HostBuffer HostBuffer::createPool(uint64_t poolSize, void *data)
450{
451 if (poolSize == 0) {
452 throw std::runtime_error("HostBuffer: invalid pool size");
453 }
454 HostBuffer buffer;
455 buffer.mPool = std::make_shared<Pool>(poolSize, data);
456 // note the buffer is NOT registered by its pool since it is not using its memory
457 buffer.mSize = 0;
458 buffer.mData = nullptr;
459 return buffer;
460}
461
462inline HostBuffer HostBuffer::createFull(uint64_t bufferSize, void *data)
463{
464 if (bufferSize == 0) {
465 throw std::runtime_error("HostBuffer: invalid buffer size");
466 }
467 HostBuffer buffer;
468 buffer.mPool = std::make_shared<Pool>(bufferSize, data);
469 buffer.mPool->add(&buffer, bufferSize);
470 return buffer;
471}
472
473inline HostBuffer HostBuffer::create(uint64_t bufferSize, const HostBuffer* pool)
474{
475 HostBuffer buffer;
476 if (pool == nullptr || !pool->mPool) {
477 buffer.mPool = std::make_shared<Pool>(bufferSize);
478 } else {
479 buffer.mPool = pool->mPool;
480 }
481 buffer.mPool->add(&buffer, bufferSize);
482 return buffer;
483}
484
485inline void HostBuffer::clear()
486{
487 if (mPool) {// remove self from the buffer register in the pool
488 mPool->remove(this);
489 }
490 mPool.reset();
491 mSize = 0;
492 mData = nullptr;
493}
494
495inline void HostBuffer::reset()
496{
497 if (this->size()>0) {
498 throw std::runtime_error("HostBuffer: only empty buffers can call reset");
499 }
500 if (!mPool) {
501 throw std::runtime_error("HostBuffer: this buffer contains no pool to reset");
502 }
503 mPool->reset();
504}
505
506inline void HostBuffer::resizePool(uint64_t size, void *data)
507{
508 if (!mPool) {
509 throw std::runtime_error("HostBuffer: this buffer contains no pool to resize");
510 }
511 mPool->resize(size, data);
512}
513
514} // namespace nanovdb
515
516#endif // end of NANOVDB_HOSTBUFFER_H_HAS_BEEN_INCLUDED
This is a buffer that contains a shared or private pool to either externally or internally managed ho...
Definition: HostBuffer.h:110
~HostBuffer()
Custom descructor.
Definition: HostBuffer.h:139
const uint8_t * data() const
Retuns a pointer to the raw memory buffer managed by this allocator.
Definition: HostBuffer.h:181
HostBuffer(const HostBuffer &)=delete
Disallow copy-construction.
HostBuffer(uint64_t bufferSize=0)
Return a full buffer or an empty buffer.
Definition: HostBuffer.h:378
HostBuffer & operator=(HostBuffer &&other)
Move copy assignment operation.
Definition: HostBuffer.h:412
uint64_t bufferSize() const
Returns the size in bytes associated with this buffer.
Definition: HostBuffer.h:187
bool empty() const
Definition: HostBuffer.h:201
static HostBuffer createFull(uint64_t bufferSize, void *data=nullptr)
Return a full buffer which satisfies: buffer.size == bufferSize, buffer.poolSize() == bufferSize,...
Definition: HostBuffer.h:462
uint64_t poolSize() const
Returns the size in bytes of the memory pool shared with this instance.
Definition: HostBuffer.h:429
uint64_t size() const
Definition: HostBuffer.h:188
void init(uint64_t bufferSize, void *data=nullptr)
Initialize as a full buffer with the specified size. If data is NULL the memory is internally allocat...
Definition: HostBuffer.h:398
HostBuffer & operator=(const HostBuffer &)=delete
Disallow copy assignment operation.
uint64_t poolUsage() const
Total number of bytes from the pool currently in use by buffers.
Definition: HostBuffer.h:434
uint8_t * data()
Definition: HostBuffer.h:182
bool isPool() const
Return true if this is a pool, i.e. an empty buffer with a nonempty internal pool,...
Definition: HostBuffer.h:206
bool isManaged() const
Return true if memory is managed (using std::malloc and std:free) by the shared pool in this buffer....
Definition: HostBuffer.h:439
static HostBuffer create(uint64_t bufferSize, const HostBuffer *pool=nullptr)
Return a buffer with bufferSize bytes managed by the specified memory pool. If none is provided,...
Definition: HostBuffer.h:473
void clear()
Clear this buffer so it is empty.
Definition: HostBuffer.h:485
bool isEmpty() const
Returns true if this buffer has no memory associated with it.
Definition: HostBuffer.h:200
void reset()
Clears all existing buffers that are registered against the memory pool and resets the pool so it can...
Definition: HostBuffer.h:495
bool isFull() const
Return true if the pool exists, is nonempty but has no more available memory.
Definition: HostBuffer.h:444
void resizePool(uint64_t poolSize, void *data=nullptr)
resize the pool size. It will attempt to resize the existing memory block, but if that fails a deep c...
Definition: HostBuffer.h:506
static HostBuffer createPool(uint64_t poolSize, void *data=nullptr)
Return a pool buffer which satisfies: buffer.size == 0, buffer.poolSize() == poolSize,...
Definition: HostBuffer.h:449
Definition: Camera.h:16
Index64 memUsage(const TreeT &tree, bool threaded=true)
Return the total amount of memory in bytes occupied by this tree.
Definition: Count.h:408
Definition: HostBuffer.h:95
static const bool hasDeviceDual
Definition: HostBuffer.h:96
Definition: HostBuffer.h:245
std::unordered_set< HostBuffer * > HashTableT
Definition: HostBuffer.h:246
void resize(uint64_t size, void *data=nullptr)
Resize this Pool and update registered buffers as needed. If data is no NULL it is used as externally...
Definition: HostBuffer.h:338
uint64_t usage() const
Return the total number of bytes used from this Pool by buffers.
Definition: HostBuffer.h:287
void replace(HostBuffer *buffer1, HostBuffer *buffer2)
Replaces buffer1 with buffer2 in the register.
Definition: HostBuffer.h:317
Pool(uint64_t size=0, void *data=nullptr)
External memory ctor.
Definition: HostBuffer.h:255
uint64_t mSize
Definition: HostBuffer.h:251
Pool(const Pool &)=delete
Disallow copy-construction.
void add(HostBuffer *buffer, uint64_t size)
Allocate a buffer of the specified size and add it to the register.
Definition: HostBuffer.h:290
void remove(HostBuffer *buffer)
Remove the specified buffer from the register.
Definition: HostBuffer.h:310
uint8_t * mFree
Definition: HostBuffer.h:250
Pool & operator=(const Pool &&)=delete
Disallow move assignment operation.
uint8_t * mData
Definition: HostBuffer.h:249
std::mutex mMutex
Definition: HostBuffer.h:247
Pool(const Pool &&)=delete
Disallow move-construction.
HashTableT mRegister
Definition: HostBuffer.h:248
Pool & operator=(const Pool &)=delete
Disallow copy assignment operation.
void reset()
Reset the register and all its buffers.
Definition: HostBuffer.h:325
~Pool()
Custom destructor.
Definition: HostBuffer.h:266
bool isFull() const
Return true is all the memory in this pool is in use.
Definition: HostBuffer.h:369
bool mManaged
Definition: HostBuffer.h:252