「如何实现流动式软件发布」线上课堂开课啦,快来报名参与课堂抽奖吧~ 了解详情
写点什么

当小内存遇上大量数据,你该怎么解决这个问题?

2019 年 11 月 30 日

当小内存遇上大量数据,你该怎么解决这个问题?


当你写了一个处理数据的软件,它可能在小样本文件上运行地很好,但一旦加载大量真实数据后,这个软件就会崩溃。


问题在于你没有足够的内存——如果你有 16GB 的RAM,你就无法一次载入 100GB 大小的文件。载入这么大的文件时,操作系统在某个时刻就会耗尽内存,不能分配存储单元,你的程序也就会崩溃。


所以,你该怎样防止这类情况发生?你可以启动一个大数据集群——你所需要做的是:


  • 搞到一个计算机集群。

  • 花一周时间搭建这个集群。

  • 大部分情况下,你需要学习一套全新的 API,重写你所有的代码。


这个代价可能很昂贵,会令人沮丧;幸运的是,大部分情况下,我们不必这样做。


你需要一个简单而容易的解决方案:在单机上处理你的数据,最小化环境搭建开销,尽可能利用你正在使用的代码库。实际上,大部分情况你都可以做到这样,只要使用一些方法即可,有时候这些方法被称为“核外计算”(out-of-core computation)。


本文将介绍如下内容:


  • 你究竟为什么需要 RAM。

  • 处理无法放入内存的数据最简单的方法:花些钱。

  • 处理大量数据的三种基本软件方法:压缩、分块、索引。


之后的文章将会展示如何把这些方法应用到诸如NumPyPandas这样的库中。


你究竟为什么需要 RAM?

在我们开始解释解决方案前,我们要弄清楚该问题是如何产生的。我们的计算机内存(RAM)能让你读写数据,但是你的硬盘也可以读写数据——那为什么计算机还需要 RAM 呢?硬盘比 RAM 更便宜,所以它通常大到能够容纳下你的所有数据,那为什么你的代码不能直接从硬盘读写数据呢?


理论上讲,这也行得通的。但是,即使是最现代化且速度很快的SSD硬盘也比RAM慢太多


  • 从 SSD 上读取数据:大约 1.6 万纳秒

  • 从 RAM 上读取数据:大于 100 纳秒


如果你想要实现快速计算,数据就只能放在 RAM 中,否则你的代码运行时就会慢上 150 倍。


资金方面的解决方案:购买更多的 RAM

没有足够 RAM 时的最简单解决方案就是花钱来解决。你要么购买一台计算机,或者租一台云端的虚拟机(VM:Virtual Machine,这会比大多数笔记本电脑贵得多)。2019 年 11 月,我稍微调研了一下,在价格方面做了一些比较,发现你可以这样:


  • 购买一台 Thinkpad M720 Tower,它有 6 个核和 64GB RAM,价格是 1074 美金。

  • 租用一台云端的 VM,它有 64 个核和 432GB RAM,价格是每小时 3.62 美金。


这只是我稍微调研后发现的数字,再继续调研下去,你会发现更好的方案。


如果花钱购买硬件可以把你的数据读入 RAM,这通常就会是一个最经济的解决方案:毕竟你的时间相当宝贵。但是,有时候,这还不够解决这个问题。


例如,如果你要运行许多数据处理任务,在一段时期内,云计算可能是一个自然能想到的解决方案,但也是一个昂贵的解决方案。曾经在一个工作中,我的软件项目需要的计算开销几乎快用完了我们产品所有的预期收入,包括支付我薪水所需的至关重要的那部分收入。


如果购买/租用更多的 RAM 不足以满足需求或者根本行不通时,下一步就应该考虑如何通过修改软件来减少内存使用了。


技巧 #1:压缩

压缩意味着用一种不同的表达形式表示你的数据,这种形式能占用更少内存。有两种方式来压缩:


  • 无损压缩:存储的数据包含的信息和原始数据包含的信息完全相同。

  • 有损压缩:存储的数据丢失了一些原始数据里的细节信息,但是这种信息丢失理想情况下不会对计算结果造成什么影响。


我想说明的是,我不是在谈论使用 ZIP 或者 gzip 工具来压缩文件,因为这些工具通常是对硬盘上的文件进行压缩的。为了处理 ZIP 压缩过的文件,你通常需要把这个文件载入内存中再进行解压缩为原始文件大小,所以这其实对内存节省没有什么帮助。


你需要的是内存中的数据压缩表示形式。


例如,比如说你的数据有两个值,一共也只会有两个值:“AVAILABLE”(代表可能取到的值)和“UNAVAILABLE”(代表不可能取到的值)。我们可以不必将其存为 10 个或更多字节的字符串,你可以将其存为一个布尔值,用 True 或者 False 表示,这样你就可以只用 1 个字节来表示了。你甚至可以继续压缩至 1 位来表示布尔值,这样就继续压缩到了 1 个字节时的 1/8 大小。


技巧 #2:分块,每次只加载所有数据里的某一块

当你需要处理所有数据,而又无需把所有数据同时载入内存时,分块是很有用的。你可以把数据按块载入内存,每次计算一块的数据(或者正如我们要在今后一篇文章里想讨论的,可以多块并行处理)。


例如,比如说,你想搜索一本书里的最长单词。你可以一次性将所有数据载入内存:


largest_word = ""for word in book.get_text().split():    if len(word) > len(largest_word):        largest_word = word
复制代码


