【问题标题】:Is the order of an unordered_map deterministic?unordered_map 的顺序是确定性的吗?
【发布时间】:2018-02-11 19:59:07
【问题描述】:

我想知道是否有任何保证 unordered_map 的顺序在所有 CPU、线程等中总是相同的。

我意识到特定顺序本身可能没有明显的模式(因此,“无序”映射),但如果我在另一台机器上运行我的进程,或连续多次,或在不同的线程上,将如果散列函数和插入顺序保持不变,插入的项目总是相同的吗?换句话说,如果我的代码没有改变,我的流程的每次执行都会导致地图的元素处于相同的顺序吗?

我进行了几次测试,每次插入后的项目顺序似乎都相同,但这可能只是侥幸,我只有这台机器可以测试。我需要知道顺序是否会受到任何其他因素的影响,例如 CPU/内存架构、操作系统(Windows 8 与 Windows 10)等。

【问题讨论】:

  • 跨越所有可能的实现方式?当然不是。这将是限制标准部分的方式。此外,类型的名称指定您可能不假设特定顺序...
  • @StoryTeller 我不是在问跨实现的一致性,而是在我编译的代码碰巧运行的任何地方/时间的一致性。另外,我的问题与容器的名称并不矛盾……作为类比,有(p)RNG 是确定性的。如果有人问他们是否应该期望来自同一个种子的相同随机数序列,回答“名字暗示不”是没有意义的。
  • 只需使用boost.multiindex,这样您就可以拥有可预测的元素顺序并通过哈希访问。

标签: c++ dictionary deterministic unordered


【解决方案1】:

TL;DR:可以,但我不建议这样做。如果可以,请使用其他数据结构;您自己的哈希表、“treap”、平面数组或其他东西。

std::unordered_map(或集合)中的项目顺序更多地取决于标准库实现,而不是硬件/CPU/等。

因此,如果您在不同的硬件上使用相同的库实现,并提供自己的哈希函数(以确保它不会在运行中随机化 - 以对抗针对您的数据结构的 DoS 攻击)或其他硬件或操作系统-依赖,那么你应该没问题。

但是,如果您在标准中寻找保证,您将找不到任何保证。唯一相关的保证是,在对象的同一个实例中,同一个键将散列到同一个桶。我认为即使对于地图的不同实例也不能保证,而且我从(痛苦的)个人经验中知道,应用程序的不同运行之间没有一致性。

但并不是所有的希望都落空了!如果您坚持unordered_map 的相同实现,并使用您自己的哈希函数,并查看实现以确保没有隐藏的意外(任何对硬件/操作系统/时间/RNG/等的依赖应该是相对容易发现)你可以管理它。

请注意,由于您似乎在 Windows 上并且可能使用 MSVC,默认的 unordered_map 和默认的散列算法是 not 在同一编译二进制文件(在至少不是在 2013/2015 IIRC。)

要记住的另一件事是,如果您认真对待一致性,则必须确保您对 CRT 的链接静态。如果您链接到 DLL 版本,则未来的某些补丁/更新可能会在您发布应用程序后改变其行为。

【讨论】:

  • 感谢@yzt,“应用程序的不同运行之间没有一致性”是我最关心的问题。
  • @Tyson,这是默认行为;如果您使用自己的哈希,我认为您会没事的。通读代码,然后测试以确保!
【解决方案2】:

下面的代码在不同的硬件平台上运行时得到了不同的输出顺序。

#include <iostream>
#include <set>
#include <unordered_set>
#include <vector>
#include <functional>
#include <string>

struct hash_func {
    std::size_t operator()(const std::string &key) const {
        std::size_t hash = 0U;
        const std::size_t mask = 0xF0000000;
        for (std::string::size_type i = 0; i < key.length(); ++i) {
            hash = (hash << 4U) + key[i];
            std::size_t x = hash & mask;
            if (x != 0)
                hash ^= (x >> 24);
            hash &= ~x;
        }
        std::cout << "for key " << key << " hash " << hash << std::endl;

        return hash;
    }
};

void show() {
    std::vector<std::string> v;
    v.push_back("n1YxyaBzoRogNh72eri7HBGijCAtcHpf9nm,");
    v.push_back("n1GV9UScgncwU6KKL9T18mCo2S6uAE69SWs,");
    v.push_back("n1X2SXyEKej7GZgAreXDCkiT59qaYKBDcYi,");

    std::unordered_set<std::string, hash_func> s;
    for (auto it = v.begin(); it != v.end(); it++) {
        s.insert(*it);
    }

    for (auto it = s.begin(); it != s.end(); it++) {
        std::cout << *it << std::endl;
    }
}

int main() {
    show();

    return 0;
}

结果是:

1) Intel(R) Xeon(R) CPU E5-2680 v4 @ 2.40GHz

for key n1YxyaBzoRogNh72eri7HBGijCAtcHpf9nm, hash 43411516
for key n1GV9UScgncwU6KKL9T18mCo2S6uAE69SWs, hash 93983804
for key n1X2SXyEKej7GZgAreXDCkiT59qaYKBDcYi, hash 17984604
n1X2SXyEKej7GZgAreXDCkiT59qaYKBDcYi,
n1GV9UScgncwU6KKL9T18mCo2S6uAE69SWs,
n1YxyaBzoRogNh72eri7HBGijCAtcHpf9nm,

2) AMD EPYC 7501 32 核处理器

for key n1YxyaBzoRogNh72eri7HBGijCAtcHpf9nm, hash 43411516
for key n1GV9UScgncwU6KKL9T18mCo2S6uAE69SWs, hash 93983804
for key n1X2SXyEKej7GZgAreXDCkiT59qaYKBDcYi, hash 17984604
n1X2SXyEKej7GZgAreXDCkiT59qaYKBDcYi,
n1YxyaBzoRogNh72eri7HBGijCAtcHpf9nm,
n1GV9UScgncwU6KKL9T18mCo2S6uAE69SWs,

【讨论】:

  • 差异来自软件实现,而不是 CPU。
  • 你的意思是编译器?我使用了相同的编译器,llvm-6.0.1。
  • 特定操作系统上特定工具链的特定打包版本的组合,是的。它们可以以不同的方式进行配置。并且可能存在“机会”元素。但这并不是 CPU 单方面决定的。它没有 C++ 容器的概念。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-03-11
  • 2011-04-18
  • 2020-10-11
  • 2015-09-30
  • 1970-01-01
  • 2022-11-25
  • 1970-01-01
相关资源
最近更新 更多