Skip to content

[libc++] Introduce __product_iterator_traits and optimise flat_map::insert #139454

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions libcxx/docs/ReleaseNotes/21.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ Improvements and New Features
- The ``bitset::to_string`` function has been optimized, resulting in a performance improvement of up to 8.3x for bitsets
with uniformly distributed zeros and ones, and up to 13.5x and 16.1x for sparse and dense bitsets, respectively.

- The ``flat_map::insert`` and ``flat_set::insert_range`` have been optimized, resulting in a performance improvement of up
to 10x for inserting elements into a ``flat_map`` when the input range is a ``flat_map`` or a ``zip_view``.

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

Expand Down
1 change: 1 addition & 0 deletions libcxx/include/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,7 @@ set(files
__iterator/ostreambuf_iterator.h
__iterator/permutable.h
__iterator/prev.h
__iterator/product_iterator.h
__iterator/projected.h
__iterator/ranges_iterator_traits.h
__iterator/readable_traits.h
Expand Down
27 changes: 27 additions & 0 deletions libcxx/include/__flat_map/key_value_iterator.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@
#include <__compare/three_way_comparable.h>
#include <__concepts/convertible_to.h>
#include <__config>
#include <__cstddef/size_t.h>
#include <__iterator/iterator_traits.h>
#include <__iterator/product_iterator.h>
#include <__memory/addressof.h>
#include <__type_traits/conditional.h>
#include <__utility/forward.h>
#include <__utility/move.h>
#include <__utility/pair.h>

Expand Down Expand Up @@ -57,6 +60,8 @@ struct __key_value_iterator {
template <class, class, class, bool>
friend struct __key_value_iterator;

friend struct __product_iterator_traits<__key_value_iterator>;

public:
using iterator_concept = random_access_iterator_tag;
// `__key_value_iterator` only satisfy "Cpp17InputIterator" named requirements, because
Expand Down Expand Up @@ -167,6 +172,28 @@ struct __key_value_iterator {
}
};

template <class _Owner, class _KeyContainer, class _MappedContainer, bool _Const>
struct __product_iterator_traits<__key_value_iterator<_Owner, _KeyContainer, _MappedContainer, _Const>> {
static constexpr size_t __size = 2;

template <size_t _Nth, class _Iter>
_LIBCPP_HIDE_FROM_ABI static decltype(auto) __get_iterator_element(_Iter&& __it)
requires(_Nth <= 1)
{
if constexpr (_Nth == 0) {
return std::forward<_Iter>(__it).__key_iter_;
} else {
return std::forward<_Iter>(__it).__mapped_iter_;
}
}

template <class _KeyIter, class _MappedIter>
_LIBCPP_HIDE_FROM_ABI static auto __make_product_iterator(_KeyIter&& __key_iter, _MappedIter&& __mapped_iter) {
return __key_value_iterator<_Owner, _KeyContainer, _MappedContainer, _Const>(
std::forward<_KeyIter>(__key_iter), std::forward<_MappedIter>(__mapped_iter));
}
};

_LIBCPP_END_NAMESPACE_STD

#endif // _LIBCPP_STD_VER >= 23
Expand Down
22 changes: 20 additions & 2 deletions libcxx/include/__flat_map/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#define _LIBCPP___FLAT_MAP_UTILS_H

#include <__config>
#include <__iterator/product_iterator.h>
#include <__type_traits/container_traits.h>
#include <__utility/exception_guard.h>
#include <__utility/forward.h>
Expand Down Expand Up @@ -79,8 +80,6 @@ struct __flat_map_utils {
return typename _Map::iterator(std::move(__key_it), std::move(__mapped_it));
}

// TODO: We could optimize this, see
// https://github.com/llvm/llvm-project/issues/108624
template <class _Map, class _InputIterator, class _Sentinel>
_LIBCPP_HIDE_FROM_ABI static typename _Map::size_type
__append(_Map& __map, _InputIterator __first, _Sentinel __last) {
Expand All @@ -93,6 +92,25 @@ struct __flat_map_utils {
}
return __num_appended;
}

template <class _Map, class _InputIterator>
_LIBCPP_HIDE_FROM_ABI static typename _Map::size_type
__append(_Map& __map, _InputIterator __first, _InputIterator __last)
requires __is_product_iterator_of_size<_InputIterator, 2>::value
{
auto __s1 = __map.__containers_.keys.size();
__map.__containers_.keys.insert(
__map.__containers_.keys.end(),
__product_iterator_traits<_InputIterator>::template __get_iterator_element<0>(__first),
__product_iterator_traits<_InputIterator>::template __get_iterator_element<0>(__last));

__map.__containers_.values.insert(
__map.__containers_.values.end(),
__product_iterator_traits<_InputIterator>::template __get_iterator_element<1>(__first),
__product_iterator_traits<_InputIterator>::template __get_iterator_element<1>(__last));

return __map.__containers_.keys.size() - __s1;
}
};
_LIBCPP_END_NAMESPACE_STD

