C ++:在不调用析构函数的情况下删除对象

C++: Remove object without calling destructor

对于那些不熟悉多态内存资源 (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 and then is released all at once."

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

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

由于所有内存都来自 mbr,我想简单地销毁缓冲区。但是,这不会阻止 vectors' 析构函数被调用并尝试释放已经释放的内存。

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

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

我不熟悉多态容器和分配器,但我怀疑问题在于所有这些向量都使用不同的分配器。只有你的主要使用你的 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 的元素。

t.niese 在评论中有一个很好的建议,那就是更改容器的分配器。毕竟,我们的问题不在于容器,而在于分配器认为它需要调用 do_deallocate。我们可以简单地复制 boost 的 polymorphic_allocator 的代码并将 deallocate 替换为空操作。你可以找到整个代码 here. In the end, it is just copied from polymorphic_allocator.hppdeallocate 替换成这样:

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

我们可以通过使用打印内存资源来验证这是否有效(请参阅此处的 或上面链接的要点中的那个)。如果我们使用原始 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 传递到内部向量中。这是完成 。使用我们的新no_free_allocator,结果只有

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

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

polymorphic_allocator no_free_allocator
resize w/ default_resource 47910 62574
resize w/ monotonic_buffer_resource 30298 30947
reset w/ default_resource 12298 3 (but leaks)
reset w/ 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 重置)相比,这大约快两倍。