boost::flat_map 及其与 map 和 unordered_map 相比的性能

2024-01-04

编程中的常识是,由于缓存命中,内存局部性可以大大提高性能。我最近发现boost::flat_map这是基于矢量的地图实现。它似乎并不像典型的那样受欢迎map/unordered_map所以我没能找到任何性能比较。它的比较如何?它的最佳用例是什么?

Thanks!


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

标杆管理

在网络上,我们很少找到(如果有的话)精心设计的基准。直到今天,我只找到了以记者的方式完成的基准(速度相当快,并在地毯下清除了数十个变量)。

1)您需要考虑缓存预热

大多数运行基准测试的人都担心计时器差异,因此他们运行他们的东西数千次并花费整个时间,他们只是小心地为每个操作花费相同的数千次,然后考虑可比性。

事实是,在现实世界中它没有什么意义,因为您的缓存不会是热的,并且您的操作可能只会被调用一次。因此,您需要使用 RDTSC 进行基准测试,并且只对调用它们一次进行计时。 Intel 做了一篇论文描述 http://www.intel.com/content/dam/www/public/us/en/documents/white-papers/ia-32-ia-64-benchmark-code-execution-paper.pdf如何使用 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

这是一个差异测量器,它将取所有测量值的最小值,以避免时不时地得到 -10**18(64 位第一个负值)。

请注意使用内部函数而不是内联汇编。如今,编译器很少支持第一个内联汇编,但更糟糕的是,编译器在内联汇编周围创建了完整的排序障碍,因为它无法静态分析内部,所以这是对现实世界的东西进行基准测试的问题,特别是在调用东西时就一次。因此,内部函数适合这里,因为它不会破坏编译器对指令的自由重新排序。

3)参数

最后一个问题是人们通常测试的场景变化太少。 容器性能受以下因素影响:

  1. 分配器
  2. 所包含类型的大小
  3. 执行包含类型的复制操作、赋值操作、移动操作、构造操作的成本。
  4. 容器中的元素数量(问题的大小)
  5. 类型有琐碎的3.-操作
  6. 类型是POD

第 1 点很重要,因为容器确实会不时进行分配,如果它们使用 CRT“new”或某些用户定义的操作(例如池分配或 freelist 或其他)进行分配,则非常重要......