Expand Down
76 changes: 76 additions & 0 deletions libcxx/include/__iterator/product_iterator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

#ifndef _LIBCPP___ITERATOR_PRODUCT_ITERATOR_H
#define _LIBCPP___ITERATOR_PRODUCT_ITERATOR_H

// Product iterators are iterators that contain two or more underlying iterators.
//
// For example, std::flat_map stores its data into two separate containers, and its iterator
// is a proxy over two separate underlying iterators. The concept of product iterators
// allows algorithms to operate over these underlying iterators separately, opening the
// door to various optimizations.
//
// If __product_iterator_traits can be instantiated, the following functions and associated types must be provided:
// - static constexpr size_t Traits::__size
// The number of underlying iterators inside the product iterator.
//
// - template <size_t _N>
// static decltype(auto) Traits::__get_iterator_element(It&& __it)
// Returns the _Nth iterator element of the given product iterator.
//
// - template <class... _Iters>
// static _Iterator __make_product_iterator(_Iters&&...);
// Creates a product iterator from the given underlying iterators.

#include <__config>
#include <__cstddef/size_t.h>
#include <__type_traits/enable_if.h>
#include <__type_traits/integral_constant.h>
#include <__utility/declval.h>

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

_LIBCPP_BEGIN_NAMESPACE_STD

template <class _Iterator>
struct __product_iterator_traits;
/* exposition-only:
{
static constexpr size_t __size = ...;

template <size_t _N, class _Iter>
static decltype(auto) __get_iterator_element(_Iter&&);

template <class... _Iters>
static _Iterator __make_product_iterator(_Iters&&...);
};
*/

template <class _Tp, size_t = 0>
struct __is_product_iterator : false_type {};

template <class _Tp>
struct __is_product_iterator<_Tp, sizeof(__product_iterator_traits<_Tp>) * 0> : true_type {};

template <class _Tp, size_t _Size, class = void>
struct __is_product_iterator_of_size : false_type {};

template <class _Tp, size_t _Size>
struct __is_product_iterator_of_size<_Tp, _Size, __enable_if_t<__product_iterator_traits<_Tp>::__size == _Size> >
: true_type {};

template <class _Iterator, size_t _Nth>
using __product_iterator_element_t _LIBCPP_NODEBUG =
decltype(__product_iterator_traits<_Iterator>::template __get_iterator_element<_Nth>(std::declval<_Iterator>()));

_LIBCPP_END_NAMESPACE_STD

#endif // _LIBCPP___ITERATOR_PRODUCT_ITERATOR_H
22 changes: 22 additions & 0 deletions libcxx/include/__ranges/zip_view.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <__iterator/iter_move.h>
#include <__iterator/iter_swap.h>
#include <__iterator/iterator_traits.h>
#include <__iterator/product_iterator.h>
#include <__ranges/access.h>
#include <__ranges/all.h>
#include <__ranges/concepts.h>
Expand Down Expand Up @@ -251,6 +252,10 @@ class zip_view<_Views...>::__iterator : public __zip_view_iterator_category_base

friend class zip_view<_Views...>;

static constexpr bool __is_zip_view_iterator = true;

friend struct __product_iterator_traits<__iterator>;

public:
using iterator_concept = decltype(ranges::__get_zip_view_iterator_tag<_Const, _Views...>());
using value_type = tuple<range_value_t<__maybe_const<_Const, _Views>>...>;
Expand Down Expand Up @@ -468,6 +473,23 @@ inline constexpr auto zip = __zip::__fn{};
} // namespace views
} // namespace ranges

template <class _Iterator>
requires _Iterator::__is_zip_view_iterator
struct __product_iterator_traits<_Iterator> {
static constexpr size_t __size = tuple_size<decltype(std::declval<_Iterator>().__current_)>::value;

template <size_t _Nth, class _Iter>
requires(_Nth < __size)
_LIBCPP_HIDE_FROM_ABI static constexpr decltype(auto) __get_iterator_element(_Iter&& __it) {
return std::get<_Nth>(std::forward<_Iter>(__it).__current_);
}

template <class... _Iters>
_LIBCPP_HIDE_FROM_ABI static constexpr _Iterator __make_product_iterator(_Iters&&... __iters) {
return _Iterator(std::tuple(std::forward<_Iters>(__iters)...));
}
};

#endif // _LIBCPP_STD_VER >= 23

