Skip to content

Commit 34b2e93

Browse files
huixie90ldionne
andauthored
[libc++] Introduce __product_iterator_traits and optimise flat_map::insert (#139454)
Fixes #108624 This allows `flat_map::insert(Iter, Iter)` to directly forward to underlying containers' `insert(Iter, Iter)`, instead of inserting one element at a time, when input models "product iterator". atm, `flat_map::iterator` and `zip_view::iterator` are "product iterator"s. This gives about almost 10x speed up in my benchmark with -03 (for both before and after) ```cpp Benchmark Time CPU Time Old Time New CPU Old CPU New ----------------------------------------------------------------------------------------------------------------------------------------------- flat_map::insert_product_iterator_flat_map/32 -0.5028 -0.5320 149 74 149 70 flat_map::insert_product_iterator_flat_map/1024 -0.8617 -0.8618 3113 430 3112 430 flat_map::insert_product_iterator_flat_map/8192 -0.8877 -0.8877 26682 2995 26679 2995 flat_map::insert_product_iterator_flat_map/65536 -0.8769 -0.8769 226235 27844 226221 27841 flat_map::insert_product_iterator_zip/32 -0.5844 -0.5844 162 67 162 67 flat_map::insert_product_iterator_zip/1024 -0.8754 -0.8754 3427 427 3427 427 flat_map::insert_product_iterator_zip/8192 -0.8934 -0.8934 28134 3000 28132 3000 flat_map::insert_product_iterator_zip/65536 -0.8783 -0.8783 229783 27960 229767 27958 OVERALL_GEOMEAN -0.8319 -0.8332 0 0 0 0 ``` --------- Co-authored-by: Louis Dionne <[email protected]>
1 parent feb61f5 commit 34b2e93

File tree

12 files changed

+287
-5
lines changed

12 files changed

+287
-5
lines changed

libcxx/docs/ReleaseNotes/21.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ Improvements and New Features
8181
- The ``bitset::to_string`` function has been optimized, resulting in a performance improvement of up to 8.3x for bitsets
8282
with uniformly distributed zeros and ones, and up to 13.5x and 16.1x for sparse and dense bitsets, respectively.
8383

84+
- The ``flat_map::insert`` and ``flat_set::insert_range`` have been optimized, resulting in a performance improvement of up
85+
to 10x for inserting elements into a ``flat_map`` when the input range is a ``flat_map`` or a ``zip_view``.
86+
8487
Deprecations and Removals
8588
-------------------------
8689

libcxx/include/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,7 @@ set(files
498498
__iterator/ostreambuf_iterator.h
499499
__iterator/permutable.h
500500
__iterator/prev.h
501+
__iterator/product_iterator.h
501502
__iterator/projected.h
502503
__iterator/ranges_iterator_traits.h
503504
__iterator/readable_traits.h

libcxx/include/__flat_map/key_value_iterator.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@
1313
#include <__compare/three_way_comparable.h>
1414
#include <__concepts/convertible_to.h>
1515
#include <__config>
16+
#include <__cstddef/size_t.h>
1617
#include <__iterator/iterator_traits.h>
18+
#include <__iterator/product_iterator.h>
1719
#include <__memory/addressof.h>
1820
#include <__type_traits/conditional.h>
21+
#include <__utility/forward.h>
1922
#include <__utility/move.h>
2023
#include <__utility/pair.h>
2124

@@ -57,6 +60,8 @@ struct __key_value_iterator {
5760
template <class, class, class, bool>
5861
friend struct __key_value_iterator;
5962

63+
friend struct __product_iterator_traits<__key_value_iterator>;
64+
6065
public:
6166
using iterator_concept = random_access_iterator_tag;
6267
// `__key_value_iterator` only satisfy "Cpp17InputIterator" named requirements, because
@@ -181,6 +186,29 @@ struct __key_value_iterator {
181186
}
182187
};
183188

189+
template <class _Owner, class _KeyContainer, class _MappedContainer, bool _Const>
190+
struct __product_iterator_traits<__key_value_iterator<_Owner, _KeyContainer, _MappedContainer, _Const>> {
191+
static constexpr size_t __size = 2;
192+
193+
template <size_t _Nth, class _Iter>
194+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 static decltype(auto) __get_iterator_element(_Iter&& __it)
195+
requires(_Nth <= 1)
196+
{
197+
if constexpr (_Nth == 0) {
198+
return std::forward<_Iter>(__it).__key_iter_;
199+
} else {
200+
return std::forward<_Iter>(__it).__mapped_iter_;
201+
}
202+
}
203+
204+
template <class _KeyIter, class _MappedIter>
205+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 static auto
206+
__make_product_iterator(_KeyIter&& __key_iter, _MappedIter&& __mapped_iter) {
207+
return __key_value_iterator<_Owner, _KeyContainer, _MappedContainer, _Const>(
208+
std::forward<_KeyIter>(__key_iter), std::forward<_MappedIter>(__mapped_iter));
209+
}
210+
};
211+
184212
_LIBCPP_END_NAMESPACE_STD
185213

186214
#endif // _LIBCPP_STD_VER >= 23

libcxx/include/__flat_map/utils.h

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#define _LIBCPP___FLAT_MAP_UTILS_H
1212

1313
#include <__config>
14+
#include <__iterator/product_iterator.h>
1415
#include <__type_traits/container_traits.h>
1516
#include <__utility/exception_guard.h>
1617
#include <__utility/forward.h>
@@ -79,8 +80,6 @@ struct __flat_map_utils {
7980
return typename _Map::iterator(std::move(__key_it), std::move(__mapped_it));
8081
}
8182

82-
// TODO: We could optimize this, see
83-
// https://github.com/llvm/llvm-project/issues/108624
8483
template <class _Map, class _InputIterator, class _Sentinel>
8584
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 static typename _Map::size_type
8685
__append(_Map& __map, _InputIterator __first, _Sentinel __last) {
@@ -93,6 +92,25 @@ struct __flat_map_utils {
9392
}
9493
return __num_appended;
9594
}
95+
96+
template <class _Map, class _InputIterator>
97+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 static typename _Map::size_type
98+
__append(_Map& __map, _InputIterator __first, _InputIterator __last)
99+
requires __is_product_iterator_of_size<_InputIterator, 2>::value
100+
{
101+
auto __s1 = __map.__containers_.keys.size();
102+
__map.__containers_.keys.insert(
103+
__map.__containers_.keys.end(),
104+
__product_iterator_traits<_InputIterator>::template __get_iterator_element<0>(__first),
105+
__product_iterator_traits<_InputIterator>::template __get_iterator_element<0>(__last));
106+
107+
__map.__containers_.values.insert(
108+
__map.__containers_.values.end(),
109+
__product_iterator_traits<_InputIterator>::template __get_iterator_element<1>(__first),
110+
__product_iterator_traits<_InputIterator>::template __get_iterator_element<1>(__last));
111+
112+
return __map.__containers_.keys.size() - __s1;
113+
}
96114
};
97115
_LIBCPP_END_NAMESPACE_STD
98116

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef _LIBCPP___ITERATOR_PRODUCT_ITERATOR_H
10+
#define _LIBCPP___ITERATOR_PRODUCT_ITERATOR_H
11+
12+
// Product iterators are iterators that contain two or more underlying iterators.
13+
//
14+
// For example, std::flat_map stores its data into two separate containers, and its iterator
15+
// is a proxy over two separate underlying iterators. The concept of product iterators
16+
// allows algorithms to operate over these underlying iterators separately, opening the
17+
// door to various optimizations.
18+
//
19+
// If __product_iterator_traits can be instantiated, the following functions and associated types must be provided:
20+
// - static constexpr size_t Traits::__size
21+
// The number of underlying iterators inside the product iterator.
22+
//
23+
// - template <size_t _N>
24+
// static decltype(auto) Traits::__get_iterator_element(It&& __it)
25+
// Returns the _Nth iterator element of the given product iterator.
26+
//
27+
// - template <class... _Iters>
28+
// static _Iterator __make_product_iterator(_Iters&&...);
29+
// Creates a product iterator from the given underlying iterators.
30+
31+
#include <__config>
32+
#include <__cstddef/size_t.h>
33+
#include <__type_traits/enable_if.h>
34+
#include <__type_traits/integral_constant.h>
35+
#include <__utility/declval.h>
36+
37+
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
38+
# pragma GCC system_header
39+
#endif
40+
41+
_LIBCPP_BEGIN_NAMESPACE_STD
42+
43+
template <class _Iterator>
44+
struct __product_iterator_traits;
45+
/* exposition-only:
46+
{
47+
static constexpr size_t __size = ...;
48+
49+
template <size_t _N, class _Iter>
50+
static decltype(auto) __get_iterator_element(_Iter&&);
51+
52+
template <class... _Iters>
53+
static _Iterator __make_product_iterator(_Iters&&...);
54+
};
55+
*/
56+
57+
template <class _Tp, size_t = 0>
58+
struct __is_product_iterator : false_type {};
59+
60+
template <class _Tp>
61+
struct __is_product_iterator<_Tp, sizeof(__product_iterator_traits<_Tp>) * 0> : true_type {};
62+
63+
template <class _Tp, size_t _Size, class = void>
64+
struct __is_product_iterator_of_size : false_type {};
65+
66+
template <class _Tp, size_t _Size>
67+
struct __is_product_iterator_of_size<_Tp, _Size, __enable_if_t<__product_iterator_traits<_Tp>::__size == _Size> >
68+
: true_type {};
69+
70+
template <class _Iterator, size_t _Nth>
71+
using __product_iterator_element_t _LIBCPP_NODEBUG =
72+
decltype(__product_iterator_traits<_Iterator>::template __get_iterator_element<_Nth>(std::declval<_Iterator>()));
73+
74+
_LIBCPP_END_NAMESPACE_STD
75+
76+
#endif // _LIBCPP___ITERATOR_PRODUCT_ITERATOR_H

libcxx/include/__ranges/zip_view.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <__iterator/iter_move.h>
2424
#include <__iterator/iter_swap.h>
2525
#include <__iterator/iterator_traits.h>
26+
#include <__iterator/product_iterator.h>
2627
#include <__ranges/access.h>
2728
#include <__ranges/all.h>
2829
#include <__ranges/concepts.h>
@@ -251,6 +252,10 @@ class zip_view<_Views...>::__iterator : public __zip_view_iterator_category_base
251252

252253
friend class zip_view<_Views...>;
253254

255+
static constexpr bool __is_zip_view_iterator = true;
256+
257+
friend struct __product_iterator_traits<__iterator>;
258+
254259
public:
255260
using iterator_concept = decltype(ranges::__get_zip_view_iterator_tag<_Const, _Views...>());
256261
using value_type = tuple<range_value_t<__maybe_const<_Const, _Views>>...>;
@@ -468,6 +473,23 @@ inline constexpr auto zip = __zip::__fn{};
468473
} // namespace views
469474
} // namespace ranges
470475

