首页 > 解决方案 > Matlab 2019 中的 3 维内部收益率

问题描述

我正在尝试在 Matlab 2019a 中计算具有多个维度的 IRR。我的公式在理论上有效(暂时忽略“多重回报率”警告),但问题是对于更大的矩阵,即 noScenarios > 5 左右,代码变得非常慢。有哪些编程替代方案?我也试过 fsolve 但我认为它也不是更快。

请注意,由于我不是数学高手,所以像“布伦特方法”这样的简单关键词对我来说是不够的(例如,计算内部收益率 IRR 的最有效方法是什么?)。我必须知道a)如何在Matlab中实现它,以及b)如果它是非常愚蠢的证明,那么什么都不会出错?谢谢!

clc
clear
close all

noScenarios = 50;

CF = ones(300,noScenarios,noScenarios,noScenarios);
CF = [repmat(-300, 1,noScenarios,noScenarios,noScenarios); CF];

for scenarios1 = 1:noScenarios
    for scenarios2 = 1:noScenarios
        for scenarios3 = 1:noScenarios
            IRR3dimensional(scenarios1,scenarios2,scenarios3) = irr(CF(:,scenarios1,scenarios2,scenarios3));
        end
    end
end

标签: matlabperformancefor-loopmultidimensional-arrayirr

解决方案


要计算 IRR,您需要求解一个多项式方程。这必须对每个现金流量向量分别进行。因此,应用于irr多维矩阵不会提高执行时间。我怀疑 Matlab 内部仍然使用循环。

您可能可以通过使用优化选项来获得一些速度,fsolve但不太可能有很大的改进。据推测,Matlab 开发人员已经选择了一种足够好的方法。

因此,您唯一的其他选择是并行化。如果您可以访问服务器或笔记本电脑/台式机有多个 CPU,则可以通过irr并行运行函数来减少运行时间。(您可能还需要一个并行计算工具箱。)

我稍微修改了您的示例以使用随机现金流值以使其更易于检查。但是,我减少了场景和时间点的数量,以便该timeit函数可以在合理的时间内运行多个模拟。(另外,请记住,执行时间似乎是时间点数量的指数。)

t = 150;
noScenarios = 10;
noThreads = 4;

CF = rand(t,noScenarios,noScenarios,noScenarios);
CF = [-rand(1,noScenarios,noScenarios,noScenarios); CF];

h1 = @() f1(CF, noScenarios);
fprintf("%0.4f : single thread, loop\n", timeit(h1))

h2 = @() f2(CF, noScenarios);
fprintf("%0.4f : single thread, vectorized\n", timeit(h2))

poolObj = parpool('local', noThreads);
h3 = @() f3(CF, noScenarios);
fprintf("%0.4f : parallelized outer loop\n", timeit(h3))
delete(poolObj);

poolObj = parpool('local', noThreads);
h4 = @() f4(CF, noScenarios);
fprintf("%0.4f : parallelized inner loop\n", timeit(h4))
delete(poolObj);

function res = f1(CF, noScenarios)
    res = zeros(noScenarios, noScenarios, noScenarios);
    for scenarios1 = 1:noScenarios
        for scenarios2 = 1:noScenarios
            for scenarios3 = 1:noScenarios
                res(scenarios1,scenarios2,scenarios3) = irr(CF(:,scenarios1,scenarios2,scenarios3));
            end
        end
    end
end

function res = f2(CF, noScenarios)
    res = reshape(irr(CF), noScenarios, noScenarios, noScenarios);
end

function res = f3(CF, noScenarios)
    res = zeros(noScenarios, noScenarios, noScenarios);
    parfor scenarios1 = 1:noScenarios
        for scenarios2 = 1:noScenarios
            for scenarios3 = 1:noScenarios
                res(scenarios1,scenarios2,scenarios3) = irr(CF(:,scenarios1,scenarios2,scenarios3));
            end
        end
    end
end

function res = f4(CF, noScenarios)
    res = zeros(noScenarios, noScenarios, noScenarios);
    for scenarios1 = 1:noScenarios
        for scenarios2 = 1:noScenarios
            parfor scenarios3 = 1:noScenarios
                res(scenarios1,scenarios2,scenarios3) = irr(CF(:,scenarios1,scenarios2,scenarios3));
            end
        end
    end
end

当我在具有 4 个 CPU 和 16 Gb 内存的服务器上运行此代码时,我得到了以下结果。

19.9357 : single thread, loop
20.4318 : single thread, vectorized
...
5.6346 : parallelized outer loop
...
12.4640 : parallelized inner loop

如您所见,矢量化版本irr的循环没有提供任何好处。在这种情况下,它会稍微慢一些。在我的其他测试中,它偶尔会更快一些。

但是,您可以通过将外部循环与parfor函数并行化来显着减少运行时间。它比并行化最内层循环要好,因为每个批次都有一定的执行开销。因此,少量较大的批次比大量的较小批次具有更低的开销。

以下是并行化的工作原理。首先,您使用以下命令创建一个本地工作线程池。确保不超过您拥有的 CPU 数量。parpool可以无限期地等待,直到创建所有本地工作人员,并且只有在 CPU 可用时才能创建本地工作人员。

poolObj = parpool('local', noThreads);

创建池可能需要几秒钟。这就是为什么我把它移到我计时的功能之外。对于较大的作业,池创建时间与总执行时间相比是微不足道的。

在这里,我将池对象保存在一个变量中,然后将其删除。但是,它是可选的。默认情况下,池在 30 分钟不活动或 Matlab 终止后被销毁。

之后,您将for要并行化的循环替换为parfor调用,即for scenarios1 = 1:noScenarios变为parfor scenarios1 = 1:noScenarios. 默认情况下,parfor将使用所有可用的工作人员,但您也可以指定允许使用的工作人员的最大数量parfor (scenarios1 = 1:noScenarios, maxWorkers)。但是请注意,执行顺序并不能保证,即第五次迭代可能在第三次迭代之前执行。


推荐阅读