我正在比较Matlab和C ++之间的Vanilla调用选项的蒙特卡罗定价算法的速度。这与Why is MATLAB so fast in matrix multiplication?不同,因为加速不是由于矩阵乘法(只有一个快速完成的点积),但似乎是由于其高效的高斯随机数发生器。
在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
如果我使用以下1000万条路径运行
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
我得到了
Elapsed time is 0.359304 seconds.
12.8311
现在我在VS2013的C ++中做同样的事情
我的代码在OptionMC类中,如下所示
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;
}
BM代码如下
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;
}
我已将优化选项设置为在Visual Studio中优化速度。
对于10米路径,需要4.124秒。
这比Matlab慢11倍。
任何人都可以解释这种差异吗?
编辑:在进一步测试时,减速似乎是对GaussianRVByBoxMuller的调用。 Matlab似乎有一个非常有效的实现 - Ziggurat方法。请注意,BM在这里是次优的,因为它生成2个RV而我只使用1.修复这个将使速度提高2倍。
答案 0 :(得分:3)
现在,您正在生成单线程代码。猜测,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";
}
我用VC ++ 2015编译了这个,使用以下命令行:
cl -openmp -Qpar -arch:AVX -O2b2 -GL test.cpp
在AMD A8-7600上运行时间约为0.32秒 在Intel i7处理器上,运行时间约为.16秒。
当然,如果你有一个拥有更多内核的CPU,你仍然有相当快的速度运行它。
就目前而言,我的代码需要VC ++ 2015而不是2013,但我怀疑它对性能的影响很大。它主要只是方便,比如使用10'000'000
代替10000000
(但我不打算在这台机器上安装2013的副本,只是为了弄明白我需要什么改变以适应它。)
另请注意,与最近的英特尔处理器相比,您可能(或可能不会)通过将arch:AVX
更改为arch:AVX2
来获得一些改进。
快速检查单线程代码表明您的Box-Muller分发代码可能比标准库的正常分发代码快一点,因此切换到线程友好版本可能会获得一点点速度更快(优化版 也应该大约加倍)。