[libcxx-commits] [libcxx] [libc++] Introduce `__product_iterator_traits` and optimise `flat_map::insert` (PR #139454)
via libcxx-commits
libcxx-commits at lists.llvm.org
Sun May 11 11:44:08 PDT 2025
https://github.com/huixie90 updated https://github.com/llvm/llvm-project/pull/139454
>From fdbe85fc3b4bcd8b526a17ca9baedae9428ac51f Mon Sep 17 00:00:00 2001
From: Hui Xie <hui.xie1990 at gmail.com>
Date: Sun, 11 May 2025 16:39:34 +0100
Subject: [PATCH 1/4] [libc++] Introduce and optimise
---
libcxx/include/CMakeLists.txt | 1 +
.../include/__flat_map/key_value_iterator.h | 20 ++++++
libcxx/include/__flat_map/utils.h | 20 ++++++
libcxx/include/__iterator/product_iterator.h | 68 +++++++++++++++++++
libcxx/include/__ranges/zip_view.h | 17 +++++
.../containers/associative/flat_map.bench.cpp | 46 +++++++++++++
.../insert_iter_iter.pass.cpp | 24 ++++++-
7 files changed, 195 insertions(+), 1 deletion(-)
create mode 100644 libcxx/include/__iterator/product_iterator.h
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 8cb05857ea8d7..c03dd10ec1159 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -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
diff --git a/libcxx/include/__flat_map/key_value_iterator.h b/libcxx/include/__flat_map/key_value_iterator.h
index 3ebb653deb197..968d354de8350 100644
--- a/libcxx/include/__flat_map/key_value_iterator.h
+++ b/libcxx/include/__flat_map/key_value_iterator.h
@@ -14,6 +14,7 @@
#include <__concepts/convertible_to.h>
#include <__config>
#include <__iterator/iterator_traits.h>
+#include <__iterator/product_iterator.h>
#include <__memory/addressof.h>
#include <__type_traits/conditional.h>
#include <__utility/move.h>
@@ -57,6 +58,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
@@ -167,6 +170,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 _N>
+ _LIBCPP_HIDE_FROM_ABI static auto
+ __get_iterator_element(__key_value_iterator<_Owner, _KeyContainer, _MappedContainer, _Const> __it)
+ requires(_N == 0 || _N == 1)
+ {
+ if constexpr (_N == 0) {
+ return __it.__key_iter_;
+ } else {
+ return __it.__mapped_iter_;
+ }
+ }
+};
+
_LIBCPP_END_NAMESPACE_STD
#endif // _LIBCPP_STD_VER >= 23
diff --git a/libcxx/include/__flat_map/utils.h b/libcxx/include/__flat_map/utils.h
index acb7dca7ffe96..2c0e6ada31a5e 100644
--- a/libcxx/include/__flat_map/utils.h
+++ b/libcxx/include/__flat_map/utils.h
@@ -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>
@@ -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
diff --git a/libcxx/include/__iterator/product_iterator.h b/libcxx/include/__iterator/product_iterator.h
new file mode 100644
index 0000000000000..70b7acce456fb
--- /dev/null
+++ b/libcxx/include/__iterator/product_iterator.h
@@ -0,0 +1,68 @@
+//===----------------------------------------------------------------------===//
+//
+// 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>
+
+#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 _N>
+using __product_iterator_element_t =
+ decltype(__product_iterator_traits<_Iterator>::__get_iterator_element<_N>(declval<_Iterator>()));
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___PRODUCT_ITERATOR_H
diff --git a/libcxx/include/__ranges/zip_view.h b/libcxx/include/__ranges/zip_view.h
index 6d31f5ce0993c..b074e69832535 100644
--- a/libcxx/include/__ranges/zip_view.h
+++ b/libcxx/include/__ranges/zip_view.h
@@ -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>
@@ -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>>...>;
@@ -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 _N>
+ requires(_N < __size)
+ _LIBCPP_HIDE_FROM_ABI static constexpr auto __get_iterator_element(_Iter __it) {
+ return std::get<_N>(__it.__current_);
+ }
+};
+
#endif // _LIBCPP_STD_VER >= 23
_LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/test/benchmarks/containers/associative/flat_map.bench.cpp b/libcxx/test/benchmarks/containers/associative/flat_map.bench.cpp
index 82902d50f31e6..7351e79758fa8 100644
--- a/libcxx/test/benchmarks/containers/associative/flat_map.bench.cpp
+++ b/libcxx/test/benchmarks/containers/associative/flat_map.bench.cpp
@@ -10,6 +10,7 @@
#include <flat_map>
#include <utility>
+#include <ranges>
#include "associative_container_benchmarks.h"
#include "../../GenerateInput.h"
@@ -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();
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_iter_iter.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_iter_iter.pass.cpp
index 8455b19475fe4..8bbb7caa82545 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_iter_iter.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_iter_iter.pass.cpp
@@ -17,6 +17,7 @@
#include <cassert>
#include <functional>
#include <deque>
+#include <ranges>
#include "MinSequenceContainer.h"
#include "../helpers.h"
@@ -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);
>From eda23d3e8945ad1d1814811ccb0f87f1e48047a4 Mon Sep 17 00:00:00 2001
From: Hui Xie <hui.xie1990 at gmail.com>
Date: Sun, 11 May 2025 19:08:03 +0100
Subject: [PATCH 2/4] CI
---
libcxx/include/__flat_map/key_value_iterator.h | 6 +++---
libcxx/include/__iterator/product_iterator.h | 9 +++++----
libcxx/include/__ranges/zip_view.h | 6 +++---
3 files changed, 11 insertions(+), 10 deletions(-)
diff --git a/libcxx/include/__flat_map/key_value_iterator.h b/libcxx/include/__flat_map/key_value_iterator.h
index 968d354de8350..130e6413bdecc 100644
--- a/libcxx/include/__flat_map/key_value_iterator.h
+++ b/libcxx/include/__flat_map/key_value_iterator.h
@@ -174,12 +174,12 @@ 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 _N>
+ template <size_t _Nth>
_LIBCPP_HIDE_FROM_ABI static auto
__get_iterator_element(__key_value_iterator<_Owner, _KeyContainer, _MappedContainer, _Const> __it)
- requires(_N == 0 || _N == 1)
+ requires(_Nth <= 1)
{
- if constexpr (_N == 0) {
+ if constexpr (_Nth == 0) {
return __it.__key_iter_;
} else {
return __it.__mapped_iter_;
diff --git a/libcxx/include/__iterator/product_iterator.h b/libcxx/include/__iterator/product_iterator.h
index 70b7acce456fb..2522f292fe6d7 100644
--- a/libcxx/include/__iterator/product_iterator.h
+++ b/libcxx/include/__iterator/product_iterator.h
@@ -28,6 +28,7 @@
#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
@@ -56,12 +57,12 @@ 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>>
+struct __is_product_iterator_of_size<_Tp, _Size, __enable_if_t<__product_iterator_traits<_Tp>::__size == _Size> >
: true_type {};
-template <class _Iterator, size_t _N>
-using __product_iterator_element_t =
- decltype(__product_iterator_traits<_Iterator>::__get_iterator_element<_N>(declval<_Iterator>()));
+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
diff --git a/libcxx/include/__ranges/zip_view.h b/libcxx/include/__ranges/zip_view.h
index b074e69832535..0f25f3ad8be56 100644
--- a/libcxx/include/__ranges/zip_view.h
+++ b/libcxx/include/__ranges/zip_view.h
@@ -478,10 +478,10 @@ template <class _Iter>
struct __product_iterator_traits<_Iter> {
static constexpr size_t __size = tuple_size<decltype(std::declval<_Iter>().__current_)>::value;
- template <size_t _N>
- requires(_N < __size)
+ template <size_t _Nth>
+ requires(_Nth < __size)
_LIBCPP_HIDE_FROM_ABI static constexpr auto __get_iterator_element(_Iter __it) {
- return std::get<_N>(__it.__current_);
+ return std::get<_Nth>(__it.__current_);
}
};
>From 883ba5eb21d6c3ca6975e0fbfe5326ffd3350349 Mon Sep 17 00:00:00 2001
From: Hui Xie <hui.xie1990 at gmail.com>
Date: Sun, 11 May 2025 19:20:07 +0100
Subject: [PATCH 3/4] CI
---
libcxx/include/module.modulemap.in | 1 +
1 file changed, 1 insertion(+)
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index b9cb43ebd999a..434d23f4d7ab7 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -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" }
>From 6f3b3cc51d2a022efba7f12923905d03bd069d89 Mon Sep 17 00:00:00 2001
From: Hui Xie <hui.xie1990 at gmail.com>
Date: Sun, 11 May 2025 19:43:57 +0100
Subject: [PATCH 4/4] CI
---
libcxx/include/__flat_map/key_value_iterator.h | 1 +
1 file changed, 1 insertion(+)
diff --git a/libcxx/include/__flat_map/key_value_iterator.h b/libcxx/include/__flat_map/key_value_iterator.h
index 130e6413bdecc..085b52c277203 100644
--- a/libcxx/include/__flat_map/key_value_iterator.h
+++ b/libcxx/include/__flat_map/key_value_iterator.h
@@ -13,6 +13,7 @@
#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>
More information about the libcxx-commits
mailing list