从文件读取/写入 std::unordered_map 的更快方法

2024-02-03

我正在与一些非常大的公司合作std::unordered_maps(数亿个条目)并且需要将它们保存到文件中或从文件中加载它们。我目前执行此操作的方法是迭代映射并一次读取/写入每个键和值对:

std::unordered_map<unsigned long long int, char> map;

void save(){
    std::unordered_map<unsigned long long int, char>::iterator iter;
    FILE *f = fopen("map", "wb");
    for(iter=map.begin(); iter!=map.end(); iter++){
        fwrite(&(iter->first), 8, 1, f);
        fwrite(&(iter->second), 1, 1, f);
    }
    fclose(f);
}

void load(){
    FILE *f = fopen("map", "rb");
    unsigned long long int key;
    char val;
    while(fread(&key, 8, 1, f)){
        fread(&val, 1, 1, f);
        map[key] = val;
    }
    fclose(f);
}

但由于有大约 6.24 亿个条目,从文件中读取地图需要 9 分钟。写入文件速度更快,但仍需要几分钟。有没有更快的方法来做到这一点?


C++ unordered_map实现必须全部使用chaining https://en.wikipedia.org/wiki/Hash_table#Separate_chaining。您可能希望对通用哈希表执行此操作有多种非常好的理由,对此进行了讨论here https://stackoverflow.com/questions/31112852/how-stdunordered-map-is-implemented.

这对性能有着巨大的影响。最重要的是,这意味着哈希表的条目可能分散在整个内存中,这使得访问每个条目的效率降低一个数量级(左右),如果可以以某种方式串行访问它们,就会出现这种情况。

幸运的是,您可以构建哈希表,当哈希表接近满时,可以对相邻元素进行近乎顺序的访问。这是使用完成的开放寻址 https://en.wikipedia.org/wiki/Hash_table#Open_addressing.

由于您的哈希表不是通用的,您可以尝试这个。

下面,我构建了一个简单的哈希表容器,具有开放寻址和线性探测 https://en.wikipedia.org/wiki/Linear_probing。它假设了一些事情:

  1. 您的密钥已经以某种方式随机分布。这消除了对哈希函数的需要(尽管良好的哈希函数构建起来相当简单,即使伟大的哈希函数很困难)。

  2. 您只能将元素添加到哈希表中,而不能删除它们。如果不是这种情况,您需要更改used向量成可以保持三种状态的东西:USED, UNUSED, and TOMBSTONE where TOMBSTONE是删除元素的声明,用于继续线性搜索探测或停止线性插入探测。

  3. 您提前知道哈希表的大小,因此无需调整其大小/重新哈希。

  4. 您不需要以任何特定顺序遍历元素。

当然,网上可能有各种优秀的开放寻址哈希表实现,可以解决上述许多问题。然而,我的表格的简单性使我能够传达重要的一点。

重要的一点是:我的设计允许哈希表的所有信息存储在三个向量中。即:内存是连续的。

连续内存分配速度快,读取速度快,写入速度快。其影响是深远的。

使用与我相同的测试设置之前的回答 https://stackoverflow.com/a/48410769/752843,我得到以下时间:

Save. Save time = 82.9345 ms
Load. Load time = 115.111 ms

保存时间缩短了 95%(快 22 倍),加载时间缩短了 98%(快 62 倍)。

Code:

#include <cassert>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <functional>
#include <iostream>
#include <random>
#include <vector>

const int TEST_TABLE_SIZE = 10000000;



template<class K, class V>
class SimpleHash {
 public:
  int usedslots = 0;

  std::vector<K> keys;
  std::vector<V> vals;
  std::vector<uint8_t> used;

  //size0 should be a prime and about 30% larger than the maximum number needed
  SimpleHash(int size0){
    vals.resize(size0);
    keys.resize(size0);
    used.resize(size0/8+1,0);
  }

  //If the key values are already uniformly distributed, using a hash gains us
  //nothing
  uint64_t hash(const K key){
    return key;
  }

  bool isUsed(const uint64_t loc){
    const auto used_loc = loc/8;
    const auto used_bit = 1<<(loc%8);
    return used[used_loc]&used_bit;    
  }

  void setUsed(const uint64_t loc){
    const auto used_loc = loc/8;
    const auto used_bit = 1<<(loc%8);
    used[used_loc] |= used_bit;
  }

  void insert(const K key, const V val){
    uint64_t loc = hash(key)%keys.size();

    //Use linear probing. Can create infinite loops if table too full.
    while(isUsed(loc)){ loc = (loc+1)%keys.size(); }

    setUsed(loc);
    keys[loc] = key;
    vals[loc] = val;
  }

