繁体   English   中英

C++:删除 object 而不调用析构函数

[英]C++: Remove object without calling destructor

对于那些不熟悉多态 memory 资源 (PMR) 的人,

std::pmr::monotonic_buffer_resource

"the class std::pmr::monotonic_buffer_resource is a special-purpose memory resource class that releases the allocated memory only when the resource is destroyed. It is intended for very fast memory allocations in situations where memory is used to build up a few objects然后一下子就被释放了。”

因此,如果析构函数的唯一目的是释放 memory,那么在单调缓冲区资源上调用它是没有意义的:

auto mbr = std::make_unique<std::pmr::monotonic_buffer_resource>();

auto vectors = std::pmr::vector<std::pmr::vector<int>>(mbr.get());
vectors.resize(1'000'000, pmr_vector<int>(100));  // Create 1M vectors with 100 ints each

// Reset vectors
// This will cause 1M destructors to be unnecessarily called
vectors = {};

由于所有 memory 都是从mbr中提取的,我想简单地破坏缓冲区。 然而,这不会阻止vectors的析构函数被调用并试图释放已经被释放的 memory。

一个非常非常糟糕的方法是调用std::memset(&vectors, 0, sizeof(vectors)); . 这将性能提高了 2.8 倍,但对(现已删除)答案的评论强烈同意不应该这样做。

因为标准库还没有完全支持 PMR,所以我在这里提供了一个基于 boost 的完整示例https://godbolt.org/z/nMbbez - 它需要 -lboost_container

t.niese在评论中有一个很好的建议,那就是改变容器的分配器。 毕竟,我们的问题不在于容器,而在于分配器认为它需要调用do_deallocate 我们可以简单地复制 boost 的polymorphic_allocator的代码并将deallocate替换为 no-op。 您可以在 此处找到完整的代码。 最后,它只是从polymorphic_allocator.hpp复制而来,并被deallocate替换为:

  void deallocate(T* p, size_t n) noexcept {}

我们可以通过使用打印 memory 资源来验证这是否有效(请参阅此处的debug_resource或上面链接的要点中的那个)。 如果我们使用原始的 polymorphic_allocator 分配 3 个包含 100 个条目的内部向量,我们会看到

my_vector<my_vector<int>> vectors{mbr.get()};
vectors.resize(3, my_vector<int>(100));
// malloc 96
// malloc 400
// malloc 400
// malloc 400
// free 400
// free 400
// free 400
// free 96

请注意,我们不需要将mbr传递给内部向量。 这是由 PMR 自动完成的。 使用我们新的no_free_allocator ,结果只有

// malloc 96
// malloc 400
// malloc 400
// malloc 400

正如预期的那样,memory 不再被释放,但对象仍被正确解构。 让我们看看这如何改变解构的性能。 整个基准也包含在gist中。 请注意,我更改了内部向量的大小并为monotonic_buffer_resource预分配了 memory 。 这是为了使效果更加明显,并特别突出 PMR 和 MBR 的好处。 以下是结果( g++10 -O3 ):

多态分配器 no_free_allocator
使用 default_resource 调整大小 47910 62574
使用 monotonic_buffer_resource 调整大小 30298 30947
使用 default_resource 重置 12298 3(但泄漏)
使用 monotonic_buffer_resource 重置 5463 2520

首先,这证明了使用monotonic_buffer_resource的最初动机。 您可以看到,如果我们不对每个向量调用malloc ,则调整大小要快得多。 如果没有使用 MBR,我仍然不确定为什么no_free_allocatorpolymorphic_allocator慢,但这是一个不同的话题。

其次,通过删除对do_deallocate的虚拟方法调用,我们将重置外部向量和 MBR 的成本降低了 50% 以上。 2520 us 的剩余成本是由于必须释放 MBR 而不是重置向量造成的。

std::allocator (调整大小需要 58087 us,重置需要 10741 us)相比,这需要大约两倍的速度。

我不熟悉多态容器和分配器,但我怀疑问题在于所有这些向量都使用不同的分配器。 只有你的主要使用你的mbr 所以你真的有 1000001 个不同的池。 不是你想的那样。

所以试试这个:

// vectors of 1 element each as your actual code (see note bellow)
vectors.resize(1000000, pmr_vector<int>(1, 100, mbr.get())); 

// or
// vectors of 100 elements each as your comment (see note bellow)
vectors.resize(1000000, pmr_vector<int>(100, 0, mbr.get())); 

这做同样的事情,除了所有 100001 个向量现在共享相同的monotonic_buffer_resource 现在您只使用一个在“重置”`mbr 时释放的池。


注意:您的评论是错误的。 vectors.resize(1'000'000, {100}); 创建1'000'000个向量,每个向量都有 1 个值为100的元素。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM