[libcxx-commits] [PATCH] D132505: [libc++] Refactor deque::iterator algorithm optimizations
Louis Dionne via Phabricator via libcxx-commits
libcxx-commits at lists.llvm.org
Thu Sep 8 10:49:58 PDT 2022
ldionne requested changes to this revision.
ldionne added a comment.
I really like this patch in spirit! It should be rebased onto `main`, though.
================
Comment at: libcxx/include/__algorithm/copy.h:73
+ if (__range_size < __segment_size) {
+ auto __iters = std::__copy(__first, __last, __segment.__begin_);
+ __result += __range_size;
----------------
It seems to me that what you really need here is some kind of `__copy_up_to` primitive, where we would copy `[__first, as-far-as-we-can-go)` to `[__segment.__begin_, segment-end)`. Then you would not need to know the size of the input segment, and this optimization would apply to non-random-access iterators as well.
It might not be worth trying to optimize for non random-access input iterators anyway though, since we will end up having to loop over each element regardless inside `__copy_up_to`, which is not much better than what we'd do without applying any segmented optimization at all.
================
Comment at: libcxx/include/__iterator/segmented_iterator.h:12-26
+// segmented iterators are iterators which are made up of multiple other iterators.
+// For example, std::deque uses segments to store a few elements in a contiguous block and then have a new block
+// after that. To allow algorithms to optimize on this an iterator can specialize __segmented_iterator_traits.
+// If __segmented_iterator_traits::__is_segmented_iterator::value is true, the following functions have to be
+// implemented:
+// - `static <undefined> __get_forward_range(_Iterator __first, _Iterator __last);`
+// where <undefined> is a range which contains the segments to iterate over. The segments are iterated front to back.
----------------
I think your definition of segmented iterators is an excellent starting point, however we could make the following changes based on prior art (http://lafstern.org/matt/segmented.pdf):
```
// Segmented iterators are iterators over (not necessarily contiguous) sub-ranges.
//
// For example, std::deque stores its data into multiple blocks of contiguous memory,
// which are not stored contiguously themselves. The concept of segmented iterators
// allows algorithms to operate over these multi-level iterators natively, opening the
// door to various optimizations. See http://lafstern.org/matt/segmented.pdf for details.
//
// Given Traits = __segmented_iterator_traits<It>, if Traits::__is_segmented_iterator::value
// is true, the following functions and associated types must be provided:
// - Traits::__local_iterator
// The type of iterators used to iterate inside a segment.
//
// - Traits::__segment_iterator
// The type of iterators used to iterate over segments.
// Segment iterators can be forward iterators or bidirectional iterators, depending on the
// underlying data structure.
//
// - static __segment_iterator Traits::__segment(It __it)
// Returns an iterator to the segment that the provided iterator is in.
//
// - static __local_iterator Traits::__local(It __it)
// Returns the local iterator pointing to the element that the provided iterator points to.
//
// - static __local_iterator Traits::__begin(__segment_iterator __it)
// Returns the local iterator to the beginning of the segment that the provided iterator is pointing into.
//
// - static __local_iterator Traits::__end(__segment_iterator __it)
// Returns the one-past-the-end local iterator to the segment that the provided iterator is pointing into.
```
Then, you can use it as:
```
void f(__deque_iterator<T> __first, __deque_iterator<T> __last) {
using _Traits = __segmented_iterator_traits<__deque_iterator<T> >;
Traits::__segment_iterator __sfirst = _Traits::__segment(__first);
Traits::__segment_iterator __slast = _Traits::__segment(__last);
use(Traits::__local(__first), Traits::__end(__sfirst)); // special case the first block
for (++__sfirst; __sfirst != __slast; ++__sfirst) {
use(Traits::__begin(__sfirst), Traits::__end(__sfirst));
}
use(Traits::__begin(__slast), Traits::__local(__last));
}
```
At a glance, I think we would implement the traits for `deque` something like:
```
template <class T>
struct __segmented_iterator_traits<__deque_iterator<T> > {
struct __segment_iterator {
// fun stuff
};
using __local_iterator = T*;
static __segment_iterator __segment(__deque_iterator<T> __it) {
return __segment_iterator(__it.__m_iter_);
}
static __local_iterator __local(__deque_iterator<T> __it) {
return __it.__ptr_;
}
static __local_iterator __begin(__segment_iterator __it) {
return *__it.__m_iter_;
}
static __local_iterator __end(__segment_iterator __it) {
return *__it.__m_iter_ + BLOCK_SIZE;
}
};
```
================
Comment at: libcxx/test/std/algorithms/alg.modifying.operations/alg.move/ranges.move.pass.cpp:82
+template <class InContainer, class OutContainer, class In, class Out, class Sent = In>
+constexpr void test_containers() {
----------------
> Also fix `ranges::move` with contiguous iterators and non-iterator sentinels as a drive-by.
I would really prefer doing this separately. If @var-const 's patch does that already, can you simply double-check that your added tests pass now? Basically I want to make sure that you don't lose test coverage when you rebase on top of his change.
Repository:
rG LLVM Github Monorepo
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D132505/new/
https://reviews.llvm.org/D132505
More information about the libcxx-commits
mailing list