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 4 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
1 change: 1 addition & 0 deletions libcxx/include/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,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
21 changes: 21 additions & 0 deletions libcxx/include/__flat_map/key_value_iterator.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
#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/move.h>
Expand Down Expand Up @@ -57,6 +59,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 +171,23 @@ 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>
_LIBCPP_HIDE_FROM_ABI static auto
__get_iterator_element(__key_value_iterator<_Owner, _KeyContainer, _MappedContainer, _Const> __it)
requires(_Nth <= 1)
{
if constexpr (_Nth == 0) {
return __it.__key_iter_;
} else {
return __it.__mapped_iter_;
}
}
};

_LIBCPP_END_NAMESPACE_STD

#endif // _LIBCPP_STD_VER >= 23
Expand Down
20 changes: 20 additions & 0 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 @@ -93,6 +94,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
69 changes: 69 additions & 0 deletions libcxx/include/__iterator/product_iterator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//===----------------------------------------------------------------------===//
//
// 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___PRODUCT_ITERATOR_H
#define _LIBCPP___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 auto Traits::__get_iterator_element(It __it)
// Returns the _Nth iterator element of the given product iterator.

#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>
static auto __get_iterator_element(_Iterator);
};
*/

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>::__get_iterator_element<_Nth>(std::declval<_Iterator>()));

_LIBCPP_END_NAMESPACE_STD

#endif // _LIBCPP___PRODUCT_ITERATOR_H
17 changes: 17 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...>;

using __is_zip_view_iterator _LIBCPP_NODEBUG = true_type;

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,18 @@ inline constexpr auto zip = __zip::__fn{};
} // namespace views
} // namespace ranges

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

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

#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 @@ -1525,6 +1525,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 @@ -10,6 +10,7 @@

#include <flat_map>
#include <utility>
#include <ranges>

#include "associative_container_benchmarks.h"
#include "../../GenerateInput.h"
Expand All @@ -26,9 +27,54 @@ struct support::adapt_operations<std::flat_map<K, V>> {
static auto get_iterator(InsertionResult const& result) { return result.first; }
};

void product_iterator_benchmark_flat_map(benchmark::State& state) {
const std::size_t size = state.range(0);

using M = std::flat_map<int, int>;

const M source =
std::views::iota(0, static_cast<int>(size)) | std::views::transform([](int i) { return std::pair(i, i); }) |
std::ranges::to<std::flat_map<int, int>>();

for (auto _ : state) {
M m;
m.insert(std::sorted_unique, source.begin(), source.end());
benchmark::DoNotOptimize(m);
benchmark::ClobberMemory();
}
}

void product_iterator_benchmark_zip_view(benchmark::State& state) {
const std::size_t size = state.range(0);

using M = std::flat_map<int, int>;

const std::vector<int> keys = std::views::iota(0, static_cast<int>(size)) | std::ranges::to<std::vector<int>>();
const std::vector<int> values = keys;

auto source = std::views::zip(keys, values);
for (auto _ : state) {
M m;
m.insert(std::sorted_unique, source.begin(), source.end());
benchmark::DoNotOptimize(m);
benchmark::ClobberMemory();
}
}

int main(int argc, char** argv) {
support::associative_container_benchmarks<std::flat_map<int, int>>("std::flat_map<int, int>");

benchmark::RegisterBenchmark("flat_map::insert_product_iterator_flat_map", product_iterator_benchmark_flat_map)
->Arg(32)
->Arg(1024)
->Arg(8192)
->Arg(65536);
benchmark::RegisterBenchmark("flat_map::insert_product_iterator_zip", product_iterator_benchmark_zip_view)
->Arg(32)
->Arg(1024)
->Arg(8192)
->Arg(65536);

benchmark::Initialize(&argc, argv);
benchmark::RunSpecifiedBenchmarks();
benchmark::Shutdown();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <cassert>
#include <functional>
#include <deque>
#include <ranges>

#include "MinSequenceContainer.h"
#include "../helpers.h"
Expand Down Expand Up @@ -75,12 +76,33 @@ void test() {
M expected2{{0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}};
assert(m == expected2);
}

void test_product_iterator() {
using M = std::flat_map<int, int>;
{
M m1{{1, 1}, {2, 1}, {3, 1}};
M m2{{4, 1}, {5, 1}, {6, 1}};
m1.insert(m2.begin(), m2.end());
M expected{{1, 1}, {2, 1}, {3, 1}, {4, 1}, {5, 1}, {6, 1}};
assert(m1 == expected);
}
{
std::vector<int> keys{1, 2, 3};
std::vector<int> values{1, 1, 1};
auto zv = std::views::zip(keys, values);
M m;
m.insert(zv.begin(), zv.end());
M expected{{1, 1}, {2, 1}, {3, 1}};
assert(m == expected);
}
}

int main(int, char**) {
test<std::vector<int>, std::vector<double>>();
test<std::deque<int>, std::vector<double>>();
test<MinSequenceContainer<int>, MinSequenceContainer<double>>();
test<std::vector<int, min_allocator<int>>, std::vector<double, min_allocator<double>>>();

test_product_iterator();
{
auto insert_func = [](auto& m, const auto& newValues) { m.insert(newValues.begin(), newValues.end()); };
test_insert_range_exception_guarantee(insert_func);
Expand Down
Loading