出色的软件设计会导致 20% 的性能损失

计算科学 线性代数 稀疏矩阵 哎呀
2021-12-16 22:12:35

我正在编写一个用于稀疏矩阵计算的小型库,以此来教自己充分利用面向对象的编程。我非常努力地建立了一个很好的对象模型,其中的部分(稀疏矩阵和描述它们的连接结构的图)非常松散耦合。在我自己看来,代码的可扩展性和可维护性要高得多。

但是,它也比我使用直截了当的方法要慢一些。为了测试拥有这个对象模型的权衡,我编写了一个新的稀疏矩阵类型,它打破了底层图的封装,看看它运行的速度有多快。

起初,它看起来很暗淡。我曾经引以为豪的代码比没有任何优雅软件设计的版本慢 60%。但是,我能够进行一些低级优化——内联一个函数并稍微改变一个循环——根本不改变 API。有了这些变化,它现在只比竞争对手慢 20%。

这让我想到了一个问题:如果这意味着我有一个很好的对象模型,我应该接受多少性能损失?

4个回答

这是一个关于你把时间花在什么上面的问题。对于我们大多数人来说,我们将 3/4 的时间用于编程,而将 1/4 的时间用于等待结果。(您的数字可能会有所不同,但我认为这个数字并非完全没有优点。)因此,如果您的设计允许您以两倍的速度进行编程(3/4 个时间单位而不是 1.5 个时间单位),那么您可以在性能上受到 300% 的打击(从 1/4 到 1 个时间单位),而您在解决问题上花费的实际时间仍然领先。

另一方面,如果您正在执行繁重的计算,您的计算可能看起来会有所不同,您可能希望花更多的时间来优化您的代码。

对我来说,如果你最终变得更有效率,20% 似乎是一个相当不错的权衡。

很少有科学软件开发人员了解良好的设计原则,所以如果这个答案有点冗长,我深表歉意。从软件工程的角度来看,科学软件开发人员的目标是设计一个解决方案,以满足一组经常相互冲突的约束。

以下是这些约束的一些典型示例,可应用于稀疏矩阵库的设计:

  • 一个月内完成
  • 在您的笔记本电脑和多个工作站上正常运行
  • 高效运行

科学家们逐渐更加关注软件工程的其他一些常见需求:

  • 文档(用户指南、教程、代码注释)
  • 可维护性(版本控制、测试、模块化设计)
  • 可重用性(模块化设计,“灵活性”)

您可能需要或多或少的这些要求之一。如果你想赢得 Gordon Bell 的性能奖,那么即使是百分之一的分数都是相关的,而且很少有评委会评估你的代码质量(只要你能说服他们这是正确的)。如果您试图证明在共享资源(例如集群或超级计算机)上运行此代码是合理的,那么您通常必须为您的代码性能的声明辩护,但这些声明很少非常严格。如果你想在期刊上发表一篇描述你的方法的性能提升的论文,那么你需要比你的竞争对手更快,而 20% 的性能是一个折衷,我很乐意为更好的可维护性和可重用性做出权衡。

回到你的问题,“好的设计”,给予足够的开发时间,不应该牺牲性能。如果目标是让代码尽可能快地运行,那么代码应该围绕这些约束进行设计。您可以使用代码生成、内联汇编等技术,或利用高度调整的库来帮助您解决问题。

但是,如果您没有足够的开发时间怎么办?什么足够好?好吧,这取决于,没有更多的上下文,没有人能够给你一个很好的答案。

FWIW:如果您真的对编写高性能稀疏矩阵内核感兴趣,您应该与优化的 PETSc 安装进行比较,如果您击败他们,请与他们的团队合作,他们很乐意将调整后的内核合并到库中。

恕我直言,高达 50% 的罚款(无论出于何种原因)并没有那么糟糕。

事实上,我已经看到仅基于编译器类型的性能差异为 0-30%。这适用于 PETSc 的稀疏 MatMult 例程,用于处理由低阶 FE 离散化产生的矩阵。

软件设计不会随着时间的推移而自动改进。表演会。您将使用下一个 CPU 获得 20% 的回报。此外,良好的软件设计将使将来更容易扩展或改进库。