c# - 如何有效地计算对数返回

标签 c# .net class math return

我有一个多维数组double[,] results,其中每一列代表特定项目(例如汽车、房屋...)的价格时间序列。我想将每个时间序列的对数返回计算为 日志(price_t/price_t1) 其中 t>t1。因此,我将为 double[,] 结果 的每一列生成一个新的日志返回时间序列。 如何以有效的方式在 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[,] 结果 中的列数和行数。 该解决方案运行速度非常慢,而且效率似乎很低。有什么建议可以更快地执行类似的计算吗?

我看到在像 MATLAB 这样的语言中,可以对代码进行矢量化,并简单地将原始矩阵除以另一个仅滞后一个元素的矩阵。然后取除法得到的整个矩阵的对数。它在 C# 中可行吗?如何实现?

最佳答案

如果您的计算机有多个内核,您可以轻松提高计算速度。为了亲自尝试,我首先创建了这个函数:

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 倍多一点的速度,表明只有物理内核而非逻辑内核能够计算对数。

现代 x86 CPU 有称为 SSE 的特殊指令允许您向量化某些计算。我希望 MATLAB 使用这些指令,这可以解释为什么与您自己的 C# 代码相比,您在 MATLAB 中体验到更好的性能。

为了测试 SSE,我尝试了 Yeppp!绑定(bind)到 C#。该库作为预发行版在 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 秒左右。如果耶普!如果有一种执行除法的方法(SSE 有),您可能会获得更短的执行时间,可能会导致速度提高十倍。

根据我对 SSE 的实验,您应该能够实现相当大的性能改进。但是,如果这很重要,您可能应该注意精度。与 .NET 实现相比,SSE 日志函数可能会提供略有不同的结果。

关于c# - 如何有效地计算对数返回,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28514471/

相关文章:

function - React 函数未定义

c# - 属性路由和 CreatedAtRoute

c# - Visual Studio 2010 的精度问题

c# - 基于可为空类型的对象仅在对象为非空时才装箱

c# - 有没有办法让派生类覆盖 ToString()?

php - 这两种初始化 PHP 类的方法有什么区别?

java - 哪个 Web 服务堆栈允许将 wsdl first Web 服务绑定(bind)到 Java 中的现有类?

C# TreeView.GetNodeAt() 单击图像的问题

c# - 在递归调用中使用 lock(obj)

c# - foreach 与多个字段的总和