  V& get(const K key) {
    uint64_t loc = hash(key)%keys.size();

    while(true){
      if(!isUsed(loc))
        throw std::runtime_error("Item not present!");
      if(keys[loc]==key)
        return vals[loc];

      loc = (loc+1)%keys.size();
    }
  }

  uint64_t usedSize() const {
    return usedslots;
  }

  uint64_t size() const {
    return keys.size();
  }
};

typedef SimpleHash<uint64_t, char> table_t;

void SaveSimpleHash(const table_t &map){
  std::cout<<"Save. ";
  const auto start = std::chrono::steady_clock::now();
  FILE *f = fopen("/z/map", "wb");
  uint64_t size = map.size();
  fwrite(&size, 8, 1, f);
  fwrite(map.keys.data(), 8, size, f);
  fwrite(map.vals.data(), 1, size, f);
  fwrite(map.used.data(), 1, size/8+1, f);
  fclose(f);
  const auto end = std::chrono::steady_clock::now();
  std::cout<<"Save time = "<< std::chrono::duration<double, std::milli> (end-start).count() << " ms" << std::endl;
}

table_t LoadSimpleHash(){
  std::cout<<"Load. ";
  const auto start = std::chrono::steady_clock::now();
  FILE *f = fopen("/z/map", "rb");

  uint64_t size;
  fread(&size, 8, 1, f);

  table_t map(size);
  fread(map.keys.data(), 8, size, f);
  fread(map.vals.data(), 1, size, f);
  fread(map.used.data(), 1, size/8+1, f);
  fclose(f);
  const auto end = std::chrono::steady_clock::now();
  std::cout<<"Load time = "<< std::chrono::duration<double, std::milli> (end-start).count() << " ms" << std::endl;

  return map;
}

