何时应该使用线性代数库,比如Math.NET?

12

我不能确定这个问题有一个正确的答案,但是让我们试试。虽然许多数值问题可以用线性代数的形式来表述,但从我的有限经验来看,在使用Math.NET进行简单操作时,与编写等效操作的原始数组相比,存在性能开销。

作为测试案例,我编写了代码来计算向量和列表中最接近向量之间的距离,包括三个版本:在数组上操作、在稠密向量上操作以及在带有MKL提供程序的稠密向量上操作。在数组上运行的速度大约比在向量上运行快4倍,在使用MKL提供程序时要快3倍。

缺点是我不得不手动编写距离计算,而不是利用内置的Norm函数。好处是它更快。注意:我没有发布代码,如果需要,我很乐意这样做,我可能也没有正确地使用Math.NET。

所以我的问题是:对我来说,使用更高级的抽象会带来性能成本。一般情况下是这样吗?还是有一些情况(例如稀疏矩阵),在这些情况下使用Math.NET预计会优于手动编写的数组操作?

如果是这种情况,我倾向于认为使用Math.NET的线性代数部分主要有用于涉及矩阵的“真正”的代数,以避免重新实现更复杂的计算/算法,并潜在地提高代码可读性,但对于更简单的逐个向量操作,最好还是使用原始数组。希望能够解开何时使用库和何时自行编写的一些问题!


一般来说,我会建议您优先使用现有的库,除非性能测试表明它对您的使用过慢,并且您可以手动实现更快的方法。然后,您应该在代码中记录下来,以防以后情况发生变化,您可以切换回标准库实现。关于Math.NET,我不确定;我从未使用过它。 - Cody Gray
@Cody:总的来说我同意你的观点,但我倾向于更仔细地看待与线性代数有关的代码的性能,因为你通常会在涉及密集计算的地方遇到它,而性能会产生巨大影响。这就是为什么我对这个问题感兴趣-我想更清楚地了解应该在何时选择哪种方式的界限。 - Mathias
@Mathias,Christoph提供的细节非常有趣,并且确认了我的选择。当我觉得实现函数本身会花费很多时间并且不是性能关键时,例如我使用Math.net库中的特征值分解函数。但在其他情况下,当需要高性能时(我做了相当多的矩阵乘法,Cholesky分解和转置),我使用自己的实现,因为它更快,因为它不太通用。 - user1892410
1个回答

27

免责声明:我正在维护Math.NET Numerics。

像Math.NET Numerics这样的工具包主要提供的价值是增加开发者的生产力,特别是对于那些没有相关领域博士学位的人来说,他们很难或需要花费大量时间才能自己实现这些有时非常复杂的算法,而不是花时间解决实际问题。

此外,有些情况下你需要的功能可能已经被其他人使用过了。其中一些人可能已经发现并指出了一些问题,并贡献了改进意见。用户数量越多,代码质量和可靠性就越高。但不幸的是,这也带来了一个主要缺点:它也倾向于使代码更加通用,这通常会使其比专门针对你的需求进行高度专业化的实现效率更低。

这与Cody Gray的评论所言相符:如果它能正常运行且速度足够快,那么请使用它;否则,请修复它并让它正常运行(且快速),选择另一个工具包,或者自己实现你所需的功能。幸运的是,Math.NET Numerics还有其他一些选项,见下文。

因此,我同意你的结论:如果你实际上不需要任何复杂操作,不处理非常大的数据但性能很重要,使用数组或另一个数据结构是没有问题的(特别是在F#中,我个人更倾向于经常使用原生的数据结构而不是在C#中)。当然,这代价是失去了一些便利性以及风险,即当你需要更多操作时,可能最终需要重新实现工具包。最终,这也取决于它对你的项目有多么关键,以及你是否可以花费资源和时间来维护自己的数学代码。

尽管如此,在我的经验中,拥有代码(这样你就可以立即进行更改)且保持简单和专注(这样它只会做你需要它做的事情)通常是一个优势。

与Math.NET Numerics相关的特定内容

  • 非常具体的托管实现总是比通用的托管实现更高效。然而,我们的托管实现不应该比任何其他托管替代方案慢得多。毕竟,如果正确优化,我们的算法在内部也会直接操作数组。如果另一种算法更快,那么似乎最好用那种算法替换我们的实现,因此请告诉我们(或者更好地贡献变化)。
  • 如果你恰好遇到了一个可以利用像MKL这样的本机提供程序的路径,并且你处理大数据,我期望 Math.NET 会快几个数量级,尽管抽象层次较高。
  • Math.NET Numerics 中并非所有的代码路径都已经得到同等程度的优化,或者使用了本地提供程序。在过去的几个小版本中,已经投入了大量的工作来优化线性代数,所以我们正在变得更好,但是进展缓慢;仍然有很多工作要做(尤其是对于稀疏类型)。在你的情况下,可能碰巧遇到了一些未经过优化的路径。因此,我非常感兴趣你的代码示例,以便我们可以针对这个特定的案例进行工作
  • Math.NET Numerics 性能提示

    • 使用本地提供程序
    • 在 Control 类中尝试一些并行化设置(但请注意,我们已经意识到 v2.4 之前的并行化实现实际上相当糟糕,并计划在 v2.5 中完全替换它。首次基准测试表现良好)
    • 在实现自己的操作时,尽量避免访问任何 At/indexer,而是直接访问原始数组(参见 .Storage)
    • 许多操作允许指定结果向量/矩阵,有时甚至可以与操作数之一相同(就地)。如果你处理非常大的数据,避免在每个操作中创建新数组,从而降低内存压力。不幸的是,这也会使代码丑陋。

7
对Math.NET Numerics的有趣洞见以及对整个“自己动手”vs“使用库”的辩论做出了相当好的贡献,这种辩论在SO(以及我所听说过的每个软件开发社区)上引发了许多问题和答案。 - High Performance Mark
2
在我的测试中,我发现初始化大小为3的Vector<double>double[]要慢2-3倍。这在Math.Net中是否正常,还是我做错了什么? - Jeff

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接