不重新分配内存的std::set的替代方案
Alternative for std::set without memory reallocation?
在一个应用程序中,我详尽地生成了许多子问题,并使用"std::set"操作来解决它们。为此,我需要"插入"answers"查找"元素,也"迭代"在排序列表。
问题在于,当我每次在集合中插入一个元素时,"std::set"实现都会为每一个数百万个子问题分配新的内存,这使得整个应用程序非常慢:
{ // allocate a non-value node
_Nodeptr _Pnode = this->_Getal().allocate(1); // <- bottleneck of the program
是否有一些静态结构允许我在"O(log(n))"中执行上述操作,而不重新分配任何内存?
使用自定义分配器似乎是减少构建和发布std::set<...>
所花费的时间的一种方法。下面是一个简单分配器的完整演示,以及对结果时间的程序分析。
#include <algorithm>
#include <chrono>
#include <cstdlib>
#include <iostream>
#include <iterator>
#include <memory>
#include <set>
#include <vector>
// ----------------------------------------------------------------------------
template <typename T, std::size_t pool_size = 1024>
class pool_allocator
{
private:
std::vector<T*> d_pools;
T* d_next;
T* d_end;
public:
template <typename O>
struct rebind {
typedef pool_allocator<O, pool_size> other;
};
pool_allocator(): d_next(), d_end() {}
~pool_allocator() {
std::for_each(this->d_pools.rbegin(), this->d_pools.rend(),
[](T* memory){ operator delete(memory); });
}
typedef T value_type;
T* allocate(std::size_t n) {
if (std::size_t(this->d_end - this->d_next) < n) {
if (pool_size < n) {
// custom allocation for bigger number of objects
this->d_pools.push_back(static_cast<T*>(operator new(sizeof(T) * n)));
return this->d_pools.back();
}
this->d_pools.push_back(static_cast<T*>(operator new(sizeof(T) * pool_size)));
this->d_next = this->d_pools.back();
this->d_end = this->d_next + pool_size;
}
T* rc(this->d_next);
this->d_next += n;
return rc;
}
void deallocate(T*, std::size_t) {
// this could try to recycle buffers
}
};
// ----------------------------------------------------------------------------
template <typename Allocator>
void time(char const* name, std::vector<int> const& random) {
std::cout << "running " << name << std::flush;
using namespace std::chrono;
high_resolution_clock::time_point start(high_resolution_clock::now());
std::size_t size(0);
{
std::set<int, std::less<int>, Allocator> values;
for (int value: random) {
values.insert(value);
}
size = values.size();
}
high_resolution_clock::time_point end(high_resolution_clock::now());
std::cout << ": size=" << size << " time="
<< duration_cast<milliseconds>(end - start).count() << "msn";
}
// ----------------------------------------------------------------------------
int main()
{
std::cout << "preparing..." << std::flush;
std::size_t count(10000000);
std::vector<int> random;
random.reserve(count);
std::generate_n(std::back_inserter(random), count, [](){ return std::rand(); });
std::cout << "donen";
time<std::allocator<int>>("default allocator ", random);
time<pool_allocator<int, 32>>("custom allocator (32) ", random);
time<pool_allocator<int, 256>>("custom allocator (256) ", random);
time<pool_allocator<int, 1024>>("custom allocator (1024)", random);
time<pool_allocator<int, 2048>>("custom allocator (2048)", random);
time<pool_allocator<int, 4096>>("custom allocator (4096)", random);
time<std::allocator<int>>("default allocator ", random);
}
// results from clang/libc++:
// preparing...done
// running default allocator : size=10000000 time=13927ms
// running custom allocator (32) : size=10000000 time=9260ms
// running custom allocator (256) : size=10000000 time=9511ms
// running custom allocator (1024): size=10000000 time=9172ms
// running custom allocator (2048): size=10000000 time=9153ms
// running custom allocator (4096): size=10000000 time=9599ms
// running default allocator : size=10000000 time=13730ms
// results from gcc/libstdc++:
// preparing...done
// running default allocator : size=10000000 time=15814ms
// running custom allocator (32) : size=10000000 time=10868ms
// running custom allocator (256) : size=10000000 time=10229ms
// running custom allocator (1024): size=10000000 time=10556ms
// running custom allocator (2048): size=10000000 time=10392ms
// running custom allocator (4096): size=10000000 time=10664ms
// running default allocator : size=10000000 time=17941ms
在std::set
中使用自定义分配器可能会有所帮助。如果在构造集合之前知道元素的数量,您可以分配一个大小合适的原始内存缓冲区,然后在您的自定义分配器类(使用std::allocator
作为基类)中重写allocate
方法,以便它返回指向缓冲区中地址的指针,而不是调用new
操作符。它仍然需要内存分配,但只需要一次。它可能看起来像这样:
template<class T, size_t S>
class MyAlloc: public allocator<T>
{
T *buf;
size_t ptr;
public:
MyAlloc()
{
buf = (T*)malloc(sizeof(T) * S);
ptr = 0;
}
~MyAlloc()
{
free(buf);
}
T* allocate(size_t n, allocator<void>::const_pointer hint=0)
{
ptr += n;
return &buf[ptr - n];
}
void deallocate(T* p, size_t n)
{
//Do nothing.
}
template<class T1>
struct rebind
{
typedef MyAlloc<T1, S> other;
};
};
相关文章:
- 在声明中合并两个常量"std::set"(不是在运行时)
- 有没有办法对std::unordered_set、std::unrdered_map、std::set、std::map
- 将 std::set 与基于键的比较器一起使用
- 修改"std::set"中用户定义类型的值
- 如何在构造函数参数中初始化"std::set"?
- 如何使用 lower_bound/upper_bound 从 std::set 获取索引号?
- 如何在 C++ 中转发声明 std::set?
- 重构使用动态强制转换的 std::set 的比较运算符
- 为什么 std::set.erase(first, last) 会影响从中获取 (first, last) 的容器?
- std::set 是否将对象连续存储在内存中?
- 是否有一个 std::set 函数来确定不超过数字 x 的最大元素?
- 有什么理由不扩展 std::set 以添加下标运算符吗?
- 我从 std::set 得到const_iterator而不是迭代器
- 为什么 std:set(带有单个冒号)可以编译?
- 遍历 std::set 中包含的所有三重不同值?
- 插入 std::set 作为 std::map 的键
- 如何在 c++ 中使用默认值将 std::set 转换为 std::map
- 错误:'class std::unique_ptr<std::set<long unsigned int> >'没有名为 'size' 的成员
- 如何从 std::set 绘制 n 个元素的样本
- 为什么 std::set 容器使用的内存比其数据大小多得多?