Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions libcxx/docs/ReleaseNotes/22.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ Implemented Papers
Improvements and New Features
-----------------------------

- The ``std::distance`` and ``std::ranges::distance`` algorithms have been optimized for segmented iterators (e.g.,
``std::join_view`` iterators), reducing the complexity from ``O(n)`` to ``O(n / segment_size)``. Benchmarks show
performance improvements of over 1600x in favorable cases with large segment sizes (e.g., 1024).

Deprecations and Removals
-------------------------

Expand Down
55 changes: 41 additions & 14 deletions libcxx/include/__iterator/distance.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,41 +10,71 @@
#ifndef _LIBCPP___ITERATOR_DISTANCE_H
#define _LIBCPP___ITERATOR_DISTANCE_H

#include <__algorithm/for_each_segment.h>
#include <__config>
#include <__iterator/concepts.h>
#include <__iterator/incrementable_traits.h>
#include <__iterator/iterator_traits.h>
#include <__iterator/segmented_iterator.h>
#include <__ranges/access.h>
#include <__ranges/concepts.h>
#include <__ranges/size.h>
#include <__type_traits/decay.h>
#include <__type_traits/enable_if.h>
#include <__type_traits/remove_cvref.h>
#include <__utility/move.h>

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
#endif

_LIBCPP_PUSH_MACROS
#include <__undef_macros>

_LIBCPP_BEGIN_NAMESPACE_STD

