Thanks to visit codestin.com
Credit goes to github.com

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;
}

Copy link
Member

Choose a reason for hiding this comment

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

The TODO above can now go away.

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
Comment on lines +9 to +10
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
#ifndef _LIBCPP___PRODUCT_ITERATOR_H
#define _LIBCPP___PRODUCT_ITERATOR_H
#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>
Copy link
Member

Choose a reason for hiding this comment

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

As discussed just now, would it be useful to have a function like __make_product_iterator(It1, It2, It3, ... ItN)? I can imagine this being useful in algorithms that may want to perform operations on a single iterator in the product, but which need to still return a product iterator as a result. This is similar to what we do with segmented iterators and also wrapped iterators.

// 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
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
#endif // _LIBCPP___PRODUCT_ITERATOR_H
#endif // _LIBCPP___ITERATOR_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;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
using __is_zip_view_iterator _LIBCPP_NODEBUG = true_type;
static constexpr bool __is_zip_view_iterator = true;

Slight simplification. This is what we do for join_view::iterator and segmented iterator traits.


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_);
}
Comment on lines +483 to +485
Copy link
Member

Choose a reason for hiding this comment

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

Do you intend to be making copies here? You're making a copy of the zip_view::iterator as an argument to the function, and you're making a copy of the sub-iterator in the return value.

This should get a test case.

};

#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) {
Copy link
Member

Choose a reason for hiding this comment

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

I would suggest looking at whether this can be moved to libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h and reused by other containers, flat or not. We might also want to drop the sorted_unique hint, since additional optimizations may be possible based on the knowledge that a sequence is sorted (and then this benchmark would show the difference without needed modifications).

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