Main Content

本页翻译不是最新的。点击此处可查看最新英文版本。

解决“内存不足”错误

问题

当您的代码处理大量数据或不能高效使用内存时,MATLAB® 可能会因数组大小不合理或内存不足而生成错误。MATLAB 具有内置防护机制,可防止创建过大的数组。例如,以下代码会导致错误,因为 MATLAB 无法创建包含请求的元素数的数组。

A = rand(1e9);
Requested array exceeds the maximum possible variable size.

默认情况下,MATLAB 可使用 100% 的计算机 RAM(不包括虚拟内存)来为数组分配内存,如果数组大小超过该阈值,则 MATLAB 生成错误。例如,以下代码尝试创建一个大小超过最大数组大小限制的数组。

B = rand(1e6);
Requested 1000000x1000000 (7450.6GB) array exceeds maximum array size preference (63.7GB). This might cause MATLAB to become
unresponsive.

如果在 MATLAB 工作区预设项中关闭数组大小限制,则尝试创建一个不合理的大型数组可能导致 MATLAB 耗尽内存,或可能由于内存分页过多(即在 RAM 和磁盘之间移动内存页)导致 MATLAB 甚至您的计算机没有响应。

B = rand(1e6);
Out of memory.

可能的解决方案

无论您因何种原因遇到内存限制,MATLAB 都提供了相应的解决方案,您可以根据自己的情况和目标进行选择。例如,您可以改进代码利用内存的方式,利用专用数据结构体(如数据存储和 tall 数组),利用计算集群中的池化资源,或调整您的设置和预设项。

注意

此处介绍的解决方案是针对 MATLAB 的。要优化系统范围的内存性能,请考虑为您的计算机添加更多物理内存 (RAM) 或在操作系统级别进行调整。

在不再需要时清除变量

养成一个好的习惯,及时清除不再需要的变量。要从内存中清除项目,请使用 clear 函数。

更新前更新后
A = rand(1e4);
disp(max(A,[],"all"))
B = rand(1e4);
A = rand(1e4);
disp(max(A,[],"all"))
clear A
B = rand(1e4);

有关详细信息,请参阅高效使用内存的策略

使用适当的数据存储

各个 MATLAB 数据类型的内存要求不同。通过使用适当的数据类型和存储,您也许能够减少代码使用的内存量。有关本节中解决方案的详细信息,请参阅高效使用内存的策略

使用相应的数值类.  您应使用的数值类取决于您的预期操作。在 MATLAB 中,double 是默认的数值数据类型,它可为大多数计算任务提供足够的精度:

  • 如果要执行复杂的数学运算,如线性代数,请使用双精度 (double) 或单精度 (single) 格式的浮点数。类型为 single 的数值比类型为 double 的数值需要更少的内存,但表示的精度也更低。

  • 如果您只需执行简单的算术运算并将原始数据表示为整数,则可以在 MATLAB 中使用整数类。

类(数据类型)字节支持的运算
single4绝大多数的数学运算
double8所有数学运算
logical1逻辑/条件运算
int8, uint81算术和某些简单函数
int16, uint162算术和某些简单函数
int32, uint324算术和某些简单函数
int64, uint648算术和某些简单函数

减少存储数据时的开销量.  创建数值或字符数组时,MATLAB 会分配一块内存来存储数组数据。MATLAB 还将有关数组数据的信息(如它的类和维度)存储在一个称为标头的单独小内存块中。由于简单的数值和字符数组开销最小,因此尽可能使用它们。只有当数据过于复杂而无法存储在简单数组中时,再使用其他数据结构体。

对于结构体和元胞数组,MATLAB 不仅为数组创建一个标头,还为结构体的每个字段及元胞数组的每个元胞创建一个标头。因此,存储结构体或元胞数组所需的内存量不仅取决于其包含的数据量,还取决于其构造方式。因此,应避免使用包含许多小元素的元胞数组或包含内容极少的很多字段的结构体,因为它们开销很大。

