Skip to content

Commit d9e240c

Browse files
committed
ENH: Add bulk getValues/setValues API to AbstractDataStore
Add virtual getValues() and setValues() methods to AbstractDataStore for reading/writing contiguous ranges of elements in a single call. DataStore overrides use std::memcpy for maximum throughput. ZarrStore overrides (in FileStore plugin) use chunk-aware bulk access with three optimizations: per-row memcpy, chunk-sticky shared_ptr reuse, and multi-row extension when chunks cover full fast dimensions. Existing methods fill(), copy(), copyFrom(), setTuple(), and fillTuple() are rewritten to use the bulk API internally, so all callers benefit automatically with no code changes. Uses std::make_unique<T[]> for intermediate buffers to avoid the std::vector<bool> bit-packing issue.
1 parent f7d4f6f commit d9e240c

3 files changed

Lines changed: 416 additions & 17 deletions

File tree

src/simplnx/DataStructure/AbstractDataStore.hpp

Lines changed: 84 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <algorithm>
1111
#include <compare>
1212
#include <iterator>
13+
#include <memory>
1314
#include <vector>
1415

1516
namespace nx::core
@@ -410,6 +411,44 @@ class AbstractDataStore : public IDataStore
410411
*/
411412
virtual void setValue(usize index, value_type value) = 0;
412413

414+
/**
415+
* @brief Reads a contiguous range of values from the DataStore into a buffer.
416+
* Reads buffer.size() elements starting from startIndex.
417+
* @param startIndex The first flat element index to read from
418+
* @param buffer Span to write values into; buffer.size() determines count
419+
*/
420+
virtual void getValues(usize startIndex, nonstd::span<T> buffer) const
421+
{
422+
const usize count = buffer.size();
423+
if(startIndex + count > getSize())
424+
{
425+
throw std::out_of_range(fmt::format("AbstractDataStore::getValues: range [{}, {}) exceeds size {}", startIndex, startIndex + count, getSize()));
426+
}
427+
for(usize i = 0; i < count; i++)
428+
{
429+
buffer[i] = getValue(startIndex + i);
430+
}
431+
}
432+
433+
/**
434+
* @brief Writes a contiguous range of values from a buffer into the DataStore.
435+
* Writes buffer.size() elements starting at startIndex.
436+
* @param startIndex The first flat element index to write to
437+
* @param buffer Span of values to write; buffer.size() determines count
438+
*/
439+
virtual void setValues(usize startIndex, nonstd::span<const T> buffer)
440+
{
441+
const usize count = buffer.size();
442+
if(startIndex + count > getSize())
443+
{
444+
throw std::out_of_range(fmt::format("AbstractDataStore::setValues: range [{}, {}) exceeds size {}", startIndex, startIndex + count, getSize()));
445+
}
446+
for(usize i = 0; i < count; i++)
447+
{
448+
setValue(startIndex + i, buffer[i]);
449+
}
450+
}
451+
413452
/**
414453
* @brief Returns the value found at the specified index of the DataStore.
415454
* This cannot be used to edit the value found at the specified index.
@@ -584,7 +623,16 @@ class AbstractDataStore : public IDataStore
584623
*/
585624
virtual void fill(value_type value)
586625
{
587-
std::fill(begin(), end(), value);
626+
const usize totalSize = getSize();
627+
constexpr usize k_BulkBufferSize = 8192;
628+
const usize bufSize = (std::min)(totalSize, k_BulkBufferSize);
629+
auto buffer = std::make_unique<T[]>(bufSize);
630+
std::fill_n(buffer.get(), bufSize, value);
631+
for(usize offset = 0; offset < totalSize; offset += bufSize)
632+
{
633+
const usize batchSize = (std::min)(bufSize, totalSize - offset);
634+
setValues(offset, nonstd::span<const T>(buffer.get(), batchSize));
635+
}
588636
}
589637

