为什么Matlab比C+快11倍+; 我比较了蒙特卡洛和VC++之间的香草期权定价算法的速度。这与此不同,因为加速不是由于矩阵乘法(只有快速完成的点积),而是由于其高效的高斯随机数生成器

为什么Matlab比C+快11倍+; 我比较了蒙特卡洛和VC++之间的香草期权定价算法的速度。这与此不同,因为加速不是由于矩阵乘法(只有快速完成的点积),而是由于其高效的高斯随机数生成器,c++,matlab,montecarlo,matlab-compiler,C++,Matlab,Montecarlo,Matlab Compiler,在Matlab中,代码已矢量化,代码如下所示 function [ value ] = OptionMCValue( yearsToExpiry, spot, strike, riskFreeRate, dividendYield, volatility, numPaths ) sd = volatility*sqrt(yearsToExpiry); sAdjusted = spot * exp( (riskFreeRate - dividendYield - 0.5*vol

在Matlab中,代码已矢量化,代码如下所示

function [ value ] = OptionMCValue( yearsToExpiry, spot, strike, riskFreeRate, dividendYield, volatility, numPaths  )

    sd = volatility*sqrt(yearsToExpiry);
    sAdjusted = spot * exp( (riskFreeRate - dividendYield - 0.5*volatility*volatility) * yearsToExpiry);

    g = randn(1,numPaths);
    sT = sAdjusted * exp( g * sd );
    values = max(sT-strike,0);`
    value = mean(values);
    value = value * exp(-riskFreeRate * yearsToExpiry);

end
strike = 100.0;
yearsToExpiry = 2.16563;
spot = 100.0;
volatility = 0.20;
dividendYield = 0.03;
riskFreeRate = 0.05;
oneMillion = 1000000;
numPaths = 10*oneMillion;

tic
value = OptionMCValue( yearsToExpiry, spot, strike, riskFreeRate, dividendYield, volatility, numPaths  );
toc
double OptionMC::value(double yearsToExpiry, 
                   double spot,
                   double riskFreeRate,
                   double dividendYield,
                   double volatility, 
                   unsigned long numPaths )
{
    double sd = volatility*sqrt(yearsToExpiry);
    double sAdjusted = spot * exp( (riskFreeRate - dividendYield - 0.5*volatility*volatility) * yearsToExpiry);
    double value = 0.0;
    double g, sT;

    for (unsigned long i = 0; i < numPaths; i++)
    {
        g = GaussianRVByBoxMuller();
        sT = sAdjusted * exp(g * sd);
        value += Max(sT - m_strike, 0.0);
    }

    value = value * exp(-riskFreeRate * yearsToExpiry);
    value /= (double) numPaths;
    return value;
}
double GaussianRVByBoxMuller()
{
double result;
double x; double y;;
double w;

do
{
    x = 2.0*rand() / static_cast<double>(RAND_MAX)-1;
    y = 2.0*rand() / static_cast<double>(RAND_MAX)-1;
    w = x*x + y*y;
} while (w >= 1.0);

w = sqrt(-2.0 * log(w) / w);
result = x*w;

return result;
}
如果我用1000万条路径运行它,如下所示

function [ value ] = OptionMCValue( yearsToExpiry, spot, strike, riskFreeRate, dividendYield, volatility, numPaths  )

    sd = volatility*sqrt(yearsToExpiry);
    sAdjusted = spot * exp( (riskFreeRate - dividendYield - 0.5*volatility*volatility) * yearsToExpiry);

    g = randn(1,numPaths);
    sT = sAdjusted * exp( g * sd );
    values = max(sT-strike,0);`
    value = mean(values);
    value = value * exp(-riskFreeRate * yearsToExpiry);

end
strike = 100.0;
yearsToExpiry = 2.16563;
spot = 100.0;
volatility = 0.20;
dividendYield = 0.03;
riskFreeRate = 0.05;
oneMillion = 1000000;
numPaths = 10*oneMillion;

tic
value = OptionMCValue( yearsToExpiry, spot, strike, riskFreeRate, dividendYield, volatility, numPaths  );
toc
double OptionMC::value(double yearsToExpiry, 
                   double spot,
                   double riskFreeRate,
                   double dividendYield,
                   double volatility, 
                   unsigned long numPaths )
{
    double sd = volatility*sqrt(yearsToExpiry);
    double sAdjusted = spot * exp( (riskFreeRate - dividendYield - 0.5*volatility*volatility) * yearsToExpiry);
    double value = 0.0;
    double g, sT;

    for (unsigned long i = 0; i < numPaths; i++)
    {
        g = GaussianRVByBoxMuller();
        sT = sAdjusted * exp(g * sd);
        value += Max(sT - m_strike, 0.0);
    }

    value = value * exp(-riskFreeRate * yearsToExpiry);
    value /= (double) numPaths;
    return value;
}
double GaussianRVByBoxMuller()
{
double result;
double x; double y;;
double w;

do
{
    x = 2.0*rand() / static_cast<double>(RAND_MAX)-1;
    y = 2.0*rand() / static_cast<double>(RAND_MAX)-1;
    w = x*x + y*y;
} while (w >= 1.0);

w = sqrt(-2.0 * log(w) / w);
result = x*w;

return result;
}
我明白了

Elapsed time is 0.359304 seconds.
   12.8311
现在我在VS2013

中用C++做了同样的事情 我的代码在OptionMC类中,如下所示

function [ value ] = OptionMCValue( yearsToExpiry, spot, strike, riskFreeRate, dividendYield, volatility, numPaths  )

    sd = volatility*sqrt(yearsToExpiry);
    sAdjusted = spot * exp( (riskFreeRate - dividendYield - 0.5*volatility*volatility) * yearsToExpiry);

    g = randn(1,numPaths);
    sT = sAdjusted * exp( g * sd );
    values = max(sT-strike,0);`
    value = mean(values);
    value = value * exp(-riskFreeRate * yearsToExpiry);

end
strike = 100.0;
yearsToExpiry = 2.16563;
spot = 100.0;
volatility = 0.20;
dividendYield = 0.03;
riskFreeRate = 0.05;
oneMillion = 1000000;
numPaths = 10*oneMillion;

tic
value = OptionMCValue( yearsToExpiry, spot, strike, riskFreeRate, dividendYield, volatility, numPaths  );
toc
double OptionMC::value(double yearsToExpiry, 
                   double spot,
                   double riskFreeRate,
                   double dividendYield,
                   double volatility, 
                   unsigned long numPaths )
{
    double sd = volatility*sqrt(yearsToExpiry);
    double sAdjusted = spot * exp( (riskFreeRate - dividendYield - 0.5*volatility*volatility) * yearsToExpiry);
    double value = 0.0;
    double g, sT;

    for (unsigned long i = 0; i < numPaths; i++)
    {
        g = GaussianRVByBoxMuller();
        sT = sAdjusted * exp(g * sd);
        value += Max(sT - m_strike, 0.0);
    }

    value = value * exp(-riskFreeRate * yearsToExpiry);
    value /= (double) numPaths;
    return value;
}
double GaussianRVByBoxMuller()
{
double result;
double x; double y;;
double w;

do
{
    x = 2.0*rand() / static_cast<double>(RAND_MAX)-1;
    y = 2.0*rand() / static_cast<double>(RAND_MAX)-1;
    w = x*x + y*y;
} while (w >= 1.0);

w = sqrt(-2.0 * log(w) / w);
result = x*w;

return result;
}
double OptionMC::value(两年到期,
双点,
双重无风险利率,
双分割收益率,
双重波动,
无符号长数)
{
双sd=波动率*sqrt(到期前一年);
双重调整=即期*exp((无风险利率-分割收益率-0.5*波动率*波动率)*到期年);
双值=0.0;
双g,sT;
for(无符号长i=0;i
BM代码如下所示

function [ value ] = OptionMCValue( yearsToExpiry, spot, strike, riskFreeRate, dividendYield, volatility, numPaths  )

    sd = volatility*sqrt(yearsToExpiry);
    sAdjusted = spot * exp( (riskFreeRate - dividendYield - 0.5*volatility*volatility) * yearsToExpiry);

    g = randn(1,numPaths);
    sT = sAdjusted * exp( g * sd );
    values = max(sT-strike,0);`
    value = mean(values);
    value = value * exp(-riskFreeRate * yearsToExpiry);

end
strike = 100.0;
yearsToExpiry = 2.16563;
spot = 100.0;
volatility = 0.20;
dividendYield = 0.03;
riskFreeRate = 0.05;
oneMillion = 1000000;
numPaths = 10*oneMillion;

tic
value = OptionMCValue( yearsToExpiry, spot, strike, riskFreeRate, dividendYield, volatility, numPaths  );
toc
double OptionMC::value(double yearsToExpiry, 
                   double spot,
                   double riskFreeRate,
                   double dividendYield,
                   double volatility, 
                   unsigned long numPaths )
{
    double sd = volatility*sqrt(yearsToExpiry);
    double sAdjusted = spot * exp( (riskFreeRate - dividendYield - 0.5*volatility*volatility) * yearsToExpiry);
    double value = 0.0;
    double g, sT;

    for (unsigned long i = 0; i < numPaths; i++)
    {
        g = GaussianRVByBoxMuller();
        sT = sAdjusted * exp(g * sd);
        value += Max(sT - m_strike, 0.0);
    }

    value = value * exp(-riskFreeRate * yearsToExpiry);
    value /= (double) numPaths;
    return value;
}
double GaussianRVByBoxMuller()
{
double result;
double x; double y;;
double w;

do
{
    x = 2.0*rand() / static_cast<double>(RAND_MAX)-1;
    y = 2.0*rand() / static_cast<double>(RAND_MAX)-1;
    w = x*x + y*y;
} while (w >= 1.0);

w = sqrt(-2.0 * log(w) / w);
result = x*w;

return result;
}
double gaussianrvbyboxmller()
{
双重结果;
双x;双y;;
双w;
做
{
x=2.0*rand()/static_cast(rand_MAX)-1;
y=2.0*rand()/static_cast(rand_MAX)-1;
w=x*x+y*y;
}而(w>=1.0);
w=sqrt(-2.0*对数(w)/w);
结果=x*w;
返回结果;
}
我已经在VisualStudio中设置了优化选项以优化速度

对于10米路径,需要4.124秒

这比Matlab慢11倍

有人能解释这两者的区别吗


编辑:在进一步的测试中,减速似乎是对Gaussianrvbyboxmller的呼唤。Matlab似乎有一个非常有效的实现——Ziggurat方法。请注意,BM在这里是次优的,因为它生成2个RVs,而我只使用1个RVs。仅修复这一点就可以提高2倍的速度。

现在,您正在生成单线程代码。据猜测,Matlab使用的是多线程代码。这允许它以大约N的因数运行得更快,其中N=CPU中的内核数

不过,这个故事还有更多的内容。出现的另一个问题是,您使用的是
rand()
,它使用隐藏的全局状态。因此,如果您对代码进行简单的重写以使其成为多线程的,那么很有可能由于
rand()
的内部状态而产生冲突,从而使您无法获得更大的速度提升(并且可能很容易运行得更慢,可能会更慢)

为解决这个问题,您可以考虑使用C++ 11中新增的随机数生成(和可能的分布)类。有了这些,您可以为每个线程创建一个单独的随机数生成器实例,以防止其内部状态发生冲突

我将您的代码重写一点,以使用这些代码,并调用函数,以获得以下结果:

double m_strike = 100.0;

class generator {
    std::normal_distribution<double> dis;
    std::mt19937_64 gen;
public:
    generator(double lower = 0.0, double upper = 1.0)
        : gen(std::random_device()()), dis(lower, upper) {}

    double operator()() {
        return dis(gen);
    }
};

double value(double yearsToExpiry,
    double spot,
    double riskFreeRate,
    double dividendYield,
    double volatility,
    unsigned long numPaths)
{
    double sd = volatility*sqrt(yearsToExpiry);
    double sAdjusted = spot * exp((riskFreeRate - dividendYield - 0.5*volatility*volatility) * yearsToExpiry);
    double value = 0.0;
    double g, sT;

    generator gen;

// run iterations in parallel, with a private random number generator for each thread:
#pragma omp parallel for reduction(+:value) private(gen)
    for (long i = 0; i < numPaths; i++)
    {
        g = gen(); // GaussianRVByBoxMuller();
        sT = sAdjusted * exp(g * sd);
        value += std::max(sT - m_strike, 0.0);
    }

    value = value * exp(-riskFreeRate * yearsToExpiry);
    value /= (double)numPaths;
    return value;
}

int main() {
    std::cout << "value: " << value(2.16563, 100.0, 0.05, 0.03, 0.2, 10'000'000) << "\n";
}
在AMD A8-7600上,这只运行了约0.31秒。
在英特尔i7处理器上,这只需约.16秒

当然,如果你有一个拥有更多内核的CPU,你很有可能运行得更快

就目前而言,我的代码需要VC++2015而不是2013,但我怀疑它对性能的影响有多大。它主要是为了方便,比如使用
10000'000
而不是
10000000
(但我不会在这台机器上安装2013的副本,只是为了弄清楚我需要做哪些更改才能适应它)

另请注意,与最新的英特尔处理器相比,您可能(也可能不会)通过将
arch:AVX
改为
arch:AVX2
获得一些改进


对单线程代码的快速检查表明,您的Box-Muller分发代码可能比标准库的正常分发代码快一点,因此切换到线程友好的版本可能会获得更快的速度(优化的版本也应该是标准库的两倍左右)

是像MATLAB版本那样矢量化的C++版本吗?MATLAB中使用的优化与C++编译器对代码不一样。只是猜测,好的旧时代,当它足够简单地改写C++,你有一个保证的速度!现在,Matlab已经被优化了很多,你需要开始编写非常好的C++代码,并使用快速库来快速地看到速度的增加。<代码> GaussianRVByBoxMuller <代码>总是在循环中被创建和销毁。由于所有代码都是双重计算,我想这段代码(构造函数和析构函数)隐藏了一些东西!谢谢我改为多线程std,正因为如此,我的时间从4.124秒缩短到了1.44秒,这是3个改进的好因素-我的Intel i5处理器有一个四核。与其他命令行选项一样,更改架构的影响很小。我认为其他部分的性能差异一定是由于Matlab的Ziggurat Gaussian RV生成器造成的,但剩余的四个因子对我来说仍然太高。