int main(){
  //Perfectly horrendous way of seeding a PRNG, but we'll do it here for brevity
  auto generator = std::mt19937(12345); //Combination of my luggage
  //Generate values within the specified closed intervals
  auto key_rand  = std::bind(std::uniform_int_distribution<uint64_t>(0,std::numeric_limits<uint64_t>::max()), generator);
  auto val_rand  = std::bind(std::uniform_int_distribution<int>(std::numeric_limits<char>::lowest(),std::numeric_limits<char>::max()), generator);

  table_t map(1.3*TEST_TABLE_SIZE);
  std::cout<<"Created table of size "<<map.size()<<std::endl;

  std::cout<<"Generating test data..."<<std::endl;
  for(int i=0;i<TEST_TABLE_SIZE;i++)
    map.insert(key_rand(),(char)val_rand()); //Low chance of collisions, so we get quite close to the desired size

  map.insert(23,42);
  assert(map.get(23)==42);

  SaveSimpleHash(map);
  auto newmap = LoadSimpleHash();

  //Ensure that the load worked
  for(int i=0;i<map.keys.size();i++)
    assert(map.keys.at(i)==newmap.keys.at(i));
  for(int i=0;i<map.vals.size();i++)
    assert(map.vals.at(i)==newmap.vals.at(i));  
  for(int i=0;i<map.used.size();i++)
    assert(map.used.at(i)==newmap.used.at(i));    
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

从文件读取/写入 std::unordered_map 的更快方法 的相关文章

随机推荐

  • 使用主机文件的本地主机上的通配符子域[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我正在尝试在 Windows 7 上运行 IIS 7 的开发计算机上设置子域 看起来可以通过编辑hosts文件输入C Windows Sy
  • 重写onDraw()还是draw()?

    我的项目基于 SurfaceView 到目前为止 我已经在 onDraw 中进行了所有渲染 我正在覆盖它 一切似乎都很好 然而 我刚刚更新了我的 SDK 现在它给我一个错误告诉我 可疑的方法调用 可能应该调用 draw 而不是 onDraw
  • JQuery UI:如何使用其命名空间调用小部件函数

    我创建了两个具有相同名称但具有不同命名空间的自定义 JQuery UI 小部件 如下所示 第一个小部件 widget finance dialog this was created in the file jquery finance di
  • webpack 引用不带变量的类型

    running yarn run webpack dev 没问题 但是 yarn run webpack prod 产生这个错误 ERROR in Illegal State referring to a type without a va
  • OpenCV 将 Mat 保存为二进制(1 位深度)TIFF

    假设我们有一个Mat应用OpenCv后Imgproc adaptiveThreshold Mat srcImage Mat binaryImage new Mat Imgproc adaptiveThreshold srcImage bin
  • 将 JSON 转换为 .csv

    我发现有人正在将一些数据下载到 JSON 文件中 我想 我是新手 该文件包含近 600 名足球运动员的数据 这是文件 https raw githubusercontent com llimllib fantasypl stats f944
  • 如何获取当前键盘布局的代码页?

    我的非 Unicode 应用程序需要能够处理 Unicode 键盘输入 WM CHAR 等 从而接收 8 位字符代码 然后在内部将其转换为 Unicode 需要 9x 兼容性 因此不能选择使用大多数 Unicode API 目前 它查看 P
  • iOS FFT 绘制频谱

    我读过这些问题 使用 Apple FFT 和加速框架 https stackoverflow com questions 3398753 using the apple fft and accelerate framework 使用 Acc
  • 选择 Firefox 附加目录中的文件

    为了简单起见 我将基于 XUL 的 Firefox 插件转换为基于 SDK 的版本 我在基于 XUL 的版本中使用的 XPCOM 模块似乎可以工作 但 ci nsIFile 的行为不同 我不知道如何导航到当前位于目录最高层的 smartPr
  • ASP.NET MVC MultiSelectList 的选定值未正确选择

    我知道其他人也问过这个问题 但我对此完全困惑 这将显示未选择任何值的下拉列表 这将显示下拉列表 其中包含我在 Model items 中传递的值 这些值已正确选择 就像我所期望的那样
  • 使用 gdb 调试 C++11 右值引用

    我刚刚注意到我无法调试rvalue参考文献与gdb 7 7 1适当地 void simple int i 当我输入这个简约函数时 我无法获得任何有意义的信息i It s type and value are unknown to gdb s
  • 当我实现自定义 Lint 检测器时如何调试 java 源代码?

    我是一名 Android 开发者 我已经通过实现新的 XXXDetector 和 XXXIssueRegistry 设计了自己的 lint 规则 这是我的源代码片段 我的 XXXIssueRegistry 文件 public class M
  • 关闭 Windows 8 超级按钮栏

    我有一台 Surface Pro 我需要将其 锁定 为一种 Kiosk 模式 我知道 信息亭模式 的更新正在进行中 但是我需要在此之前执行此操作 我在互联网上进行了搜索 但似乎您无法通过滑动来禁用超级按钮栏screen 我找到了禁用触控板的
  • C 静态库的包装

    我有一个用于相机的 C 静态库 现在计划为 Windows 8 开发 C WPF UI 它将使用 C 静态库来捕获视频 音频 我的想法是 C Static 会有 C CLI 包装器 包装器将是托管 Dll C WPF UI 将使用此 Dll
  • 在克隆期间更改内部元素 id

    我正在单击按钮时克隆 DIV 元素 我可以更改正在克隆的 DIV 元素的 ID 值 但是是否可以更改内部元素的 id 在下面的代码中 我更改了 Id selection克隆时 我需要动态更改 id select div div class
  • 使用 Spring RestTemplate 访问 Https Rest 服务

    谁能给我提供一个代码示例来使用 Spring Rest 模板访问通过 HTTPS 保护的其余服务 URL 我有证书 用户名和密码 基本身份验证用于服务器端 我想创建一个可以使用提供的证书 用户名和密码 如果需要 连接到该服务器的客户端 Ke
  • 头部内有多个 RSS 链接标签,标记是否有效?

    在 RSS feed 中包含多个 RSS feed 是否有效 tag 我的意思是 标签如下 etc 我们有一小部分 总共五个 RSS 提要 我们已经拥有了一段时间 但只在头标签中包含了 主要 提要 可以将它们全部包括在内吗 是的 这是完全有
  • jquery UI - 将日期添加到选定日期

    这看起来很简单 但我无法解决它 我真的需要这个 如何通过 SelectedDate 事件将日期添加到选定日期 我需要对 2 个日期选择器执行日期范围限制 一旦用户设置了一个日期选择器 另一个日期选择器只需要允许日期等于第一个日期选择器的所选
  • WHERE 子句中的动态条件

    我有一个存储过程 想知道是否可以建立一个动态的where基于参数的条件 假设我有这个查询 SELECT FROM tbl Users 现在 我有一个名为 username 我想用它来建立一个动态的where条件 通过我的程序可能是 1 个或
  • 从文件读取/写入 std::unordered_map 的更快方法

    我正在与一些非常大的公司合作std unordered maps 数亿个条目 并且需要将它们保存到文件中或从文件中加载它们 我目前执行此操作的方法是迭代映射并一次读取 写入每个键和值对 std unordered map