Read binary strings/blobs in bulk chunks with a memcpy fast path (#5233)

This commit is contained in:
Niels Lohmann
2026-07-05 19:26:02 +02:00
committed by GitHub
parent eed1587000
commit 83c87cb9e0
4 changed files with 242 additions and 46 deletions
@@ -203,9 +203,51 @@ class iterator_input_adapter
out.insert(out.end(), from, to);
}
// for general iterators, we cannot really do something better than falling back to processing the range one-by-one
// Copy up to count * sizeof(T) bytes into dest, returning the number of
// bytes actually read. For contiguous iterators (e.g. pointers) this is a
// single std::memcpy; for general iterators we fall back to processing the
// range one-by-one.
template<class T>
std::size_t get_elements(T* dest, std::size_t count = 1)
{
return get_elements_impl(dest, count, std::integral_constant<bool, iterator_is_contiguous> {});
}
private:
// whether IteratorType refers to a contiguous range and therefore supports
// a std::memcpy fast path (pointers always do; in C++20 we can also detect
// library iterators such as those of std::vector and std::string)
static constexpr bool iterator_is_contiguous =
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
std::contiguous_iterator<IteratorType> ||
#endif
std::is_pointer<IteratorType>::value;
// contiguous fast path: bulk copy the remaining range with std::memcpy
template<class T>
std::size_t get_elements_impl(T* dest, std::size_t count, std::true_type /*contiguous*/)
{
const std::size_t wanted = count * sizeof(T);
const std::size_t available = static_cast<std::size_t>(std::distance(current, end)) * sizeof(char_type);
const std::size_t copied = (std::min)(wanted, available);
if (JSON_HEDLEY_LIKELY(copied != 0))
{
// the copy must stay within both buffers: the caller-provided
// destination holds `wanted` bytes and the remaining input range
// holds `available` bytes, and `copied` is the minimum of the two
JSON_ASSERT(copied <= wanted); // does not overrun the destination
JSON_ASSERT(copied <= available); // does not read past the input end
// &*current yields the raw address for both raw pointers and
// non-pointer contiguous iterators (e.g. std::vector's iterator)
std::memcpy(dest, &*current, copied);
std::advance(current, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(copied / sizeof(char_type)));
}
return copied;
}
// general fallback: copy the range one element at a time
template<class T>
std::size_t get_elements_impl(T* dest, std::size_t count, std::false_type /*contiguous*/)
{
auto* ptr = reinterpret_cast<char*>(dest);
for (std::size_t read_index = 0; read_index < count * sizeof(T); ++read_index)
@@ -223,7 +265,6 @@ class iterator_input_adapter
return count * sizeof(T);
}
private:
IteratorType begin;
IteratorType current;
IteratorType end;