unordered_set 可以为节点和存储桶列表使用不同的分配器吗?

Mar*_*utz 12 c++ allocator unordered-set c++17 c++pmr

我想将 astd::pmr::unordered_map与 a一起使用std::pmr::monotonic_buffer_resource。两者配合得很好,因为集合的节点是稳定的,所以我不会通过重新分配在缓冲区资源中创建很多漏洞:

 std::pmr::monotonic_buffer_resource res;
 std::pmr::unordered_set<T> set(&res);
Run Code Online (Sandbox Code Playgroud)

也就是说,除了桶列表,当集合超过max_load_factor(). 假设我无法reserve()摆脱这种情况,并且我实际上关心自增长以来旧桶列表留下的缓冲区资源中的漏洞,我有什么选择?

如果我知道它unordered_set是作为 实现的std::vector<std::forward_list>,就像在(某些版本的)MSVC 中一样,那么我应该能够使用 ascoped_allocatorvectorforward_list. 但)我不能依赖unordered_set是一个vector<forward_list>可移植的代码和b)scoped_allocator是一个Allocator,而monotonic_buffer_resourcememory_resource,阻抗失配,这将使非常复杂的初始化。

或者我可以根据请求的大小编写一个switch_memory_resource委托给其他memory_resources 的 s 。然后,我可以使用monotonic_buffer_resource与节点大小匹配的请求(但是,我不能,可移植地,知道)以及default_memory_resource()其他所有内容。我可能会做出有根据的猜测,节点最多为sizeof(struct {void* next; size_t hash; T value;}),通过将其乘以 2 来添加一些误差幅度,并将其用作两个memory_resources之间的截止点,但我想知道是否有更简洁的方法?

Pab*_*ern 7

我几年前提出并被 C++17 采用的少数具体资源类型是一组极简主义的有用分配器。正如您的问题所证明的那样,它们并不能为每种情况提供最佳行为。没有多少调音旋钮,我对缺少功能感到有些遗憾,但它们在大多数情况下仍然有用。

对于您的具体情况,您会说“假设我无法reserve()摆脱这种情况,而且我实际上关心自增长以来旧桶列表留下的缓冲区资源中的漏洞。” 我不确定任何通用分配器可以帮助您。桶列表的几何增长会在任何分配策略中留下漏洞。问题是这些漏洞是否可以重复使用和/或最小化。正如您所指出的,只有针对特定情况非常仔细地定制分配器才能最大限度地减少这些漏洞。但也许你的假设太强了。

考虑一个std::pmr::vector<int>. 这是 a 的最坏情况,monotonic_buffer_resource因为每次重新分配都会导致内存泄漏。然而,即使是这种情况,最坏情况下的内存浪费也只有 50%;即,它使用的内存永远不会超过完全重用内存块的资源的两倍。诚然,50% 可能非常糟糕,但在您的情况下,我们谈论的要少得多。对于相当大的集合,与存储桶和数据本身相比,存储桶列表很小,您可以使用它reserve来最小化重新分配。因此,我的第一条建议是继续使用而monotonic_buffer_resource无需更改,并测量是否有不可接受的内存使用。第二个实验是使用unsynchronized_pool_resource由 (upstream) 支持的monotonic_buffer_resource

如果您决定为此目的创建自定义资源,这可能会很有成效,甚至可能很有趣,那么您选择一些较低的阈值来传递给单调分配器的方法可能会奏效,实际上不会花费太多精力。您还可以考虑使其自适应:保留最后的列表,例如 4 个分配大小。如果任何大小获得超过两次命中,则假设它是您的节点大小并从单调资源分配这些节点,而其他请求则直接传递到上游资源。但是请注意,只有在您知道发生了什么时才使用这样的自定义分配器。如果你有一个std::pmr::unordered_set<std::pmr::string>,那么这两种方法都可能导致从上游资源分配许多字符串,从而失去单调缓冲区的好处。正如您所看到的,在您的遗愿清单中过于吝啬内存可能会在一般情况下适得其反。您可能会发现未修改monotonic_buffer_resource的赌注更好。

祝你好运,请在这里报告你的发现。另外,如果您有一个可以解决您的问题(或任何其他常见分配问题)的通用资源的想法,我很想听听。标准中肯定有一些更有用的资源类型的空间。


Sur*_*urt 0

的定义std::pmr::unordered_set

using unordered_set = std::unordered_set<Key, Hash, Pred,
                                         std::pmr::polymorphic_allocator<Key>>
Run Code Online (Sandbox Code Playgroud)

这表明它仅用于节点。

但一个简短的测试表明它可以用于两者,这让我感到难过。因此唯一的方法是测试分配的大小。

这部分表明分配器只考虑Key

std::pmr::polymorphic_allocator<**Key**>
Run Code Online (Sandbox Code Playgroud)

在旧的分配器中,有一个重新绑定函数可以转换为我在 中没有看到的另一种分配器类型pmr(它在 中std::allocator_traits,请参阅注释)。

我开始写这样的东西

using unordered_set = std::unordered_set<Key, Hash, Pred,
                                         std::pmr::polymorphic_allocator<Key>>
Run Code Online (Sandbox Code Playgroud)

当我看到已经存在 pre-pmrscoped_allocator_adaptor 时,不知道它是否与 兼容pmr,这需要进一步调查。