如何使用非标量大小的变量预分配 table?

How to pre-allocate a table with non-scalar sized variables?

出于各种原因,当我遇到以下挑战时,我正在尝试使用 tables 作为常规数值数组的替代品:如何(预)分配非 table标量变量?

给定一个循环:

function A = myfun(...)
N = large number
A = zeros(N,4);

for i = 1:N
   do stuff
   A(i,:) = [scalar, vector];
end

我想 return 一个带有命名变量的 table。

我可以简单地重写它说:

function T = myfun2(...)
N = large number
A = zeros(N,4);

for i = 1:N
   do stuff
   A(i,:) = [scalar, vector];
end
T = table(A(:,1), A(:,2:end),'VariableNames',{'scalar','vector'});

这显然会产生格式为 table 的

T =

  N×2 table

    scalar      vector   
    ______    ___________

      0       0    0    0
      0       0    0    0
      0       0    0    0
     ...          ...

现在,如果我想预分配输出 table 并在每次迭代时更新它,我会尝试以下方法:

function T = myfun3(...)
N = large number
T = table('Size',[N,2],...
       'VariableTypes',{'double','double'},...
       'VariableNames',{'scalar', 'vector'});

for i = 1:N
   do stuff
   T(i,:) = {scalar, vector};
end

myfun3的问题是T的格式是:

T =

  N×2 table

    scalar    vector
    ______    ______

      0         0   
      0         0   
      0         0   

很明显变量 'vector' 现在是标量而不是 array/vector。从 table 文档中读到 'size' 类型的预分配似乎不能接受数组大小?

Q1:如何使用非标量变量预分配 table

Q2:如果 myfun2 中的 A 很大,是开销不好还是这是一个 acceptable 解决方案?

我担心索引 into/out-of 和 table 的额外开销与数值数组相比非常大,会对性能代码产生不利影响。

======= 编辑 =======

我联系了 MathWorks,他们确认从 MATLAB R2019b 开始,无法使用 size 参数实现 Q1。

您可以在 for 循环之前创建 table,然后通过列名访问它:

function T = myfun2(...)
N = large number
A = zeros(N,4);
T = table(A(:,1), A(:,2:end),'VariableNames',{'scalar','vector'});
for i = 1:N
   do stuff
   T.scalar(i,:) = scalar_i;
   T.vector(i,:) = vector_i;
   % or in one line: T(i,:) = table(scalar_i, vector_i);
end

我不确定每次迭代创建一点 table 是否有效,所以可能更喜欢一次访问一列。

注意

正如 Juhl 在评论中指出的那样,可能存在使用临时对象创建 table 的双重分配,而对于 'Size' 参数,您可以预期只有一个数据块已分配。

让我们检查一下。在我的电脑上,使用 Matlab 2019a,有:

>> memory
Maximum possible array:       56239 MB (5.897e+10 bytes) *

所以我可以在单个数组中分配 56.239e9 / 8 = 7.0299e9 个元素(知道双精度在 8 个字节上)。让我们四舍五入,假设我想创建一个 table,其中一列超过一半(3.51e9 个元素):

>> T = table(zeros(4e9,1));
>> memory
Maximum possible array:       33644 MB (3.528e+10 bytes)

分配时间较长,但已完成。用'Size',完全一样:

>> T = table(zeros(4e9,1));
>> memory
Maximum possible array:       33677 MB (3.531e+10 bytes) *

看来我们没有双重分配。

有一个有趣的事实:T 占用的内存比我们预期的要少。如果我尝试修改我的 table 的最后一个元素,它消耗的内存似乎达到了预期的内存大小:

>> T.Var1(end) = 1;
>> memory
Maximum possible array:       27574 MB (2.891e+10 bytes)

披露

请注意修改这种table需要时间:

>> tic; T.Var1(end) = 1; toc
Elapsed time is 33.286967 seconds.

所以我的结论是:使用普通数组,速度要快得多:

>> tic; T = table('Size', [4e9, 1], 'VariableTypes',{'double'}); toc
Elapsed time is 15.997680 seconds.
>> tic; T.Var1(end) = 1; toc
Elapsed time is 33.286967 seconds.
>> clear T;

>> tic; A = zeros(4e9,1); toc
Elapsed time is 0.043366 seconds.
>> tic; A(end) = 1; toc
Elapsed time is 0.002430 seconds.
>> clear A;