目录
本主题提供关于 MATLAB® 在处理变量时如何分配内存的信息。这些信息,就像关于 MATLAB 内部如何处理数据的任何信息一样,在以后的版本中可能会变更。
为数组分配内存
当将数值或字符数组分配给变量时,MATLAB 会分配一个连续的内存块,并将数组数据存储在该内存块中。MATLAB 还将有关数组数据的信息(如它的类和维度)存储在一个单独的小内存块标头。对于多数数组,存储标头所需的内存可忽略不计。然而,将大数据集存储在数量较少的大数组中比存储在数量较大的小数组中可能更理想。这是因为较少的数组需要较少的数组标头。
如果向现有数组中添加新元素,MATLAB 会按照使内存存储保持连续的方式扩展该数组。这通常需要查找新的足以容纳扩展后的数组的内存块。MATLAB 随后将该数组的内容从其原始位置复制到内存中这一新块中,向该块中的数组添加新元素,并释放原始数组在内存中的位置。
如果从现有数组中删除元素,MATLAB 会通过清除已删除的元素,然后使其在原始内存位置变得紧凑来使内存存储连续。
复制数组
当将数组分配给第二个变量时(例如,当您执行 B = A 时),MATLAB 不会立即分配新内存。此时,它会创建数组引用副本。只要不修改 A 和 B 引用的内存块的内容,就不需要存储多个数据副本。但是,如果使用 A 或 B 修改内存块的任何元素,MATLAB 就会分配新内存,将数据复制到其中,然后修改所创建的副本。
在 Windows® 系统上,memory 函数可用于检查内存详细信息。要了解复制数组如何影响 Windows 系统的内存使用量,请在当前文件夹的一个文件中创建函数 memUsed。该函数调用 memory,以 MB 为单位返回MATLAB 进程使用的内存量。
function y = memUsed
usr = memory;
y = usr.MemUsedMATLAB/1e6;
调用 memUsed 以显示当前内存使用量。
format shortG
memUsed
ans =
3966.1
创建一个 2000×2000 数值数组,并观察内存使用量的变化。该数组使用大约 32 MB 的内存。
A = magic(2000);
memUsed
ans =
3998.1
在 B 中制作 A 的副本。由于不需要使用数组数据的两个副本,MATLAB 仅创建数组引用的一个副本。这不需要额外增加大量内存。
B = A;
memUsed
ans =
3998.1
现在通过删除 B 的行数的一半来修改它。由于 A 和 B 不再指向同一数据,MATLAB 必须为 B 分配一个单独的内存块。结果,MATLAB 进程使用的内存量增加了 B 的大小,约为 16 MB(即 A 所需的 32 MB 的一半)。
B(1001:2000,:) = [];
memUsed
ans =
4014.1
函数参数
MATLAB 处理函数调用中传递的参数的方式与处理复制的数组的方式相同。将变量传递给函数时,实际是传递对该变量表示的数据的引用。只要被调用的函数未修改数据,调用函数或脚本中的变量和被调用函数中的变量就指向内存中的同一位置。如果被调用函数修改输入数据的值,则 MATLAB 将在内存中的新位置创建原始变量的副本,用修改后的值更新该副本,并将被调用函数中的输入参数指向此新位置。
例如,假设有函数 myfun,它修改传递给它的数组的值。MATLAB 在内存中的新位置生成 A 的副本,将变量 X 设置为对此副本的引用,然后将 X 的一行设置为零。A 引用的数组保持不变。
A = magic(5);
myfun(A)
function myfun(X)
X(4,:) = 0;
disp(X)
end
如果调用函数或脚本需要其传递给 myfun
的数组的修改后的值,您需要以被调用函数的输出形式返回更新的数组。
数据类型和内存
MATLAB 的各数据类型的内存要求不同。通过了解 MATLAB 如何处理各种数据类型,有助于减少代码使用的内存量。
数值数组
MATLAB 分别对 8 位、16 位、32 位和 64 位有符号和无符号整数分配 1、2、4 或 8 个字节。它以双精度 (double) 或单精度 (single) 格式表示浮点数。由于 MATLAB 使用 4 个字节存储 single 类型的数值,因此与使用 8 位的 double 类型的数值相比,前者需要的内存更少。但是,由于它们是使用较少的位存储的,因此 single 类型的数值所呈现的精度要低于 double 类型的数值。在 MATLAB 中,double 是默认的数值数据类型,它可为大多数计算任务提供足够的精度。有关详细信息,请参阅浮点数。
结构体和元胞数组
数值数组必须存储在连续内存块中,但结构体和元胞数组可以存储在不连续内存块中。对于结构体和元胞数组,MATLAB 不仅为数组创建一个标头,还为结构体的每个字段及元胞数组的每个元胞创建一个标头。因此,存储结构体或元胞数组所需的内存量不仅取决于其包含的数据量,还取决于其构造方式。
例如,假设有标量结构体 S1,它包含字段 R、G 和 B,其中每个字段包含一个 100×50 数组。S1 需要一个标头来描述总体结构,一个标头用于每个唯一字段名称,一个标头用于每个字段。这使得整个结构体总共有七个标头。
S1.R = zeros(100,50);
S1.G = zeros(100,50);
S1.B = zeros(100,50);
另一方面,假设有一个 100×50 结构体数组 S2,其中每个元素都有标量字段 R、G 和 B。在本例中,S2 需要一个标头来描述总体结构,一个标头用于每个唯一字段名称,以及一个标头用于 5,000 个元素的每个字段,从而使整个结构体数组总共有 15,004 个数组标头。
for i = 1:100
for j=1:50
S2(i,j).R = 0;
S2(i,j).G = 0;
S2(i,j).B = 0;
end
end
使用 whos 函数比较在 64 位系统上分配给 S1 和 S2 的内存量。尽管 S1 和 S2 包含相同的数据,但 S1 使用的内存明显更少。
whos S1 S2
Name Size Bytes Class Attributes
S1 1x1 120504 struct
S2 100x50 1680192 struct
复数数组
MATLAB 使用复数的交错存储表示,其中实部和虚部一起存储在一个连续内存块中。如果创建复数数组的副本,然后仅修改该数组的实部或虚部,MATLAB 会创建一个同时包含实部和虚部的数组。
稀疏矩阵
使用稀疏存储来存储非零元素很少的矩阵是一种很好的做法。当一个满矩阵有少量非零元素时,将矩阵转换为稀疏存储通常会改善内存使用量和代码执行时间。可以使用 sparse 函数将满矩阵转换为稀疏存储。
例如,假设矩阵 A
是 1000×1000 满存储单位矩阵。将 B
创建为 A
的稀疏副本。在稀疏存储中,相同的数据使用的内存量要少得多。
A = eye(1000);
B = sparse(A);
whos A B
Name Size Bytes Class Attributes
A 1000x1000 8000000 double
B 1000x1000 24008 double sparse
使用大数据集
当处理大型数据集时,反复调整数组大小可能导致程序耗尽内存。如果您扩展数组使其超过其原始位置的可用连续内存,MATLAB 必须创建该数组的副本,并将副本移至具有足够空间的内存块中。在此过程中,内存中有原始数组的两个副本。这会暂时使数组所需的内存量翻倍,并增加您的程序出现内存不足的风险。