476+
template <class _Iterator>
477+
requires _Iterator::__is_zip_view_iterator
478+
struct __product_iterator_traits<_Iterator> {
479+
static constexpr size_t __size = tuple_size<decltype(std::declval<_Iterator>().__current_)>::value;
480+
481+
template <size_t _Nth, class _Iter>
482+
requires(_Nth < __size)
483+
_LIBCPP_HIDE_FROM_ABI static constexpr decltype(auto) __get_iterator_element(_Iter&& __it) {
484+
return std::get<_Nth>(std::forward<_Iter>(__it).__current_);
485+
}
486+
487+
template <class... _Iters>
488+
_LIBCPP_HIDE_FROM_ABI static constexpr _Iterator __make_product_iterator(_Iters&&... __iters) {
489+
return _Iterator(std::tuple(std::forward<_Iters>(__iters)...));
490+
}
491+
};
492+
471493
#endif // _LIBCPP_STD_VER >= 23
472494

473495
_LIBCPP_END_NAMESPACE_STD

libcxx/include/module.modulemap.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1522,6 +1522,7 @@ module std [system] {
15221522
}
15231523
module permutable { header "__iterator/permutable.h" }
15241524
module prev { header "__iterator/prev.h" }
1525+
module product_iterator { header "__iterator/product_iterator.h" }
15251526
module projected { header "__iterator/projected.h" }
15261527
module ranges_iterator_traits { header "__iterator/ranges_iterator_traits.h" }
15271528
module readable_traits { header "__iterator/readable_traits.h" }

libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@
1313
#include <iterator>
1414
#include <random>
1515
#include <string>
16+
#include <ranges>
1617
#include <type_traits>
1718
#include <utility>
1819
#include <vector>
1920

2021
#include "benchmark/benchmark.h"
2122
#include "../../GenerateInput.h"
23+
#include "test_macros.h"
2224

2325
namespace support {
2426

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

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

71+
static constexpr bool is_map_like = requires { typename Container::mapped_type; };
72+
6973
// These benchmarks are structured to perform the operation being benchmarked
7074
// a small number of times at each iteration, in order to offset the cost of
7175
// PauseTiming() and ResumeTiming().
@@ -321,6 +325,48 @@ void associative_container_benchmarks(std::string container) {
321325
}
322326
});
323327

328+
if constexpr (is_map_like) {
329+
bench("insert(iterator, iterator) (product_iterator from same type)", [=](auto& st) {
330+
const std::size_t size = st.range(0);
331+
std::vector<Value> in = make_value_types(generate_unique_keys(size + (size / 10)));
332+
Container source(in.begin(), in.end());
333+
334+
Container c;
335+
336+
for ([[maybe_unused]] auto _ : st) {
337+
c.insert(source.begin(), source.end());
338+
benchmark::DoNotOptimize(c);
339+
benchmark::ClobberMemory();
340+
341+
st.PauseTiming();
342+
c = Container();
343+
st.ResumeTiming();
344+
}
345+
});
346+
347+
#if TEST_STD_VER >= 23
348+
bench("insert(iterator, iterator) (product_iterator from zip_view)", [=](auto& st) {
349+
const std::size_t size = st.range(0);
350+
std::vector<Key> keys = generate_unique_keys(size + (size / 10));
351+
std::sort(keys.begin(), keys.end());
352+
std::vector<typename Container::mapped_type> mapped(keys.size());
353+
354+
auto source = std::views::zip(keys, mapped);
355+
356+
Container c;
357+
358+
for ([[maybe_unused]] auto _ : st) {
359+
c.insert(source.begin(), source.end());
360+
benchmark::DoNotOptimize(c);
361+
benchmark::ClobberMemory();
362+
363+
st.PauseTiming();
364+
c = Container();
365+
st.ResumeTiming();
366+
}
367+
});
368+
#endif
369+
}
324370
/////////////////////////
325371
// Erasure
326372
/////////////////////////