590638
/**
@@ -599,7 +647,17 @@ class AbstractDataStore : public IDataStore
599647
{
600648
return false;
601649
}
602-
std::copy(other.begin(), other.end(), begin());
650+
const usize totalSize = getSize();
651+
constexpr usize k_BulkBufferSize = 8192;
652+
const usize bufSize = (std::min)(totalSize, k_BulkBufferSize);
653+
auto buffer = std::make_unique<T[]>(bufSize);
654+
for(usize offset = 0; offset < totalSize; offset += bufSize)
655+
{
656+
const usize batchSize = (std::min)(bufSize, totalSize - offset);
657+
nonstd::span<T> bufSpan(buffer.get(), batchSize);
658+
other.getValues(offset, bufSpan);
659+
setValues(offset, nonstd::span<const T>(buffer.get(), batchSize));
660+
}
603661
return true;
604662
}
605663

@@ -677,10 +735,25 @@ class AbstractDataStore : public IDataStore
677735
totalSrcTuples * sourceNumComponents, destTupleOffset * numComponents, getSize()));
678736
}
679737

680-
auto srcBegin = source.begin() + (srcTupleOffset * sourceNumComponents);
681-
auto srcEnd = srcBegin + (totalSrcTuples * sourceNumComponents);
682-
auto dstBegin = begin() + (destTupleOffset * numComponents);
683-
std::copy(srcBegin, srcEnd, dstBegin);
738+
const usize totalElements = totalSrcTuples * sourceNumComponents;
739+
const usize srcStartIndex = srcTupleOffset * sourceNumComponents;
740+
const usize dstStartIndex = destTupleOffset * numComponents;
741+
constexpr usize k_BulkBufferSize = 8192;
742+
const usize bufSize = (std::min)(totalElements, k_BulkBufferSize);
743+
auto tempBuffer = std::make_unique<T[]>(bufSize);
744+
usize remaining = totalElements;
745+
usize srcOffset = srcStartIndex;
746+
usize dstOffset = dstStartIndex;
747+
while(remaining > 0)
748+
{
749+
const usize batchSize = (std::min)(remaining, bufSize);
750+
nonstd::span<T> bufSpan(tempBuffer.get(), batchSize);
751+
source.getValues(srcOffset, bufSpan);
752+
setValues(dstOffset, nonstd::span<const T>(tempBuffer.get(), batchSize));
753+
srcOffset += batchSize;
754+
dstOffset += batchSize;
755+
remaining -= batchSize;
756+
}
684757
return {};
685758
}
686759

@@ -693,10 +766,9 @@ class AbstractDataStore : public IDataStore
693766
{
694767
usize numComponents = getNumberOfComponents();
695768
index_type offset = tupleIndex * numComponents;
696-
for(usize i = 0; i < numComponents; i++)
697-
{
698-
setValue(offset + i, value);
699-
}
769+
auto tupleValues = std::make_unique<T[]>(numComponents);
770+
std::fill_n(tupleValues.get(), numComponents, value);
771+
setValues(offset, nonstd::span<const T>(tupleValues.get(), numComponents));
700772
}
701773

702774
/**
@@ -743,13 +815,8 @@ class AbstractDataStore : public IDataStore
743815
throw std::runtime_error(ss);
744816
}
745817

746-
index_type numComponents = getNumberOfComponents();
747-
index_type offset = tupleIndex * numComponents;
748-
usize count = values.size();
749-
for(usize i = 0; i < count; i++)
750-
{
751-
setValue(offset + i, values[i]);
752-
}
818+
index_type offset = tupleIndex * getNumberOfComponents();
819+
setValues(offset, values);
753820
}
754821

755822
/**

src/simplnx/DataStructure/DataStore.hpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,45 @@ class DataStore : public AbstractDataStore<T>
505505
std::swap(m_Data.get()[index1], m_Data.get()[index2]);
506506
}
507507

508+
/**
509+
* @brief Reads a contiguous range of values using memcpy.
510+
* @param startIndex The first flat element index to read from
511+
* @param buffer Span to write values into
512+
*/
513+
void getValues(usize startIndex, nonstd::span<T> buffer) const override
514+
{
515+
const usize count = buffer.size();
516+
if(startIndex + count > this->getSize())
517+
{
518+
throw std::out_of_range(fmt::format("DataStore::getValues: range [{}, {}) exceeds size {}", startIndex, startIndex + count, this->getSize()));
519+
}
520+
std::memcpy(buffer.data(), m_Data.get() + startIndex, count * sizeof(T));
521+
}
522+
523+
/**
524+
* @brief Writes a contiguous range of values using memcpy.
525+
* @param startIndex The first flat element index to write to
526+
* @param buffer Span of values to write
527+
*/
528+
void setValues(usize startIndex, nonstd::span<const T> buffer) override
529+
{
530+
const usize count = buffer.size();
531+
if(startIndex + count > this->getSize())
532+
{
533+
throw std::out_of_range(fmt::format("DataStore::setValues: range [{}, {}) exceeds size {}", startIndex, startIndex + count, this->getSize()));
534+
}
535+
std::memcpy(m_Data.get() + startIndex, buffer.data(), count * sizeof(T));
536+
}
537+
538+
/**
539+
* @brief Fills the DataStore with the specified value using direct array access.
540+
* @param value
541+
*/
542+
void fill(value_type value) override
543+
{
544+
std::fill_n(m_Data.get(), this->getSize(), value);
545+
}
546+
508547
/**
509548
* @brief Returns a deep copy of the data store and all its data.
510549
* @return std::unique_ptr<IDataStore>

0 commit comments

Comments
 (0)