更新前更新后
% S has 15,000 fields (3 fields per array element)
for i = 1:100
    for j = 1:50
        S(i,j).R = 0;  % Each field contains a numeric scalar
        S(i,j).G = 0;
        S(i,j).B = 0;
    end
end
% S has 3 fields 
S.R = zeros(100,50);  % Each field contains a numeric array
S.G = zeros(100,50);
S.B = zeros(100,50);

尽可能使数组稀疏.  使用稀疏存储来存储非零元素很少的矩阵是一种很好的做法。当一个满矩阵有少量非零元素时,将矩阵转换为稀疏存储通常会改善内存使用量和代码执行时间。MATLAB 拥有几个支持稀疏存储的函数。例如,可以使用 speye 函数创建稀疏单位矩阵。

更新前更新后
I = eye(1000);
I = speye(1000);

使用适当的 MATLAB 类导入数据.  当使用 fread 读取二进制文件中的数据时,常见的错误是,仅指定该文件中数据的类,而不指定 MATLAB 当文件位于工作区中时使用的数据的类。如果未指定内存中数据的类,即使您读取 8 位值,MATLAB 也会使用 double 类型。

更新前更新后
fileID = fopen("large_file_of_uint8s.bin","r"); 
A = fread(fileID,1e3,"uint8"); 
fileID = fopen("large_file_of_uint8s.bin","r"); 
A = fread(fileID,1e3,"uint8=>uint8"); 

避免不必要的数据副本

为了提高内存使用率和执行速率,请确保您的代码不会导致不必要的数据副本。有关本节中解决方案的详细信息,请参阅避免不必要的数据副本高效使用内存的策略

避免创建临时数组.  避免创建不必要的临时数组。例如,不要创建一个全零数组作为临时变量,然后将该变量传递给函数,而应使用一个命令来完成这两项操作。

更新前更新后
A = zeros(1e6,1);
As = single(A);
As = zeros(1e6,1,"single");

预分配内存.  当您处理大型数据集时,反复调整数组大小可能导致程序耗尽内存。如果您扩展数组使其超过其原始位置的可用连续内存,MATLAB 必须创建该数组的副本,并将副本移至具有足够空间的内存块中。在此过程中,内存中存在原始数组的两个副本。您可以通过预分配数组所需的最大空间量来改善内存使用量和代码执行时间。有关详细信息,请参阅预分配

更新前更新后
x = 0;
for k = 2:1000000
   x(k) = x(k-1) + 5;
end
x = zeros(1,1000000);
for k = 2:1000000
   x(k) = x(k-1) + 5;
end

使用嵌套函数减少传递的参数.  调用函数时,如果函数修改变量的值,则 MATLAB 通常会在调用方的工作区中创建该变量的临时副本。虽然 MATLAB 会应用各种方法来避免生成不必要的副本,但有时生成输入变量的临时副本是无可避免的。

在函数调用中避免临时副本的一种方法是使用嵌套函数。嵌套函数共享所有外部函数的工作区,因此您不需要在函数调用中传递变量的副本。有关详细信息,请参阅嵌套函数

仅加载需要的数据

解决内存问题的一种方法是只将大型数据集中解决问题所需的数据导入到 MATLAB 中。从数据库等源中导入时数据集大小通常不是问题,因为您可以显式搜索匹配查询的元素。但在加载大型简单文本或二进制文件时这是个常见问题。

datastore 函数允许您以增量方式处理大型数据集。在您需要一次只将数据集的某些部分加载到内存中时,数据存储很有帮助。

要创建数据存储,请提供文件名,或包含一系列具有相似格式的文件的目录。例如,对于单个文件,使用以下方式。

ds = datastore("path/to/file.csv");
或者对于一个文件夹中的一系列文件,使用以下方式。
ds = datastore("path/to/folder/");
您也可以使用通配符 * 来选择特定类型的所有文件。
ds = datastore("path/to/*.csv");
数据存储支持多种文件格式(表格数据、图像、电子表格等)。有关详细信息,请参阅Select Datastore for File Format or Application