template <class _InputIter>
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17 typename iterator_traits<_InputIter>::difference_type
__distance(_InputIter __first, _InputIter __last, input_iterator_tag) {
typename iterator_traits<_InputIter>::difference_type __r(0);
#if _LIBCPP_STD_VER >= 20
template <class _Iter>
using __iter_distance_t _LIBCPP_NODEBUG = std::iter_difference_t<_Iter>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have __iter_diff_t in iterator_traits.h. It doesn't do exactly what you want, but I think we should make it do what you need instead of introducing a new thing that's essentially identical.

Basically, we should try re-defining __iter_diff_t in iterator_traits.h as:

#if _LIBCPP_STD_VER >= 20
template <class _Iter>
using __iter_diff_t _LIBCPP_NODEBUG = std::iter_difference_t<_Iter>;
#else
template <class _Iter>
using __iter_diff_t _LIBCPP_NODEBUG = typename iterator_traits<_Iter>::difference_type;
#endif

And if that's not possible for some reason, it would be interesting to understand why.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that the standard hasn't requires iterator_traits<I>::difference_type to be same as iter_difference_t<I> yet when I is a non-random-access iterator type. One is allowed to make them different by specializing incrementable_traits. This is perhaps a defect, for which I've submitted LWG4080.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the potential mismatch between iterator_traits<I>::difference_type and iter_difference_t<I> (as pointed out by @frederick-vs-ja), I am now consistently using iterator_traits<I>::difference_type as the return type for the non-range std::__distance algorithm, and iter_difference_t<I> for the range algorithm.

To enable the range algorithm to reuse the same optimization introduced for the non-range std::__distance, I am now adding a static_cast to explicitly cast the result of std::__distance to the desired type iter_difference_t<I>.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am now consistently using iterator_traits<I>::difference_type as the return type for the non-range std::__distance algorithm.

However, this approach fails when the range algorithm std::ranges::distance forwards to std::__distance with I = cpp20_input_iterator, resulting in a hard substitution error: no type named 'difference_type' in 'std::iterator_traits<cpp20_input_iterator<int *>>', as captured by the tests range.pass.cpp and iterator_sentinel.pass.cpp (see the full log: stage1 (generic-modules, clang-21, clang++-21)).

The root cause for this error is that cpp20_input_iterator does not have the member type iterator_category (although it provides the iterator_concept member type), leading to a hard substitution failure in std::iterator_traits<cpp20_input_iterator<Iter>>::difference_type (See A reproducer).

To resolve this, I still need the above internal definition of __iter_distance_t, which is used as the return type of the internal function std::__distance. This allows both the non-range std::distance and range version std::ranges::distance to share the same implementation and optimization. However, to handle the potential mismatch between iterator_traits<I>::difference_type and iter_difference_t<I>, I explicitly specified iterator_traits<I>::difference_type as the return type of std::distance and iter_difference_t<I> as the return type of std::ranges::distance. This way the underlying implicit conversion would automatically take place when there is a type mismatch.

Basically, we should try re-defining __iter_diff_t in iterator_traits.h as:

#if _LIBCPP_STD_VER >= 20
template <class _Iter>
using __iter_diff_t _LIBCPP_NODEBUG = std::iter_difference_t<_Iter>;
#else
template <class _Iter>
using __iter_diff_t _LIBCPP_NODEBUG = typename iterator_traits<_Iter>::difference_type;
#endif

And if that's not possible for some reason, it would be interesting to understand why.

I am a bit hesitant to make this change because __iter_diff_t is currently unconditionally defined as iterator_traits<_Iter>::difference_type, and it is used by many algorithms. Making this change would introduce the potential type mismatch to many algorithms. My current approach with the introduction of __iter_distance_t ensures that the potential type mismatch is confined and handled within distance.h.

#else
template <class _Iter>
using __iter_distance_t _LIBCPP_NODEBUG = typename iterator_traits<_Iter>::difference_type;
#endif

template <class _InputIter, class _Sent>
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17 __iter_distance_t<_InputIter>
__distance(_InputIter __first, _Sent __last) {
__iter_distance_t<_InputIter> __r(0);
for (; __first != __last; ++__first)
++__r;
return __r;
}

template <class _RandIter>
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17 typename iterator_traits<_RandIter>::difference_type
__distance(_RandIter __first, _RandIter __last, random_access_iterator_tag) {
template <class _RandIter, __enable_if_t<__has_random_access_iterator_category<_RandIter>::value, int> = 0>
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17 __iter_distance_t<_RandIter>
__distance(_RandIter __first, _RandIter __last) {
return __last - __first;
}

#if _LIBCPP_STD_VER >= 20
template <class _SegmentedIter,
__enable_if_t<!__has_random_access_iterator_category<_SegmentedIter>::value &&
__is_segmented_iterator_v<_SegmentedIter>,
int> = 0>
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17 __iter_distance_t<_SegmentedIter>
__distance(_SegmentedIter __first, _SegmentedIter __last) {
__iter_distance_t<_SegmentedIter> __r(0);
std::__for_each_segment(__first, __last, [&__r](auto __lfirst, auto __llast) {
__r += std::__distance(__lfirst, __llast);
});
return __r;
}
#endif // _LIBCPP_STD_VER >= 20

template <class _InputIter>
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17 typename iterator_traits<_InputIter>::difference_type
distance(_InputIter __first, _InputIter __last) {
return std::__distance(__first, __last, typename iterator_traits<_InputIter>::iterator_category());
return std::__distance(__first, __last);
}

#if _LIBCPP_STD_VER >= 20
Expand All @@ -56,12 +86,7 @@ struct __distance {
template <class _Ip, sentinel_for<_Ip> _Sp>
requires(!sized_sentinel_for<_Sp, _Ip>)
_LIBCPP_HIDE_FROM_ABI constexpr iter_difference_t<_Ip> operator()(_Ip __first, _Sp __last) const {
iter_difference_t<_Ip> __n = 0;
while (__first != __last) {
++__first;
++__n;
}
return __n;
return std::__distance(std::move(__first), std::move(__last));
}

template <class _Ip, sized_sentinel_for<decay_t<_Ip>> _Sp>
Expand Down Expand Up @@ -92,4 +117,6 @@ inline constexpr auto distance = __distance{};

_LIBCPP_END_NAMESPACE_STD

_LIBCPP_POP_MACROS

#endif // _LIBCPP___ITERATOR_DISTANCE_H
84 changes: 84 additions & 0 deletions libcxx/test/benchmarks/iterators/distance.bench.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++03, c++11, c++14, c++17

#include <algorithm>
#include <cstddef>
#include <deque>
#include <iterator>
#include <ranges>
#include <vector>

#include <benchmark/benchmark.h>

int main(int argc, char** argv) {
auto std_distance = [](auto first, auto last) { return std::distance(first, last); };

// {std,ranges}::distance(std::deque)
{
auto bm = [](std::string name, auto distance) {
benchmark::RegisterBenchmark(
name,
[distance](auto& st) {
std::size_t const size = st.range(0);
std::deque<int> c(size, 1);

for ([[maybe_unused]] auto _ : st) {
benchmark::DoNotOptimize(c);
auto result = distance(c.begin(), c.end());
benchmark::DoNotOptimize(result);
}
})
->Arg(50) // non power-of-two
->Arg(1024)
->Arg(4096)
->Arg(8192);
};
bm.operator()("std::distance(deque<int>)", std_distance);
bm.operator()("rng::distance(deque<int>)", std::ranges::distance);
}

// {std,ranges}::distance(std::join_view)
{
auto bm = []<class Container>(std::string name, auto distance, std::size_t seg_size) {
benchmark::RegisterBenchmark(
name,
[distance, seg_size](auto& st) {
std::size_t const size = st.range(0);
std::size_t const segments = (size + seg_size - 1) / seg_size;
Container c(segments);
for (std::size_t i = 0, n = size; i < segments; ++i, n -= seg_size) {
c[i].resize(std::min(seg_size, n));
}

auto view = c | std::views::join;
auto first = view.begin();
auto last = view.end();

for ([[maybe_unused]] auto _ : st) {
benchmark::DoNotOptimize(c);
auto result = distance(first, last);
benchmark::DoNotOptimize(result);
}
})
->Arg(50) // non power-of-two
->Arg(1024)
->Arg(4096)
->Arg(8192);
};
bm.operator()<std::vector<std::vector<int>>>("std::distance(join_view(vector<vector<int>>))", std_distance, 256);
bm.operator()<std::vector<std::vector<int>>>(
"rng::distance(join_view(vector<vector<int>>)", std::ranges::distance, 256);
}

benchmark::Initialize(&argc, argv);
benchmark::RunSpecifiedBenchmarks();
benchmark::Shutdown();
return 0;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,38 +16,73 @@
// Iter::difference_type
// distance(Iter first, Iter last); // constexpr in C++17

#include <iterator>
#include <array>
#include <cassert>
#include <deque>
#include <iterator>
#include <vector>
#include <type_traits>

#include "test_macros.h"
#include "test_iterators.h"

template <class It>
TEST_CONSTEXPR_CXX17
void check_distance(It first, It last, typename std::iterator_traits<It>::difference_type dist)
{
typedef typename std::iterator_traits<It>::difference_type Difference;
static_assert(std::is_same<decltype(std::distance(first, last)), Difference>::value, "");
assert(std::distance(first, last) == dist);
TEST_CONSTEXPR_CXX17 void check_distance(It first, It last, typename std::iterator_traits<It>::difference_type dist) {
typedef typename std::iterator_traits<It>::difference_type Difference;
static_assert(std::is_same<decltype(std::distance(first, last)), Difference>::value, "");
assert(std::distance(first, last) == dist);
}

TEST_CONSTEXPR_CXX17 bool tests()
{
const char* s = "1234567890";
check_distance(cpp17_input_iterator<const char*>(s), cpp17_input_iterator<const char*>(s+10), 10);
check_distance(forward_iterator<const char*>(s), forward_iterator<const char*>(s+10), 10);
check_distance(bidirectional_iterator<const char*>(s), bidirectional_iterator<const char*>(s+10), 10);
check_distance(random_access_iterator<const char*>(s), random_access_iterator<const char*>(s+10), 10);
check_distance(s, s+10, 10);
return true;
#if TEST_STD_VER >= 20
/*TEST_CONSTEXPR_CXX26*/ void test_deque() { // TODO: Mark as TEST_CONSTEXPR_CXX26 once std::deque is constexpr
using Container = std::deque<std::deque<double>>;
Container c;
auto view = c | std::views::join;
Container::difference_type n = 0;
for (std::size_t i = 0; i < 10; ++i) {
n += i;
c.push_back(Container::value_type(i));
}
assert(std::distance(view.begin(), view.end()) == n);
}
#endif

TEST_CONSTEXPR_CXX17 bool tests() {
const char* s = "1234567890";
check_distance(cpp17_input_iterator<const char*>(s), cpp17_input_iterator<const char*>(s + 10), 10);
check_distance(forward_iterator<const char*>(s), forward_iterator<const char*>(s + 10), 10);
check_distance(bidirectional_iterator<const char*>(s), bidirectional_iterator<const char*>(s + 10), 10);
check_distance(random_access_iterator<const char*>(s), random_access_iterator<const char*>(s + 10), 10);
check_distance(s, s + 10, 10);

#if TEST_STD_VER >= 20
{
using Container = std::vector<std::vector<int>>;
Container c;
auto view = c | std::views::join;
Container::difference_type n = 0;
for (std::size_t i = 0; i < 10; ++i) {
n += i;
c.push_back(Container::value_type(i));
}
assert(std::distance(view.begin(), view.end()) == n);
}
{
using Container = std::array<std::array<char, 3>, 10>;
Container c;
auto view = c | std::views::join;
assert(std::distance(view.begin(), view.end()) == 30);
}
if (!TEST_IS_CONSTANT_EVALUATED) // TODO: Use TEST_STD_AT_LEAST_26_OR_RUNTIME_EVALUATED when std::deque is made constexpr
test_deque();
#endif
return true;
}

int main(int, char**)
{
tests();
int main(int, char**) {
tests();
#if TEST_STD_VER >= 17
static_assert(tests(), "");
static_assert(tests(), "");
#endif
return 0;
return 0;
}
Loading
Loading