OpenVDB  9.0.1
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 
91 namespace nanovdb {
92 
93 template<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 
131 public:
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
142  HostBuffer& operator=(HostBuffer&& other);
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;
252  bool mManaged;
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 
378 inline 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 
388 inline 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 
398 inline 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 && mSize != 0) {
421  mPool->replace(&other, this);
422  }
423  other.mPool.reset();
424  other.mSize = 0;
425  other.mData = nullptr;
426  return *this;
427 }
428 
429 inline uint64_t HostBuffer::poolSize() const
430 {
431  return mPool ? mPool->mSize : 0u;
432 }
433 
434 inline uint64_t HostBuffer::poolUsage() const
435 {
436  return mPool ? mPool->usage(): 0u;
437 }
438 
439 inline bool HostBuffer::isManaged() const
440 {
441  return mPool ? mPool->mManaged : false;
442 }
443 
444 inline bool HostBuffer::isFull() const
445 {
446  return mPool ? mPool->isFull() : false;
447 }
448 
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 
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 
473 inline 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 
485 inline 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 
495 inline 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 
506 inline 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
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:94
void reset()
Reset the register and all its buffers.
Definition: HostBuffer.h:325
void replace(HostBuffer *buffer1, HostBuffer *buffer2)
Replaces buffer1 with buffer2 in the register.
Definition: HostBuffer.h:317
void add(HostBuffer *buffer, uint64_t size)
Allocate a buffer of the specified size and add it to the register.
Definition: HostBuffer.h:290
HashTableT mRegister
Definition: HostBuffer.h:248
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
static HostBuffer createPool(uint64_t poolSize, void *data=nullptr)
Return a pool buffer which satisfies: buffer.size == 0, buffer.poolSize() == poolSize, and buffer.data() == nullptr. If data==nullptr, memory for the pool will be allocated.
Definition: HostBuffer.h:449
~HostBuffer()
Custom descructor.
Definition: HostBuffer.h:139
uint64_t poolSize() const
Returns the size in bytes of the memory pool shared with this instance.
Definition: HostBuffer.h:429
std::mutex mMutex
Definition: HostBuffer.h:247
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
bool isFull() const
Return true is all the memory in this pool is in use.
Definition: HostBuffer.h:369
Definition: NanoVDB.h:184
Pool(uint64_t size=0, void *data=nullptr)
External memory ctor.
Definition: HostBuffer.h:255
bool isPool() const
Return true if this is a pool, i.e. an empty buffer with a nonempty internal pool, i.e. this->size() == 0 and this->poolSize() != 0.
Definition: HostBuffer.h:206
Definition: HostBuffer.h:244
void clear()
Clear this buffer so it is empty.
Definition: HostBuffer.h:485
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
HostBuffer & operator=(HostBuffer &&other)
Move copy assignment operation.
Definition: HostBuffer.h:412
uint64_t poolUsage() const
Total number of bytes from the pool currently in use by buffers.
Definition: HostBuffer.h:434
uint8_t * mData
Definition: HostBuffer.h:249
static const bool hasDeviceDual
Definition: HostBuffer.h:96
This is a buffer that contains a shared or private pool to either externally or internally managed ho...
Definition: HostBuffer.h:109
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
uint64_t size() const
Returns the size in bytes associated with this buffer.
Definition: HostBuffer.h:188
bool mManaged
Definition: HostBuffer.h:252
uint8_t * mFree
Definition: HostBuffer.h:250
void reset()
Clears all existing buffers that are registered against the memory pool and resets the pool so it can...
Definition: HostBuffer.h:495
const uint8_t * data() const
Retuns a pointer to the raw memory buffer managed by this allocator.
Definition: HostBuffer.h:181
uint64_t bufferSize() const
Returns the size in bytes associated with this buffer.
Definition: HostBuffer.h:187
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
uint8_t * data()
Retuns a pointer to the raw memory buffer managed by this allocator.
Definition: HostBuffer.h:182
uint64_t usage() const
Return the total number of bytes used from this Pool by buffers.
Definition: HostBuffer.h:287
bool empty() const
Returns true if this buffer has no memory associated with it.
Definition: HostBuffer.h:201
HostBuffer(uint64_t bufferSize=0)
Return a full buffer or an empty buffer.
Definition: HostBuffer.h:378
bool isFull() const
Return true if the pool exists, is nonempty but has no more available memory.
Definition: HostBuffer.h:444
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
~Pool()
Custom destructor.
Definition: HostBuffer.h:266
std::unordered_set< HostBuffer * > HashTableT
Definition: HostBuffer.h:246
uint64_t mSize
Definition: HostBuffer.h:251
bool isEmpty() const
Returns true if this buffer has no memory associated with it.
Definition: HostBuffer.h:200