C++ boost::flat_-map及其与map和无序_-map的性能比较

C++ boost::flat_-map及其与map和无序_-map的性能比较,c++,boost,map,C++,Boost,Map,编程中的常识是,由于缓存命中,内存局部性大大提高了性能。我最近发现了关于boost::flat_map,这是一种基于向量的map实现。它似乎不像典型的map/unordered\u map那样受欢迎,因此我无法找到任何性能比较。它是如何比较的,最好的用例是什么 谢谢 从文档来看,这似乎类似于我大量使用的Loki::AssocVector。因为它是基于向量的,所以它具有向量的特性,也就是说: 每当大小超过容量时,迭代器就会失效 当超过容量时需要重新分配和移动对象,即插入时间不保证恒定,除非在结束

编程中的常识是,由于缓存命中,内存局部性大大提高了性能。我最近发现了关于
boost::flat_map
,这是一种基于向量的map实现。它似乎不像典型的
map
/
unordered\u map
那样受欢迎,因此我无法找到任何性能比较。它是如何比较的,最好的用例是什么


谢谢

从文档来看,这似乎类似于我大量使用的
Loki::AssocVector
。因为它是基于向量的,所以它具有向量的特性,也就是说:

  • 每当
    大小
    超过
    容量
    时,迭代器就会失效
  • 当超过
    容量时
    需要重新分配和移动对象,即插入时间不保证恒定,除非在
    结束时插入
    容量>大小
  • 由于缓存位置,查找比
    std::map
    快,这是一种二进制搜索,与
    std::map
    具有相同的性能特征
  • 使用更少的内存,因为它不是链接的二叉树
  • 它永远不会收缩,除非你强迫它收缩(因为这会触发重新分配)

最好的用法是当您提前知道元素的数量时(这样您可以预先保留),或者当插入/删除很少但查找频繁时。迭代器失效使它在某些用例中有点麻烦,因此它们在程序正确性方面不可互换。

我最近在我的公司在不同的数据结构上运行了一个基准测试,所以我觉得我需要说一句话。正确地对某个事物进行基准测试是非常复杂的

标杆管理 在网络上,我们很少能找到(如果有的话)设计良好的基准。直到今天,我才发现基准测试是以记者的方式完成的(相当快,并且覆盖了几十个变量)

<强> 1)< /强>你需要考虑缓存升温< /P> 大多数人运行标杆是害怕计时器差异,因此他们运行他们的东西数以千计的时间,并采取了全部时间,他们只是小心采取相同的数千次,每次操作,然后认为可比。 事实是,在现实世界中,这没有什么意义,因为您的缓存将不热,并且您的操作可能只被调用一次。因此,您需要使用RDTSC进行基准测试,并且时间工具只调用它们一次。 英特尔写了一篇关于如何使用RDTSC的论文(使用cpuid指令刷新管道,并在程序开始时至少调用3次以稳定管道)

2)RDTSC精度测量

我还建议这样做:

u64 g_correctionFactor;  // number of clocks to offset after each measurement to remove the overhead of the measurer itself.
u64 g_accuracy;

static u64 const errormeasure = ~((u64)0);

#ifdef _MSC_VER
#pragma intrinsic(__rdtsc)
inline u64 GetRDTSC()
{
    int a[4];
    __cpuid(a, 0x80000000);  // flush OOO instruction pipeline
    return __rdtsc();
}

inline void WarmupRDTSC()
{
    int a[4];
    __cpuid(a, 0x80000000);  // warmup cpuid.
    __cpuid(a, 0x80000000);
    __cpuid(a, 0x80000000);

    // measure the measurer overhead with the measurer (crazy he..)
    u64 minDiff = LLONG_MAX;
    u64 maxDiff = 0;   // this is going to help calculate our PRECISION ERROR MARGIN
    for (int i = 0; i < 80; ++i)
    {
        u64 tick1 = GetRDTSC();
        u64 tick2 = GetRDTSC();
        minDiff = std::min(minDiff, tick2 - tick1);   // make many takes, take the smallest that ever come.
        maxDiff = std::max(maxDiff, tick2 - tick1);
    }
    g_correctionFactor = minDiff;

    printf("Correction factor %llu clocks\n", g_correctionFactor);

    g_accuracy = maxDiff - minDiff;
    printf("Measurement Accuracy (in clocks) : %llu\n", g_accuracy);
}
#endif
插入

编辑:

我以前的结果包括一个bug:他们实际上测试了有序插入,这显示了平面贴图的快速行为。
我稍后将这些结果留在本页,因为它们很有趣。
这是正确的测试:

我已经检查了实现,这里的平面图中没有延迟排序。每个插入都会动态排序,因此该基准显示出渐进趋势:

映射:O(N*log(N))
哈希映射:O(N)
向量和平面图:O(N*N)

警告:此后对
std::map
flat\u map
的两项测试都是错误的,并且实际测试了有序插入(与其他容器的随机插入相比。是的,很困惑,抱歉):

我们可以看到,有序插入会导致向后推,而且速度非常快。然而,从我的基准测试的非图表结果来看,我也可以说,这并不接近反向插入的绝对最优。在10k元素时,在预保留向量上获得了完美的后插入最优。这给了我们3百万个周期;我们在这里观察到4.8米的有序插入到
平面图中(因此是最佳插入的160%)

分析:请记住,这是向量的“随机插入”,因此10亿个大循环来自于每次插入时必须向上移动一半(平均)数据(一个元素对一个元素)

随机搜索3个元素(时钟重新规范化为1)

尺寸=100

尺寸=10000

迭代

尺寸超过100(仅适用于MediumPod类型)

尺寸超过10000(仅适用于MediumPod类型)

最后一粒盐

最后,我想回到“基准测试§3 Pt1”(系统分配器)。在最近的一次关于性能的实验中,我在一些
std::unordered_map
用例()上测量到Windows 7和Windows 8之间的性能差距超过3000%。
这让我想提醒读者上述结果(它们是在Win7上产生的):您的里程可能会有所不同


致以最良好的祝愿

哦,那样的话,这是有道理的。Vector的固定摊销时间保证仅在末尾插入时适用。在随机位置插入时,每次插入的平均值应为O(n),因为插入点之后的所有内容都必须向前移动。因此,我们期望在基准测试中出现二次行为,即使对于小N,也会很快爆发。例如,AssocVector样式的实现可能会将排序推迟到需要查找时,而不是在每次插入后进行排序。很难说没有看到你的基准。@BillyONeal:啊,我们和同事一起检查了代码,发现了罪魁祸首,我的“随机”插入命令是因为我使用了std::set来确保插入的键是唯一的。这是一个明显的愚蠢,但我通过随机洗牌修正了这一点,
typeid=__int64 .  sizeof=8 . ispod=yes
typeid=struct MediumTypePod .  sizeof=184 . ispod=yes