(对于对第 1 部分感兴趣的人,加入 gamedev 上的神秘主题 http://www.gamedev.net/topic/669459-massive-allocation-perf-difference-between-win8-win7-and-linux/关于系统分配器性能影响)

第 2 点是因为某些容器(例如 A)会浪费时间来复制内容,并且类型越大,开销就越大。问题是,当与另一个容器 B 进行比较时,对于较小的类型,A 可能会胜过 B,而对于较大的类型,A 可能会输。

第 3 点与第 2 点相同,只是它将成本乘以某个权重因子。

第4点是大O问题与缓存问题混合在一起。对于少量类型,一些低复杂度容器的性能在很大程度上优于低复杂度容器(例如map vs. vector,因为它们的缓存局部性很好,但是map记忆碎片)。然后在某个交叉点,它们会失败,因为所包含的总体大小开始“泄漏”到主内存并导致缓存未命中,再加上可以开始感受到渐近复杂性的事实。

第 5 点是关于编译器能够在编译时删除空的或琐碎的内容。这可以极大地优化某些操作,因为容器是模板化的,因此每种类型都有自己的性能配置文件。

第 6 点与第 5 点相同,POD 可以受益于复制构造只是一个事实memcpy,并且某些容器可以针对这些情况有特定的实现,使用部分模板特化,或 SFINAE 根据 T 的特征选择算法。

关于平面地图

显然,平面映射是一个排序向量包装器,如 Loki AssocVector,但 C++11 附带了一些补充现代化,利用移动语义来加速单个元素的插入和删除。

这仍然是一个订购的容器。大多数人通常不需要订购部分,因此存在unordered...

您是否考虑过也许您需要一个flat_unorderedmap?这会是这样的google::sparse_map或者类似的东西——开放地址哈希图。

开放地址哈希图的问题是,在rehash他们必须将周围的所有内容复制到新扩展的平坦土地上,而标准无序映射只需重新创建哈希索引,而分配的数据保留在原处。当然缺点就是内存碎片化得厉害。

开放地址哈希映射中的重新哈希的标准是当容量超过桶向量的大小乘以负载因子时。

典型的负载系数是0.8;因此,您需要关心的是,如果您可以在填充哈希图之前预先调整其大小,请始终将其预先调整为:intended_filling * (1/0.8) + epsilon这将保证您在填充过程中永远不必虚假地重新散列和重新复制所有内容。

封闭地址映射的优点(std::unordered..)是你不必关心这些参数。

But the boost::flat_map是有序向量;因此,它总是具有 log(N) 渐近复杂度,这不如开放地址哈希图(摊销常数时间)。您也应该考虑这一点。

基准测试结果

这是一个涉及不同地图的测试(int钥匙和__int64/somestruct作为值)和std::vector.

测试类型信息:

typeid=__int64 .  sizeof=8 . ispod=yes
typeid=struct MediumTypePod .  sizeof=184 . ispod=yes

插入

EDIT:

My previous results included a bug: they actually tested ordered insertion, which exhibited a very fast behavior for the flat maps.
I left those results later down this page because they are interesting.
This is the correct test: random insert 100

我检查了实现,这里的平面地图中没有实现延迟排序之类的东西。每个插入都会动态排序,因此该基准表现出渐近趋势:

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

Warning: hereafter the 2 tests for std::map and both flat_maps are buggy and actually test ordered insertion (vs random insertion for other containers. yes it's confusing sorry):
mixed insert of 100 elements without reservation

我们可以看到,有序插入会导致回推,并且速度非常快。然而,从我的基准测试的非图表结果来看,我也可以说这并不接近反向插入的绝对最优值。在 10k 个元素时,在预先保留的向量上获得了完美的反向插入最优性。这给了我们 300 万次循环;我们在这里观察到 4.8M 的有序插入flat_map(因此是最优的 160%)。

mixed insert of 10000 elements without reservation Analysis: remember this is 'random insert' for the vector, so the massive 1 billion cycles come from having to shift half (in average) the data upward (one element by one element) at each insertion.

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

大小 = 100

大小 = 10000

迭代

尺寸超过 100(仅限 MediumPod 类型)

超大 10000(仅限中型 Pod 类型)

最后一粒盐

最后,我想回到“基准测试§3 Pt1”(系统分配器)。在最近的一项实验中,我正在围绕以下性能进行我开发的开放地址哈希图 http://sourceforge.net/projects/cgenericopenaddresshashmap/,我在某些计算机上测得 Windows 7 和 Windows 8 之间的性能差距超过 3000%std::unordered_map用例 (在这里讨论 http://www.gamedev.net/topic/669459-massive-allocation-perf-difference-between-win8-win7-and-linux/).
这让我想警告读者上述结果(它们是在Win7上进行的):您的里程可能会有所不同。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

boost::flat_map 及其与 map 和 unordered_map 相比的性能 的相关文章

  • C 编程 - 文件 - fwrite

    我有一个关于编程和文件的问题 while current NULL if current gt Id Doctor 0 current current gt next id doc current gt Id Doctor if curre
  • 为什么 C# Array.BinarySearch 这么快?

    我已经实施了一个很简单用于在整数数组中查找整数的 C 中的 binarySearch 实现 二分查找 static int binarySearch int arr int i int low 0 high arr Length 1 mid
  • 动态加载程序集的应用程序配置

    我正在尝试将模块动态加载到我的应用程序中 但我想为每个模块指定单独的 app config 文件 假设我的主应用程序有以下 app config 设置
  • 在结构中使用 typedef 枚举并避免类型混合警告

    我正在使用 C99 我的编译器是 IAR Embedded workbench 但我认为这个问题对于其他一些编译器也有效 我有一个 typedef 枚举 其中包含一些项目 并且我向该新类型的结构添加了一个元素 typedef enum fo
  • 从Web API同步调用外部api

    我需要从我的 Web API 2 控制器调用外部 api 类似于此处的要求 使用 HttpClient 从 Web API 操作调用外部 HTTP 服务 https stackoverflow com questions 13222998
  • BitTorrent 追踪器宣布问题

    我花了一点业余时间编写 BitTorrent 客户端 主要是出于好奇 但部分是出于提高我的 C 技能的愿望 我一直在使用理论维基 http wiki theory org BitTorrentSpecification作为我的向导 我已经建
  • 如何使用 ICU 解析汉字数字字符?

    我正在编写一个使用 ICU 来解析由汉字数字字符组成的 Unicode 字符串的函数 并希望返回该字符串的整数值 五 gt 5 三十一 gt 31 五千九百七十二 gt 5972 我将区域设置设置为 Locale getJapan 并使用
  • OleDbDataAdapter 未填充所有行

    嘿 我正在使用 DataAdapter 读取 Excel 文件并用该数据填充数据表 这是我的查询和连接字符串 private string Query SELECT FROM Sheet1 private string ConnectStr
  • 将 VSIX 功能添加到 C# 类库

    我有一个现有的单文件生成器 位于 C 类库中 如何将 VSIX 项目级功能添加到此项目 最终目标是编译我的类库项目并获得 VSIX 我实际上是在回答我自己的问题 这与Visual Studio 2017 中的单文件生成器更改 https s
  • C++ OpenSSL 导出私钥

    到目前为止 我成功地使用了 SSL 但遇到了令人困惑的障碍 我生成了 RSA 密钥对 之前使用 PEM write bio RSAPrivateKey 来导出它们 然而 手册页声称该格式已经过时 实际上它看起来与通常的 PEM 格式不同 相
  • 创建链表而不将节点声明为指针

    我已经在谷歌和一些教科书上搜索了很长一段时间 我似乎无法理解为什么在构建链表时 节点需要是指针 例如 如果我有一个节点定义为 typedef struct Node int value struct Node next Node 为什么为了
  • 重载<<的返回值

    include
  • 如何设计以 char* 指针作为类成员变量的类?

    首先我想介绍一下我的情况 我写了一些类 将 char 指针作为私有类成员 而且这个项目有 GUI 所以当单击按钮时 某些函数可能会执行多次 这些类是设计的单班在项目中 但是其中的某些函数可以执行多次 然后我发现我的项目存在内存泄漏 所以我想
  • 垃圾收集器是否在单独的进程中运行?

    垃圾收集器是否在单独的进程中启动 例如 如果我们尝试测量某段代码所花费的进程时间 并且在此期间垃圾收集器开始收集 它会在新进程上启动还是在同一进程中启动 它的工作原理如下吗 Code Process 1 gt Garbage Collect
  • 覆盖子类中的字段或属性

    我有一个抽象基类 我想声明一个字段或属性 该字段或属性在从该父类继承的每个类中具有不同的值 我想在基类中定义它 以便我可以在基类方法中引用它 例如覆盖 ToString 来表示 此对象的类型为 property field 我有三种方法可以
  • WPF/C# 将自定义对象列表数据绑定到列表框?

    我在将自定义对象列表的数据绑定到ListBox in WPF 这是自定义对象 public class FileItem public string Name get set public string Path get set 这是列表
  • 测试用例执行完成后,无论是否通过,如何将测试用例结果保存在变量中?

    我正在使用 NUNIT 在 Visual Studio 中使用 Selenium WebDriver 测试用例的代码是 我想在执行测试用例后立即在变量中记录测试用例通过或失败的情况 我怎样才能实现这一点 NUnit 假设您使用 NUnit
  • IEnumreable 动态和 lambda

    我想在 a 上使用 lambda 表达式IEnumerable
  • C++ 中类级 new 删除运算符的线程安全

    我在我的一门课程中重新实现了新 删除运算符 现在我正在使我的代码成为多线程 并想了解这些运算符是否也需要线程安全 我在某处读到 Visual Studio 中默认的 new delete 运算符是线程安全的 但这对于我的类的自定义 new
  • Erlang dict的时间复杂度

    我想知道 Erlang OTP 是否dict模块是作为哈希表实现的 在这种情况下它是否能提供这样的性能 平均情况 Search O 1 n k Insert O 1 Delete O 1 n k 最坏的情况下 Search O n Inse

随机推荐

  • 操作系统关闭过程中会发生什么?

    我希望这与编程足够相关 操作系统关闭过程中到底发生了什么 我们以开源的 Linux 为例 可能对此有更多的了解 如何 内核线程终止 当计算机处于 清除 状态时 电源是否停止供电 很明显 我所说的清晰状态是指 CPU 中不再发生任何事情 等等
  • 在文档中查找 HTML 字符串

    我想获取所有 HTML p p 在一个文档中 Using Regex使用以下命令查找所有此类字符串 Regex regex new Regex
  • Android Studio“项目结构”未正常显示

    我遵循 Android Studio 安装的所有必要步骤 我也检查过this https stackoverflow com questions 17961397 android studio new project missing fol
  • 在 Dyalog RIDE 中设置条件断点

    In 对话骑行 https github com Dyalog ride 我知道如何设置断点来停止 APL 源代码中特定行的执行 有没有办法使断点有条件 这样只有满足一组特定的条件 我才能在一行处停止 例如0 lt 没有IDE 或RIDE
  • 为什么 iostream 定义了一个 abs 函数,我该如何阻止它?

    以下 C 代码无法编译 int main double a abs 5 1 return 0 它抱怨说abs当然 没有定义 但以下内容确实可以编译 include
  • 如何在保存 Sitecore 项目时显示弹出窗口?

    保存 Sitecore 项目时 我尝试显示一个弹出窗口以与用户交互 根据他们更改的数据 我可能会显示一系列 1 或 2 个弹出窗口 询问他们是否要继续 我已经弄清楚如何利用 OnItemSaving 管道 这很简单 我不知道如何显示弹出窗口
  • 保留提交的JSP表单数据

    我有一个网络表单 JSP 它将数据提交到托管在不同服务器上的不同应用程序 提交表单数据后 该应用程序重定向回同一 JSP 页面 现在 我想保存输入的数据 以网络形式保留提交的数据有哪些不同的方法 我不喜欢将数据存储在数据库或任何文件中 PS
  • 当我在终端中调用 Emacs 错误“无法初始化颜色列表解档器”时

    我刚刚在 MacBook Pro 上安装了 emacs 但是当我在终端中输入 emacs 时 出现以下错误 Emacs x86 64 10 10 5647 247335 无法初始化颜色列表解档器 错误域 NSCocoaErrorDomain
  • AngularJS ui-router $state.go('^') 仅更改地址栏中的 URL,但不加载控制器

    我正在尝试使用 angularjs 创建一个 Todo App ui router 它有 2 列 第一栏 待办事项列表 第 2 列 Todo 详细信息或 Todo 编辑表单 在保存待办事项后的编辑和创建控制器中 我想重新加载列表以显示适当的
  • portaudio.h:没有这样的文件或目录

    我在 ubuntu 16 04 中尝试使用 pip3 安装 pyaudio 时出现以下错误 Collecting pyaudio Downloading PyAudio 0 2 11 tar gz Installing collected
  • python 3.4 谷歌浏览器历史

    我真的被我想做的事情困住了 我想制作一个非常简单的脚本来显示 Google Chrome 的历史记录 当我使用以下代码行时 f open C Users joey AppData Local Google Chrome User Data
  • 使用 ckeditor 整理 html

    您好 我在 ckeditor 方面遇到了一个小问题 基本上我需要让编辑器运行它的 html 清理命令 有什么办法可以做到这一点吗 目前 在我在源代码中输入一些内容然后按保存后 它似乎没有运行我希望它像在 正常 编辑器视图中那样整理 html
  • 给定一个 Android 音乐播放列表名称,如何找到播放列表中的歌曲?

    可以通过查询找到播放列表名称MediaStore Audio Playlists EXTERNAL CONTENT URI然后看看MediaStore Audio PlaylistsColumns NAME柱子 还有一个数据列 MediaS
  • REST Auth 的 Cocoa Base 64 实现

    我可以使用干净 有效的 Base64 实现来通过 HTTP 对 REST 协议进行授权 有人可以帮助我或为我指明方向吗 Thanks 您应该完全没有必要这样做 在较高的层面上 Cocoa 提供了 NSURLConnection 来进行 HT
  • 使用 dlsym 加载已命名的未导出符号?

    是否可以使用以下方式从框架加载命名的未导出符号dlsym 我尝试导入的符号有一个在框架内引用的名称 这是我需要调用的函数 我试着像往常一样做dlopen dlsym方式 但是当我尝试加载未导出的符号时 dlsym返回一个NULL指针 dls
  • 蓝兹编程

    我正在使用 USB 蓝牙适配器在 Raspberry Pi 上使用 BlueZ 进行编程 我需要能够以编程方式连接到 Arduino BT 问题是 Arduino 的蓝牙模块仍在使用传统配对 因此每当我尝试打开设备的套接字时 我都会收到Pe
  • 如何在Python中计算mod b?

    Python中有取模函数吗math图书馆 Isn t 15 4 3 但15 mod 4是1 对吗 有的是 符号 它不仅仅是求余数 而是求模运算
  • 尽管提供了 Twitter Api,回调 URL 仍未获得批准

    在 Twitter 控制台中 我有一个来自 firebase 的回调 url 链接 然而 当我尝试使用 twitter 进行身份验证时 出现错误 Request failed forbidden 403 UserInfo NSLocaliz
  • Gradle eclipse classpath - 在快照和项目依赖之间切换

    我们的 Java 项目中有多个模块 每个模块都会将 SNAPSHOT jar 文件发布到 Nexus 存储库 所有子模块都直接依赖于 SNAPSHOT jar 文件 在开发过程中 我们希望依赖 Eclipse 项目而不是 SNAPSHOT
  • boost::flat_map 及其与 map 和 unordered_map 相比的性能

    编程中的常识是 由于缓存命中 内存局部性可以大大提高性能 我最近发现boost flat map这是基于矢量的地图实现 它似乎并不像典型的那样受欢迎map unordered map所以我没能找到任何性能比较 它的比较如何 它的最佳用例是什