但是在我们的例子中,这本书太大而不能完全载入内存,这时候你就可以一页一页地载入这本书。


largest_word = ""for page in book.iterpages():    for word in page.get_text().split():        if len(word) > len(largest_word):            largest_word = word
复制代码


这样你使用的内存就大大减少了,因为你一次只需要把这本书的一页载入内存,而最后得到的结果仍然是正确的。


技巧 #3:当你需要数据的一个子集时,索引会很有用

当你需要数据的一个子集时,索引会很有用,使用索引你可以在不同时刻加载数据的不同子集。


你也可以用分块解决这个问题:每次加载所有的数据,过滤掉你不想要的数据。但这会很慢,因为你加载了很多不相关的数据。


如果你只需要部分数据,不要使用分块,最好使用索引,它可以告诉你到哪里能找出你关心的那部分数据。


想象一下,你只想阅读书本中关于土豚的章节。如果你运用分块技术,你得载入整本书,一页一页的载入,每页地搜寻土豚——但这要花很长时间才能完成。


或者说,你可以直接阅读这本书的末尾部分,也就是书本的索引部分,然后找到“土豚”的索引项。它可能会告诉你在第 7、19 页以及 120-123 页可以读到相关内容。所以,现在你可以只读那几页,这样就快多了。


这样很有效,因为索引比整本书占用的空间要小很多,所以把索引载入内存找出相关内容所在就会更容易。


最简单的索引技巧

最简单也最常用的实现索引的方法就是在目录里给文件恰当命名:


mydata/    2019-Jan.csv    2019-Feb.csv    2019-Mar.csv    2019-Apr.csv    ...
复制代码


如果你想要 2019 年 3 月数据,你就只需要加载 2019-Mar.csv 这个文件——而不用加载 2 月、7 月或者其他任何月份的数据。


下一步:应用这些技巧

RAM 不够用,最简单的解决方法就是花钱买更多的 RAM。但是,如果这个方案无法实现或者仍然不够用时,你就需要使用压缩、分块或者索引来解决。


这些方法也出现在其他许多不同的软件包和工具中。即使是大数据系统也是建立在这些方法之上的:例如使用多个计算机来处理分块数据。


在接下来的文章里,我会给你展示如何使用具体的库和工具(NumPy、Pandas,或者用 ZIP 工具压缩文件)来应用这些方法。如果在我写完这些文章时,你就想阅读到它们,请在下方表格里注册我的通讯简报(newsletter)。


原文链接:


When your data doesn’t fit in memory: the basic techniques


2019 年 11 月 30 日 21:534042

评论

发布
暂无评论
发现更多内容

精纯还是混乱?职场十二箴言——重读“成为乔布斯”的思考(一)

石君

职场 乔布斯 成功学

删掉最后一句话

池建强

心理学 情绪控制

小议RPA

一品凡心

人工智能 RPA 自动化

做小池塘里的大鱼,还是大池塘里的小鱼?这是个问题。

霍太稳@极客邦科技

创业 团队管理 目标管理

Kubernetes 容器运行时演进

倪朋飞

Kubernetes 容器 云原生

走出舒适区最好办法别走了,扩大它

乐少

如何做一名失败的安全架构师

石君

架构 安全架构师 安全评估

是时候要说再见了,春风十里,不如邮你!

乐少

喔,明白了,成功也是一种苦难

霍太稳@极客邦科技

创业 身心健康 企业文化 个人成长 心理

探究vscode debug流程,解决无法运行go程序的问题

simpleapples

golang vscode

Kubernetes中的CI/CD

倪朋飞

Kubernetes DevOps 微服务

我如何用 Python 给 Github 的 README.md 做一个访客统计功能

遇见

Python GitHub 开源 badge open-source

精纯还是混乱?职场十二箴言——重读“成为乔布斯”的思考(二)

石君

创业 乔布斯 成为乔布斯

我的第一个千万阅读量

彭宏豪95

创作 生活 写作

GitHub知错就改,是个好同志

遇见

GitHub

程序员职业鉴赏

陆陆通通

程序员 加班 职业病 鄙视链

任正非管理哲学中的三个常识和三种科学

霍太稳@极客邦科技

创业 团队管理 华为

《小狗钱钱》——财富离我们并不遥远

尹晓铁

读书笔记 投资 成长 思维方式

【深度】为您解读东西方艺术教育的专业设置差异对比~

默聲

特别评论:甲骨文的傲气

张晓楠

云计算 互联网巨头 企业文化

【SpringBoot】掌握这两个属性,你的测试类可以启动的更快些

遇见

Java Spring Boot Unit Test

翻译: Effective Go (2)

申屠鹏会

go 翻译

HTTP Methods和RESTful API的设计

孙苏勇

架构 系统设计 RESTful 接口

分布式数据库是无用的屠龙术吗?

海边的Ivan

企业架构 分布式数据库 业务中台

用你喜欢的 emoji 作为页面的 favicon 吧 🎉

遇见

CSS html favicon emoji

简单到不可能失败 —— 《微习惯》

零和幺

读书笔记

Elasticsearch文档版本冲突原理与解决

Skysper

elasticsearch 乐观锁 悲观锁

dubbo-go 中如何实现远程配置管理

joe

golang Apache 开源 微服务架构 dubbo

做产品的同理心

孙苏勇

产品 产品经理 产品设计

无代码开发

Fenng

如何解决 Kubernetes 的 DNS 延迟问题

倪朋飞

Kubernetes 微服务 云原生

当小内存遇上大量数据,你该怎么解决这个问题?-InfoQ