libcxx/test/benchmarks/containers/associative/flat_map.bench.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
//
77
//===----------------------------------------------------------------------===//
88

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

1111
#include <flat_map>
1212
#include <utility>

libcxx/test/benchmarks/containers/associative/flat_multimap.bench.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
//
77
//===----------------------------------------------------------------------===//
88

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

1111
#include <flat_map>
1212

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
10+
// gcc 15 does not seem to recognize the __product_iterator_traits specializations
11+
// UNSUPPORTED: gcc
12+
13+
#include <flat_map>
14+
#include <ranges>
15+
#include <type_traits>
16+
#include <utility>
17+
#include <vector>
18+
19+
#include "test_macros.h"
20+
#include "test_iterators.h"
21+
22+
constexpr bool test() {
23+
{
24+
// Test that the __get_iterator_element can handle a non-copyable iterator
25+
int Date[] = {1, 2, 3, 4};
26+
cpp20_input_iterator<int*> iter(Date);
27+
sentinel_wrapper<cpp20_input_iterator<int*>> sent{cpp20_input_iterator<int*>(Date + 4)};
28+
std::ranges::subrange r1(std::move(iter), std::move(sent));
29+
auto v = std::views::zip(std::move(r1), std::views::iota(0, 4));
30+
auto it = v.begin();
31+
32+
using Iter = decltype(it);
33+
34+
static_assert(!std::is_copy_constructible_v<Iter>);
35+
36+
static_assert(std::__product_iterator_traits<Iter>::__size == 2);
37+
std::same_as<cpp20_input_iterator<int*>&> decltype(auto) it1 =
38+
std::__product_iterator_traits<Iter>::__get_iterator_element<0>(it);
39+
40+
assert(*it1 == 1);
41+
}
42+
if (!std::is_constant_evaluated()) {
43+
// Test __make_product_iterator
44+
using M = std::flat_map<int, int>;
45+
M m{{1, 1}, {2, 2}, {3, 3}};
46+
using Iter = std::ranges::iterator_t<const M>;
47+
const auto& keys = m.keys();
48+
const auto& values = m.values();
49+
50+
auto it_keys = std::ranges::begin(keys);
51+
auto it_values = std::ranges::begin(values);
52+
53+
auto it = std::__product_iterator_traits<Iter>::__make_product_iterator(it_keys, it_values);
54+
assert(it->first == 1);
55+
assert(it->second == 1);
56+
}
57+
58+
return true;
59+
}
60+
61+
int main(int, char**) {
62+
test();
63+
static_assert(test());
64+
65+
return 0;
66+
}

0 commit comments

Comments
 (0)