除了数据存储之外,MATLAB 还提供其他几个函数来加载部分文件。下表按所处理的文件类型摘要显示了这些函数。

文件类型部分加载
MAT 文件

通过对使用 matfile 函数创建的对象进行索引来加载部分变量。有关详细信息,请参阅在 MAT 文件中保存和加载部分变量

文本

使用 textscan 函数可通过仅读取选定的列和行来访问大型文本文件的一部分。如果您使用 textscan 指定行数或重复格式数字,MATLAB 会提前计算所需的确切内存量。

二进制

您可以使用低级别二进制文件 I/O 函数(例如 fread)访问具有已知格式的任何文件的一部分。对于未知格式的二进制文件,请尝试通过 memmapfile 函数使用内存映射。

图像、音频、视频和 HDF

许多 MATLAB 函数都支持从这些类型的文件中加载数据,使您可以选择读取部分数据。有关详细信息,请参阅支持的导入和导出的文件格式中列出的函数参考页。

使用 tall 数组

Tall 数组帮助您处理太大而无法放入内存的数据集。MATLAB 一次处理一小块数据,并在后台自动执行所有的数据分块和处理。您可以通过两种主要方式使用 tall 数组:

  • 如果大型数组可放入内存,但在尝试执行计算时内存不足,则您可以将该数组转换为 tall 数组。

    t = tall(A);
    如果大型数组可放入内存,但这些数组消耗了太多内存而无法在计算过程中容纳数据副本,则您可以使用这种方法进行处理。例如,如果您有 8 GB 内存和一个 5 GB 矩阵,将该矩阵转换为 tall 数组可让您在不耗尽内存的情况下对矩阵执行计算。有关此用法的示例,请参阅 tall

  • 如果您有基于文件或文件夹的数据,您可以创建一个数据存储,然后基于该数据存储创建一个 tall 数组。

    ds = datastore("path/to/file.csv");
    t = tall(ds);
    这种方法可让您利用 MATLAB 中 tall 数组的全部功能。数据可以有任意数量的行,并且 MATLAB 不会耗尽内存。由于 datastore 同时支持本地和远程数据位置,因此您处理的数据不需要位于您用于分析这些数据的计算机上。有关详细信息,请参阅处理远程数据

要了解有关 tall 数组的更多信息,请参阅使用 tall 数组处理无法放入内存的数据

使用多台计算机的内存

如果您有计算机集群,则可以使用分布式数组(要求 Parallel Computing Toolbox™),利用集群中所有计算机的总内存来执行计算。根据您的数据能否放入内存,有不同的对应方式在并行池的工作进程之间对数据进行分区。有关详细信息,请参阅Distributing Arrays to Parallel Workers (Parallel Computing Toolbox)

调整设置和预设项

一般情况下,重写代码是提高内存性能最有效的方法。但是,如果您无法对代码进行更改,以下解决方案可能会帮助您提供所需的内存量。

不带 Java 虚拟机启动 MATLAB,或减小 Java 堆大小.  如果不带 Java® 虚拟机 (JVM™) 软件启动 MATLAB,或减小 Java 堆大小,则可以增大可用的工作区内存。要在不使用 JVM 的情况下启动 MATLAB,请使用命令行选项 -nojvm。有关如何减小 Java 堆大小的信息,请参阅 Java 堆内存预设

使用 -nojvm 会带来一定的损失,因为您会失去一些依赖于 JVM 的功能,例如桌面工具和图形。启动 MATLAB 时指定 -nodesktop 选项并不会节省大量内存。

调整数组大小限制.  如果您遇到数组大小超过最大数组大小预设项的错误,可以在 MATLAB 中调整此数组大小限制。有关调整数组大小限制的信息,请参阅工作区和变量预设项。仅当要创建的数组超过当前最大数组大小限制但又不是太大而无法放入内存时,此解决方案才有帮助。即使您关闭数组大小限制,尝试创建一个不合理的大型数组也可能导致 MATLAB 耗尽内容,或由于内存分页过多而导致 MATLAB 甚至您的计算机没有响应。

另请参阅

| | |

相关主题