问题背景:
在开发股票自动化交易软件过程中, 我们需要将股票的 tick 数据 (每 3s 一笔) 存储下来, 供我们的股票交易策略部分使用这种情况下首先想到使用 MySql 这种关系型数据库进行存储, 但是我们的股票自动化交易软件对速度的要求极高, 可能相差几十毫秒就是赚钱与亏钱的区别, 而 MySql 这种传统数据库的插入与查询是很慢的, 无法满足我们的要求当然我们也可以选择既满足存储数据的要求又能达到我们需要的速度要求的 Redis 这种内存数据库, 但考虑到我们交易软件是日内交易, 不需要股票数据持久化到硬盘上, 而且经过大概估算, 我们服务器的内存远大于股票每日的数据量, 所以考虑将股票数据直接保存在内存中, 软件运行时直接对内存中的股票数据进行使用即可
问题描述:
我们使用 map 保存股票的数据, map 的 key 是股票代码 (string 类型),value 是该股票的行情数据(vector 类型), 然后建立行情类 MarketData(单例模式) 将 map 封装起来, 并实现线程安全的插入数据和取数据的方法, 这样就实现了一个极其简单的内存数据库在 MarketData 建立后需要初始化股票数据, 使用 vector 的 reserve 方法为每支股票预先分配足够存储一天数据的内存空间代码如下:
- /// 初始化 mapStockData, 初始化后该 map 节点位置不会再变化, 避免多线程安全问题
- /// 为 vector 预先分配内存, 避免在程序运行中迭代器失效
- void MarketData::InitStockList(const std::set<std::string> stockcodes) {
- if (stockcodes.empty()) {
- cerr << "未订阅任何股票, 请查看 ini 配置文件!" << endl;
- return;
- }
- // 为 vector 预先分配内存, 避免在程序运行中迭代器失效
- for (auto stockcode : stockcodes) {
- mapStockData_[stockcode].market.reserve(6000); //Market 数据, 每 3 秒一个数据
- mapStockData_[stockcode].transaction.reserve(200000); //transaction 数据
- mapStockData_[stockcode].orderqueue.reserve(12000); //orderqueue 数据
- mapStockData_[stockcode].order.reserve(200000); //order 数据
- }
- }
上述代码在订阅股票数量较少 (小于 10 支) 的时候一直工作都很正常, 昨天我们添加新的股票交易策略, 新的策略需要订阅更多的股票(大概 100 支股票), 这时候这段代码就出现了 std::bad_alloc 错误, 即内存申请失败
问题分析:
既然是内存分配错误, 那我们就计算一下内存占用, 经过计算, 每支股票每天的数据量大约 48M, 一百支股票的占用就是 4800M, 总的数据量不到 5 个 G, 但我们服务器是 64G 内存, 应该不存在内存不够用的情况后来又推测是不是 vector 需要申请连续的内存, 而恰好服务器内存不存在这么多连续的内存, 细想一下这种可能性也不大
没啥头绪就先把这个问题放下去吃饭了, 在去食堂的路上, 突然想到我们项目中使用的是 x86 模式, 即我们的程序是 32 位的程序, 虽然服务器是 64 位, 但是 32 位的程序的寻址空间最大只有 4G, 所以程序是不会寻址大于 4G 的内存地址的, 所以对我们程序而言, 服务器 64G 的物理内存能用的只有 4G, 而且这 4G 也不是进程独占的, 对于 Linux 系统而言, 内核空间占用 1G,32 位程序最大的可利用内存只有 3G, 对于 Windows 系统而言, 系统空间占用 2G,32 位程序最大可利用内存只有 2G(堆内存 1.6G 左右)2G 内存只能存储大约 40 支股票, 如果再考虑到内存碎片对 vector 请求连续内存的影响, 可能只能存储 20 多支股票的数据, 所以在我们订阅了 100 支股票的时候, 就会出现内存分配失败的错误
问题解决:
查阅资料得知: Windows 操作系统有一个 boot.ini 开关, 可以为应用程序提供访问 3GB 的进程地址空间的权限, 从而将内核模式地址空间限定为 1 GB, 从而用户模式地址空间可用 3GB 这种方法虽然可以将可用内存增加 1GB, 但对我们上百支股票数据而言仍旧是杯水车薪
最好的办法就是将应用程序按照 x64 模式重新编译, 编译出来的程序便是 64 位程序, 地址空间远远大于 32 位程序, 此时, 对程序分配内存的限制就只有物理内存大小, 但我们的物理内存是完全足够的, 更改了编译参数之后, 上述内存申请失败的错误便不再出现
总结:
当前, 64 位 CPU 和 64 位操作系统已经完全普及了, 基于这些平台开发应用程序的时候, 如果没有兼容 32 位系统的需求, 应该优先考虑开发 64 位的应用程序, 对于需要大量内存的程序尤为重要
来源: https://juejin.im/entry/5aba76556fb9a028dc410c95