如何有效地计算日志返回

时间:2015-02-14 10:05:57

标签: c# .net class math return

我有一个多维数组double[,] results,其中每列代表特定商品(例如汽车,房屋......)的价格时间序列。我想计算每个时间序列的日志返回值 记录(price_t / price_t1) 其中t> t1。因此,我将为double[,] results的每一列生成一个新的时间序列的日志返回。 如何以高效的方式在C#中完成?数据量很大,我正在尝试像这样的解决方案:

for(int col = 1; col <= C; col++)
{
     for(int row = 1; row <= R; row++)
     {
         ret = Math.Log(results[row+1;col]/ results[row;col])
     }
}

其中C和R是double[,] results中的列数和行数。 这个解决方案运行得非常慢,看起来非常低效。有没有建议如何更快地执行类似的计算?

我在MATLAB等语言中看到,可以对代码进行矢量化,并简单地将原始矩阵除以另一个仅滞后于一个元素的矩阵。然后记录由除法产生的整个矩阵的对数。它在C#中是可行的吗?

1 个答案:

答案 0 :(得分:3)

如果您的计算机有多个内核,则可以轻松提高计算速度。为了自己尝试一下,我开始创建这个函数:

Double[,] ComputeLogReturns(Double[,] data) {
  var rows = data.GetLength(0);
  var columns = data.GetLength(1);
  var result = new Double[rows - 1, columns];
  for (var row = 0; row < rows - 1; row += 1)
    for (var column = 0; column < columns; column += 1)
      result[row, column] = Math.Log(data[row + 1, column]/data[row, column]);
  return result;
}

我使用1,000 x 1,000值的输入数组对此函数进行基准测试。在我的电脑上,100次通话的执行时间约为3秒。

因为循环体可以并行执行,然后重写函数以使用Parallel.For

Double[,] ComputeLogReturnsParallel(Double[,] data) {
  var rows = data.GetLength(0);
  var columns = data.GetLength(1);
  var result = new Double[rows - 1, columns];
  Parallel.For(0, rows - 1, row => {
    for (var column = 0; column < columns; column += 1)
      result[row, column] = Math.Log(data[row + 1, column]/data[row, column]);
  });
  return result;
}

在具有4个核心(8个逻辑核心)的计算机上,执行100个呼叫大约需要0.9秒。这略快超过3倍,表明只有物理核心而非逻辑核心能够计算对数。

Modern x86 CPU有一些名为SSE的特殊指令,允许您对某些计算进行矢量化。我希望MATLAB使用这些指令,这可以解释为什么你在MATLAB中遇到的性能要比你自己的C#代码好得多。

为了测试SSE,我尝试了绑定到C#的Yeppp!。该库可作为预发布在NuGet上使用,并具有对数功能。 SSE指令仅适用于一维数组,因此我重写了基线函数:

Double[] ComputeLogReturns(Double[] data, Int32 rows, Int32 columns) {
  var result = new Double[(rows - 1)*columns];
  for (var row = 0; row < rows - 1; row += 1)
    for (var column = 0; column < columns; column += 1)
      result[row*columns + column] = Math.Log(data[(row + 1)*columns + column]/data[row*columns + column]);
  return result;
}

使用相同的输入和100次迭代,执行时间现在似乎略小于3秒,表明一维数组可能会略微提高性能(但逻辑上它不应该除非它是影响执行的额外参数检查时间)。

使用Yeppp!功能变为:

Double[] ComputeLogReturnsSse(Double[] data, Int32 rows, Int32 columns) {
  var quotient = new Double[(rows - 1)*columns];
  for (var row = 0; row < rows - 1; row += 1)
    for (var column = 0; column < columns; column += 1)
      quotient[row*columns + column] = data[(row + 1)*columns + column]/data[row*columns + column];
  var result = new Double[(rows - 1)*columns];
  Yeppp.Math.Log_V64f_V64f(quotient, 0, result, 0, quotient.Length);
  return result;
}

我无法使用Yeppp找到执行矢量化除法的函数!所以除法是使用“正常”除法进行的。但是,我仍然期望对数是最昂贵的操作。最初的表现非常糟糕,100次迭代需要17秒,但后来我发现Yeppp出现了问题!关于以32位进程运行时的性能不佳。切换到64位显着提高了性能,导致大约1.3秒的执行时间。摆脱函数内部的两个数组分配(重复100次)将执行速度降低到0.7秒左右,这比并行实现要快。使用Parallel.For进行乘法可将执行时间降低到0.4秒左右。如果是Yeppp!有一种方法可以执行除法(SSE有)你可能会获得更低的执行时间,这可能会导致速度增加10倍。

根据我对SSE的实验,您应该能够获得可观的性能提升。但是,如果重要的话,你应该注意精确度。与.NET实现相比,SSE日志功能可能会提供稍微不同的结果。