12.03 内存不足、钱包不鼓怎么办?三种技巧助你摆脱内存使用困境

选自pythonspeed

作者:Itamar Turner-Trauring

机器之心编译

参与:高璇、杜伟


在编写软件的过程中,开发人员需要处理大量的数据,因而常常会遇到内存不足的情况。虽然我们都知道,解决内存不足的简单粗暴方法就是往里砸钱,但有时自己的经济实力不允许这么任性。本文作者提出了三种技巧:数据压缩、分块和索引,以此从软件本身解决了内存不足的窘境。

在你编写用于处理数据的软件时,当只用一个小的示例文件做测试,软件就可以很好地运行。但是,当加载实际数据时,程序立刻崩溃。

原因是没有足够的内存——如果你只有 16GB 的 RAM,则无法加载 100GB 的文件。有时操作系统耗尽内存,导致内存无法分配,程序就只能崩溃。

所以,你可以做什么?要启动一个大数据集,需要做的是:

获取计算机集;花一周的时间进行设置;在很多情况下,学习全新的 API 并重写所有代码。

这样做简直让人心力交瘁;幸运的是,在许多情况下,你也不必这么做。

你只需一种简单易用的解决方案:在一台计算机上用最少的设置即可处理数据,并且尽可能使用你已经在用的库。而且在很多时候,你可以使用一种被称为「核外计算」的技术来实现。

在本文中,作者将介绍:

为什么需要 RAM;处理内存中不适配数据的最简单方法:花钱;处理过多数据的三种基本软件使用技巧:压缩、组块和索引。

文章接下来将展示如何将这些技术应用于 NumPy 和 Pandas 等特定库。

为什么需要 RAM?

在继续讨论解决方案之前,让我们先阐明问题出现的原因。你可以使用计算机的内存(RAM)读取和写入数据,但是硬盘驱动器也可以读取和写入数据——那么为什么计算机需要 RAM 呢?而且磁盘比 RAM 便宜,它通常可以包含所有数据,那么为什么代码不能改为仅从磁盘读取和写入数据呢?

从理论上讲,这是可行的。但即使是更新、更快的固态硬盘(SSD)也比 RAM 慢得多:

从 SSD 读取:约 16,000 纳秒从 RAM 读取:约 100 纳秒

如果想要快速计算,数据必须匹配 RAM,否则代码运行速度可能会慢 150 倍。

解决方案:更多 RAM

解决内存不足的最简单粗暴的方法就是往里砸钱。你可以买台计算机或者租用云虚拟机(VM),后者的内存比大多数笔记本电脑都要多。以 2019 年 11 月市场价为例,你可以:

购买具有 6 核和 64GB RAM 的 Thinkpad M720 Tower,价格为 1074 美元;租用具有 64 核和 432GB RAM 的云虚拟机,每小时 3.62 美元。

这些只是我简单搜索得到的价格,如果进行一些深入研究,则可能需要更全的价格和产品。

如果花钱就能解决你的内存问题,那通常是最便宜的解决方案:毕竟时间就是金钱。但是,有时花钱也解决不了问题。

例如,如果你要处理许多数据任务,在一段时间内,云计算可能是很顺手的解决方案,但却也是昂贵的解决方案。在一项研究工作中,我所使用软件的计算成本将耗尽该产品的所有预计收入,包括我的薪水在内,这样代价就太大了。

如果购买/租用更多的 RAM 是不够或不现实的,下一步就是弄清楚如何通过更改软件来减少内存使用。

技巧 I:数据压缩

数据压缩意味着使用更少的内存来表示数据。压缩有两种形式:

无损:存储的数据与原始数据信息完全相同;有损:存储的数据丢失了原始数据中的某些细节,但在理想情况下不会对计算结果产生太大影响。

请注意,我说的不是 ZIP 或 gzip 文件,因为这些文件通常涉及磁盘压缩。要处理 ZIP 文件中的数据,首先需要解压缩到 RAM 中。因此,这无济于事。

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

例如,假设你的数据有两个值,并且将永远只有这两个值:"AVAILABLE"和"UNAVAILABLE"。你可以将它们存储为布尔值,True 或 False,这样可以将其存储为 1 个字节,而不是每个条目都要占用 10 个甚至更多字节。你甚至可以将表示降低到表示布尔值所需的单个位,从而将内存使用量减少到原来的八分之一。

技巧 II:分块,一次加载一个数据块

当你需要处理所有数据但不需要一次将所有数据加载到内存中时,分块很有用。你可以将数据分块加载到内存中,一次只处理一个数据块(或者按照后文提到的,并行处理多个块)。

例如,假设你要查找一本书中最长的单词。你可以一次将所有数据加载到内存中:

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

需要使用的内存要少得多,因为在任何给定的时间内只有一页书在内存中只有一页书在内存中。最后,你还是会得到相同的答案。


技巧 III:在你需要数据子集时进行索引

当你只需要使用数据的一个子集,并且希望在不同的时间加载数据的不同子集时,索引很有用。

你可以通过分块解决这种情况:每次加载所有数据,然后过滤掉不需要的数据。但这很慢,因为需要加载许多不相关的数据。

如果只需要部分数据,则最好使用索引,而不是分块,索引最好使用数据摘要,它可以告诉你在哪里找到所需的数据。

想象一下,您只想阅读本书中有关土豚(ardarvarks)的部分。如果使用分块,则需要逐页阅读整本书,以查找 ardarvarks,但这将花费相当长的时间。

或者,你可以利用书的索引,找到「Aardvarks」的条目。它可能会告诉你阅读第 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。但是,如果这行不通或者不现实时,就可以使用压缩、分块或索引等技巧。

这些技巧可以应用在许多不同的软件包和工具中。即使大数据系统也基于这些技巧构建:例如,使用多台计算机来处理数据块。

原文链接:https://pythonspeed.com/articles/data-doesnt-fit-in-memory/