gcc/libstdc++-v3/include/bits/uses_allocator_args.h
Jonathan Wakely db5fa0837e libstdc++: Avoid unnecessary allocations in std::map insertions [PR92300]
Inserting a pair<Key, Value> into a map<Key, Value> will allocate a new
node and construct a pair<const Key, Value> in the node, then check if
the Key is already present in the map. That is because pair<Key, Value>
is not the same type as the map's value_type. But it only differs in the
const-qualification on the Key, and so we should be able to do the
lookup directly, without allocating a new node. This avoids allocating
and then deallocating a node for the case where the key is already found
and nothing gets inserted.

We can take this optimization further and lookup the key directly for a
pair<Key, X>, pair<const Key, X>, pair<Key&, X> etc. for any X. A strict
reading of the standard says we can only do this when we know the
allocator won't do anything funky with the value when constructing a
pair<const Key, Value> from a slightly different type. Inserting that
type only requires the value_type to be Cpp17EmplaceInsertable into the
container, and that doesn't have any requirement that the value is
unchanged (unlike Cpp17CopyInsertable and Cpp17MoveInsertable). For that
reason, the optimization is only done for maps using std::allocator.

A similar optimization can be done for map.emplace(key, value) where the
first argument is similar to the key_type and so can be looked up
without allocating a new node and constructing a key_type.

Finally, both of the insert and emplace cases can use the same
optimization when key_type is a scalar type and some other scalar is
being passed as the insert/emplace argument. Converting from one scalar
type to another won't have surprising value-altering behaviour, and has
no side effects (unlike e.g. constructing a std::string from a const
char* argument, which might allocate).

We don't need to do this for std::multimap, because we always insert the
new node even if the key is already present. So there's no benefit to
doing the lookup before allocating the new node.

libstdc++-v3/ChangeLog:

	PR libstdc++/92300
	* include/bits/stl_map.h (insert(Pair&&), emplace(Args&&...)):
	Check whether the arguments can be looked up directly without
	constructing a temporary node first.
	* include/bits/stl_pair.h (__is_pair): Move to here, from ...
	* include/bits/uses_allocator_args.h (__is_pair): ... here.
	* testsuite/23_containers/map/modifiers/emplace/92300.cc: New test.
	* testsuite/23_containers/map/modifiers/insert/92300.cc: New test.
2021-12-09 22:56:57 +00:00

222 lines
7.5 KiB
C++

