brpc/include/butil/containers/pooled_map.h
2022-12-14 19:05:52 +08:00

197 lines
8.8 KiB
C++

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
// Date: Sat Dec 3 13:11:32 CST 2016
#ifndef BUTIL_POOLED_MAP_H
#define BUTIL_POOLED_MAP_H
#include "butil/single_threaded_pool.h"
#include <new>
#include <map>
namespace butil {
namespace details {
template <class T1, size_t BLOCK_SIZE> class PooledAllocator;
}
// A drop-in replacement for std::map to improve insert/erase performance slightly
//
// When do use PooledMap?
// A std::map with 10~100 elements. insert/erase performance will be slightly
// improved. Performance of find() is unaffected.
// When do NOT use PooledMap?
// When the std::map has less that 10 elements, PooledMap is probably slower
// because it allocates BLOCK_SIZE memory at least. When the std::map has more than
// 100 elements, you should use butil::FlatMap instead.
// insert/erase comparisons between several maps:
// [ value = 8 bytes ]
// Sequentially inserting 100 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 15/114/54/60
// Sequentially erasing 100 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 6/123/56/37
// Sequentially inserting 1000 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 9/92/56/54
// Sequentially erasing 1000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 3/68/51/35
// Sequentially inserting 10000 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 10/99/63/54
// Sequentially erasing 10000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 4/73/54/35
// [ value = 32 bytes ]
// Sequentially inserting 100 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 14/107/57/57
// Sequentially erasing 100 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 5/75/53/37
// Sequentially inserting 1000 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 13/94/55/53
// Sequentially erasing 1000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 4/67/50/37
// Sequentially inserting 10000 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 13/102/63/54
// Sequentially erasing 10000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 4/69/53/36
// [ value = 128 bytes ]
// Sequentially inserting 100 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 35/160/96/98
// Sequentially erasing 100 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 7/96/53/42
// Sequentially inserting 1000 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 30/159/98/98
// Sequentially erasing 1000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 6/82/49/43
// Sequentially inserting 10000 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 29/155/114/116
// Sequentially erasing 10000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 6/81/53/43
// [ value = 8 bytes ]
// Randomly inserting 100 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 13/168/103/59
// Randomly erasing 100 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 6/159/125/37
// Randomly inserting 1000 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 10/157/115/54
// Randomly erasing 1000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 4/175/138/36
// Randomly inserting 10000 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 11/219/177/56
// Randomly erasing 10000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 4/229/207/47
// [ value = 32 bytes ]
// Randomly inserting 100 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 17/178/112/57
// Randomly erasing 100 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 6/149/117/38
// Randomly inserting 1000 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 15/169/135/54
// Randomly erasing 1000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 5/157/129/39
// Randomly inserting 10000 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 19/242/203/55
// Randomly erasing 10000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 5/233/218/54
// [ value = 128 bytes ]
// Randomly inserting 100 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 36/214/145/96
// Randomly erasing 100 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 7/166/122/53
// Randomly inserting 1000 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 36/230/174/100
// Randomly erasing 1000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 6/193/153/65
// Randomly inserting 10000 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 45/304/270/115
// Randomly erasing 10000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 7/299/246/88
template <typename K, typename V, size_t BLOCK_SIZE = 512,
typename C = std::less<K> >
class PooledMap
: public std::map<K, V, C, details::PooledAllocator<std::pair<const K, V>, BLOCK_SIZE> > {
};
namespace details {
// Specialize for void
template <size_t BLOCK_SIZE>
class PooledAllocator<void, BLOCK_SIZE> {
public:
typedef void * pointer;
typedef const void* const_pointer;
typedef void value_type;
template <class U1> struct rebind {
typedef PooledAllocator<U1, BLOCK_SIZE> other;
};
};
template <class T1, size_t BLOCK_SIZE>
class PooledAllocator {
public:
typedef T1 value_type;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef T1* pointer;
typedef const T1* const_pointer;
typedef T1& reference;
typedef const T1& const_reference;
template <class U1> struct rebind {
typedef PooledAllocator<U1, BLOCK_SIZE> other;
};
public:
PooledAllocator() {}
PooledAllocator(const PooledAllocator&) {}
template <typename U1, size_t BS2>
PooledAllocator(const PooledAllocator<U1, BS2>&) {}
void operator=(const PooledAllocator&) {}
template <typename U1, size_t BS2>
void operator=(const PooledAllocator<U1, BS2>&) {}
void swap(PooledAllocator& other) { _pool.swap(other._pool); }
// Convert references to pointers.
pointer address(reference r) const { return &r; }
const_pointer address(const_reference r) const { return &r; }
// Allocate storage for n values of T1.
pointer allocate(size_type n, PooledAllocator<void, 0>::const_pointer = 0) {
if (n == 1) {
return (pointer)_pool.get();
} else {
return (pointer)malloc(n * sizeof(T1));
}
}
// Deallocate storage obtained by a call to allocate.
void deallocate(pointer p, size_type n) {
if (n == 1) {
return _pool.back(p);
} else {
free(p);
}
}
// Return the largest possible storage available through a call to allocate.
size_type max_size() const { return 0xFFFFFFFF / sizeof(T1); }
void construct(pointer ptr) { ::new (ptr) T1; }
void construct(pointer ptr, const T1& val) { ::new (ptr) T1(val); }
template <class U1> void construct(pointer ptr, const U1& val)
{ ::new (ptr) T1(val); }
void destroy(pointer p) { p->T1::~T1(); }
private:
butil::SingleThreadedPool<sizeof(T1), BLOCK_SIZE, 1> _pool;
};
// Return true if b could be used to deallocate storage obtained through a
// and vice versa. It's clear that our allocator can't be exchanged.
template <typename T1, size_t S1, typename T2, size_t S2>
bool operator==(const PooledAllocator<T1, S1>&, const PooledAllocator<T2, S2>&)
{ return false; }
template <typename T1, size_t S1, typename T2, size_t S2>
bool operator!=(const PooledAllocator<T1, S1>& a, const PooledAllocator<T2, S2>& b)
{ return !(a == b); }
} // namespace details
} // namespace butil
// Since this allocator can't be exchanged(check impl. of operator==) nor
// copied, specializing swap() is a must to make map.swap() work.
#if !defined(BUTIL_CXX11_ENABLED)
#include <algorithm> // std::swap until C++11
#else
#include <utility> // std::swap since C++11
#endif
namespace std {
template <class T1, size_t BLOCK_SIZE>
inline void swap(::butil::details::PooledAllocator<T1, BLOCK_SIZE> &lhs,
::butil::details::PooledAllocator<T1, BLOCK_SIZE> &rhs){
lhs.swap(rhs);
}
} // namespace std
#endif // BUTIL_POOLED_MAP_H