_LIBCPP_END_NAMESPACE_STD
Expand Down
1 change: 1 addition & 0 deletions libcxx/include/module.modulemap.in
Original file line number Diff line number Diff line change
Expand Up @@ -1519,6 +1519,7 @@ module std [system] {
}
module permutable { header "__iterator/permutable.h" }
module prev { header "__iterator/prev.h" }
module product_iterator { header "__iterator/product_iterator.h" }
module projected { header "__iterator/projected.h" }
module ranges_iterator_traits { header "__iterator/ranges_iterator_traits.h" }
module readable_traits { header "__iterator/readable_traits.h" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
#include <iterator>
#include <random>
#include <string>
#include <ranges>
#include <type_traits>
#include <utility>
#include <vector>

#include "benchmark/benchmark.h"
#include "../../GenerateInput.h"
#include "test_macros.h"

namespace support {

Expand Down Expand Up @@ -66,6 +68,8 @@ void associative_container_benchmarks(std::string container) {

static constexpr bool is_ordered_container = requires(Container c, Key k) { c.lower_bound(k); };

static constexpr bool is_map_like = requires { typename Container::mapped_type; };

// These benchmarks are structured to perform the operation being benchmarked
// a small number of times at each iteration, in order to offset the cost of
// PauseTiming() and ResumeTiming().
Expand Down Expand Up @@ -321,6 +325,48 @@ void associative_container_benchmarks(std::string container) {
}
});

if constexpr (is_map_like) {
bench("insert(iterator, iterator) (product_iterator from same type)", [=](auto& st) {
const std::size_t size = st.range(0);
std::vector<Value> in = make_value_types(generate_unique_keys(size + (size / 10)));
Container source(in.begin(), in.end());

Container c;

for ([[maybe_unused]] auto _ : st) {
c.insert(source.begin(), source.end());
benchmark::DoNotOptimize(c);
benchmark::ClobberMemory();

st.PauseTiming();
c = Container();
st.ResumeTiming();
}
});

#if TEST_STD_VER >= 23
bench("insert(iterator, iterator) (product_iterator from zip_view)", [=](auto& st) {
const std::size_t size = st.range(0);
std::vector<Key> keys = generate_unique_keys(size + (size / 10));
std::sort(keys.begin(), keys.end());
std::vector<typename Container::mapped_type> mapped(keys.size());

auto source = std::views::zip(keys, mapped);

Container c;

for ([[maybe_unused]] auto _ : st) {
c.insert(source.begin(), source.end());
benchmark::DoNotOptimize(c);
benchmark::ClobberMemory();

st.PauseTiming();
c = Container();
st.ResumeTiming();
}
});
#endif
}
/////////////////////////
// Erasure
/////////////////////////
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//
//===----------------------------------------------------------------------===//

// REQUIRES: std-at-least-c++26
// REQUIRES: std-at-least-c++23

#include <flat_map>
#include <utility>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//
//===----------------------------------------------------------------------===//

// REQUIRES: std-at-least-c++26
// REQUIRES: std-at-least-c++23

#include <flat_map>

Expand Down
66 changes: 66 additions & 0 deletions libcxx/test/libcxx/iterators/product_iterator.pass.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//===----------------------------------------------------------------------===//
//
// 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
// gcc 15 does not seem to recognize the __product_iterator_traits specializations
// UNSUPPORTED: gcc

#include <flat_map>
#include <ranges>
#include <type_traits>
#include <utility>
#include <vector>

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

constexpr bool test() {
{
// Test that the __get_iterator_element can handle a non-copyable iterator
int Date[] = {1, 2, 3, 4};
cpp20_input_iterator<int*> iter(Date);
sentinel_wrapper<cpp20_input_iterator<int*>> sent{cpp20_input_iterator<int*>(Date + 4)};
std::ranges::subrange r1(std::move(iter), std::move(sent));
auto v = std::views::zip(std::move(r1), std::views::iota(0, 4));
auto it = v.begin();

using Iter = decltype(it);

static_assert(!std::is_copy_constructible_v<Iter>);

static_assert(std::__product_iterator_traits<Iter>::__size == 2);
std::same_as<cpp20_input_iterator<int*>&> decltype(auto) it1 =
std::__product_iterator_traits<Iter>::__get_iterator_element<0>(it);

assert(*it1 == 1);
}
if (!std::is_constant_evaluated()) {
// Test __make_product_iterator
using M = std::flat_map<int, int>;
M m{{1, 1}, {2, 2}, {3, 3}};
using Iter = std::ranges::iterator_t<const M>;
const auto& keys = m.keys();
const auto& values = m.values();

auto it_keys = std::ranges::begin(keys);
auto it_values = std::ranges::begin(values);

auto it = std::__product_iterator_traits<Iter>::__make_product_iterator(it_keys, it_values);
assert(it->first == 1);
assert(it->second == 1);
}

return true;
}

int main(int, char**) {
test();
static_assert(test());

return 0;
}
Loading
Loading