// Utility functions for uses-allocator construction -*- C++ -*-
// Copyright (C) 2019-2021 Free Software Foundation, Inc.
//
// This file is part of the GNU ISO C++ Library. This library is free
// software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3, or (at your option)
// any later version.
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// Under Section 7 of GPL version 3, you are granted additional
// permissions described in the GCC Runtime Library Exception, version
// 3.1, as published by the Free Software Foundation.
// You should have received a copy of the GNU General Public License and
// a copy of the GCC Runtime Library Exception along with this program;
// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
// <http://www.gnu.org/licenses/>.
/*
* Copyright (c) 1997-1999
* Silicon Graphics Computer Systems, Inc.
*
* Permission to use, copy, modify, distribute and sell this software
* and its documentation for any purpose is hereby granted without fee,
* provided that the above copyright notice appear in all copies and
* that both that copyright notice and this permission notice appear
* in supporting documentation. Silicon Graphics makes no
* representations about the suitability of this software for any
* purpose. It is provided "as is" without express or implied warranty.
*
*/
/** @file include/bits/uses_allocator_args.h
* This is an internal header file, included by other library headers.
* Do not attempt to use it directly. @headername{memory}
*/
#ifndef _USES_ALLOCATOR_ARGS
#define _USES_ALLOCATOR_ARGS 1
#pragma GCC system_header
#if __cplusplus > 201703L && __cpp_concepts
#include <new> // for placement operator new
#include <tuple> // for tuple, make_tuple, make_from_tuple
#include <bits/stl_construct.h> // construct_at
#include <bits/stl_pair.h> // pair
namespace std _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION
template<typename _Tp>
concept _Std_pair = __is_pair<_Tp>;
/** @addtogroup allocators
* @{
*/
// Not specified by C++20, used internally
#define __cpp_lib_make_obj_using_allocator 201811L
template<typename _Tp, typename _Alloc, typename... _Args>
constexpr auto
uses_allocator_construction_args(const _Alloc& __a,
_Args&&... __args) noexcept
requires (! _Std_pair<_Tp>)
{
if constexpr (uses_allocator_v<remove_cv_t<_Tp>, _Alloc>)
{
if constexpr (is_constructible_v<_Tp, allocator_arg_t,
const _Alloc&, _Args...>)
{
return tuple<allocator_arg_t, const _Alloc&, _Args&&...>(
allocator_arg, __a, std::forward<_Args>(__args)...);
}
else
{
static_assert(is_constructible_v<_Tp, _Args..., const _Alloc&>,
"construction with an allocator must be possible"
" if uses_allocator is true");
return tuple<_Args&&..., const _Alloc&>(
std::forward<_Args>(__args)..., __a);
}
}
else
{
static_assert(is_constructible_v<_Tp, _Args...>);
return tuple<_Args&&...>(std::forward<_Args>(__args)...);
}
}
template<_Std_pair _Tp, typename _Alloc, typename _Tuple1, typename _Tuple2>
constexpr auto
uses_allocator_construction_args(const _Alloc& __a, piecewise_construct_t,
_Tuple1&& __x, _Tuple2&& __y) noexcept;
template<_Std_pair _Tp, typename _Alloc>
constexpr auto
uses_allocator_construction_args(const _Alloc&) noexcept;
template<_Std_pair _Tp, typename _Alloc, typename _Up, typename _Vp>
constexpr auto
uses_allocator_construction_args(const _Alloc&, _Up&&, _Vp&&) noexcept;
template<_Std_pair _Tp, typename _Alloc, typename _Up, typename _Vp>
constexpr auto
uses_allocator_construction_args(const _Alloc&,
const pair<_Up, _Vp>&) noexcept;
template<_Std_pair _Tp, typename _Alloc, typename _Up, typename _Vp>
constexpr auto
uses_allocator_construction_args(const _Alloc&, pair<_Up, _Vp>&&) noexcept;
template<_Std_pair _Tp, typename _Alloc, typename _Tuple1, typename _Tuple2>
constexpr auto
uses_allocator_construction_args(const _Alloc& __a, piecewise_construct_t,
_Tuple1&& __x, _Tuple2&& __y) noexcept
{
using _Tp1 = typename _Tp::first_type;
using _Tp2 = typename _Tp::second_type;
return std::make_tuple(piecewise_construct,
std::apply([&__a](auto&&... __args1) {
return std::uses_allocator_construction_args<_Tp1>(
__a, std::forward<decltype(__args1)>(__args1)...);
}, std::forward<_Tuple1>(__x)),
std::apply([&__a](auto&&... __args2) {
return std::uses_allocator_construction_args<_Tp2>(
__a, std::forward<decltype(__args2)>(__args2)...);
}, std::forward<_Tuple2>(__y)));
}
template<_Std_pair _Tp, typename _Alloc>
constexpr auto
uses_allocator_construction_args(const _Alloc& __a) noexcept
{
using _Tp1 = typename _Tp::first_type;
using _Tp2 = typename _Tp::second_type;
return std::make_tuple(piecewise_construct,
std::uses_allocator_construction_args<_Tp1>(__a),
std::uses_allocator_construction_args<_Tp2>(__a));
}
template<_Std_pair _Tp, typename _Alloc, typename _Up, typename _Vp>
constexpr auto
uses_allocator_construction_args(const _Alloc& __a, _Up&& __u, _Vp&& __v)
noexcept
{
using _Tp1 = typename _Tp::first_type;
using _Tp2 = typename _Tp::second_type;
return std::make_tuple(piecewise_construct,
std::uses_allocator_construction_args<_Tp1>(__a,
std::forward<_Up>(__u)),
std::uses_allocator_construction_args<_Tp2>(__a,
std::forward<_Vp>(__v)));
}
template<_Std_pair _Tp, typename _Alloc, typename _Up, typename _Vp>
constexpr auto
uses_allocator_construction_args(const _Alloc& __a,
const pair<_Up, _Vp>& __pr) noexcept
{
using _Tp1 = typename _Tp::first_type;
using _Tp2 = typename _Tp::second_type;
return std::make_tuple(piecewise_construct,
std::uses_allocator_construction_args<_Tp1>(__a, __pr.first),
std::uses_allocator_construction_args<_Tp2>(__a, __pr.second));
}
template<_Std_pair _Tp, typename _Alloc, typename _Up, typename _Vp>
constexpr auto
uses_allocator_construction_args(const _Alloc& __a,
pair<_Up, _Vp>&& __pr) noexcept
{
using _Tp1 = typename _Tp::first_type;
using _Tp2 = typename _Tp::second_type;
return std::make_tuple(piecewise_construct,
std::uses_allocator_construction_args<_Tp1>(__a,
std::move(__pr).first),
std::uses_allocator_construction_args<_Tp2>(__a,
std::move(__pr).second));
}
template<typename _Tp, typename _Alloc, typename... _Args>
inline _Tp
make_obj_using_allocator(const _Alloc& __a, _Args&&... __args)
{
return std::make_from_tuple<_Tp>(
std::uses_allocator_construction_args<_Tp>(__a,
std::forward<_Args>(__args)...));
}
template<typename _Tp, typename _Alloc, typename... _Args>
inline _Tp*
uninitialized_construct_using_allocator(_Tp* __p, const _Alloc& __a,
_Args&&... __args)
{
return std::apply([&](auto&&... __xs) {
return std::construct_at(__p, std::forward<decltype(__xs)>(__xs)...);
}, std::uses_allocator_construction_args<_Tp>(__a,
std::forward<_Args>(__args)...));
}
/// @}
_GLIBCXX_END_NAMESPACE_VERSION
} // namespace std
#endif // C++20
#endif // _USES_ALLOCATOR_ARGS