优化代码可读性的同时保持性能

18

我正在使用Python和C编写一款科学程序,其中包含一些复杂的物理模拟算法。在实现算法之后,我发现有很多可能的优化方法可以提高程序性能,例如预计算值、将计算从周期中取出、用更复杂的矩阵算法替换简单的算法等。但是问题也随之而来:未经过优化的算法速度较慢,但是其逻辑和与理论的联系更加清晰易懂,而优化后的算法则更难扩展和修改。

因此,问题是 - 我应该使用什么技术来在提高性能的同时保持可读性?现在我正在尝试并行开发快速和清晰的分支,但也许还有更好的方法?


1
一个非常有趣且有用的问题。我担心没有清晰的答案。为了获得性能提升,您必须牺牲某些东西。可读性只是其中之一。 - Noufal Ibrahim
1
有一个很好的理由,Knuth将他的书命名为“计算机程序设计的艺术”... :) - hugomg
4个回答

15

作为一般性的建议(我对Python不是很熟悉):我建议你确保可以轻松地用“优化”的部分替换“参考实现”的慢部分(例如,使用类似策略模式的东西)。

这将允许您交叉验证更复杂算法的结果(以确保您没有搞砸结果),并保持仿真算法的整体结构清晰(关注点分离)。您可以将优化算法放入单独的源文件/文件夹/包中,并根据需要单独记录它们的详细信息。

除此之外,尽量避免常见陷阱:不要过度优化(使用分析器检查是否真的值得),不要重新发明轮子(寻找可用的库)。


1
谢谢,那很有道理。也许我应该尝试将算法分成合理的多个部分,并尝试使用相同的输入/输出但不同的实现方式来实现这些部分。您所写的概念值得更加仔细地研究。 - user517893
很高兴听到你觉得这个有帮助。分割算法听起来不错...(分而治之!:-) 将实现隐藏在特定接口后,还可以让您重复使用单元测试等。 - Roland Ewald
这对我来说也有重要的原因。正如我所提到的,我使用Python编写代码,但是使用C来实现大部分密集部分(可能是最有效的优化)。因此,C对于性能非常重要,而Python则用于实现新算法和接口。这使得将策略分为两部分变得有用,以便轻松混合Python和C,以在它们的优势之间取得平衡。 - user517893

3
你的问题非常好,在几乎每个程序中都会出现,无论是简单还是复杂,任何想称自己为专业人士的程序员都会遇到这个问题。
我试着记住并牢记一个读者新来到我的代码时,对问题和解决方法的看法与我最初的粗略观点以及直接(也许是蛮力)的方法基本相同。然后,随着我对问题的深入理解和解决方案路径变得更加清晰,我尝试编写反映更好理解的注释。有时我成功了,这些注释帮助读者,特别是在六周后我回到代码时它们帮助了我。我的风格是总是写很多注释,当我不写注释时(因为:突然的灵感让我兴奋;我想看它运行;我的大脑已经炸了),我几乎总是非常后悔。
如果我能写出清晰、完整、简洁、准确且最新的注释,那就是我能做的最好的事情。对我来说,最重要的一点是优化通常不意味着将大量代码塞进一行源代码中,或者通过调用一个参数为另一个函数的函数来完成。我知道有些人这样做是为了避免临时存储函数的值。但这几乎没有什么(通常没有)加速代码的作用,而且很难跟踪。我知道你已经知道这个消息了。

感谢您分享您的经验。是的,编写足够准确的注释非常重要,我在处理没有足够注释的旧代码时已经遇到了问题。而且,是的,“一行代码”优化会带来巨大的复杂性增加,但却没有或只有很少的好处,所以这是非常正确的 :-) - user517893
1
我发现,如果我正在做一些需要解释的事情,我需要努力读者正在发生什么。这是一个努力的过程,不总是成功的。读者可能没有足够的背景知识来理解它。 - Mike Dunlavey
1
@Mike Dunlavey -- 是的,说得好:我们有责任教导读者。在阅读您的评论后,我想到了一些事情:如果读者不理解,那么(撇开背景不足),有限的可能性是自己也没有理解 :-) - Pete Wilson
我有点处于传统软件工程师和纯粹的数学家之间。我试图理解他在做什么,但是很困难。同时,我从微分方程到编译器再到UI代码都会做,而工程师们甚至不尝试去理解这些。 - Mike Dunlavey

3

通常人们认为要追求性能就必须牺牲可读性。

但这并非一定如此。

你需要找出程序在做什么,以及为什么会花费大量时间。

注意,我没有说你需要进行任何测量。

这里有一个例子可以说明我的意思。

很有可能你只需进行一些简单的更改就能避免浪费运算,但在程序自身告诉你需要修复什么之前不要进行任何修复。


同意。因此,可能没有太多地方可以进行(显著的)性能改进。与那些重要的地方相比,大多数地方根本不重要。 - andrew cooke
@andrew:我在Python方面没有太多经验,但我已经进行了很多性能调优。我同意,前几个问题可能非常局部化。然后,如果你很幸运并且能够重新设计程序,你可能能够再来一轮。我的数学代码经验是第二轮不存在。你基本上必须在线上寻找最佳算法并寻找低级别的优化。但是,大多数代码都是无辜的。 - Mike Dunlavey

1
def whatYouShouldDo(servings, integration_method=oven):
    """
        Make chicken soup
    """
    # Comments:
    # They are important. With some syntax highlighting, the comments are
    # the first thing a new programmer will look for. Therefore, they should
    # motivate your algorithm, outline it, and break it up into stages.
    # You can MAKE IT FEEL AS IF YOU ARE READING TEXT, interspersing code
    # amongst the text.
    #
    # Algorithm overview:
    # To make chicken soup, we will acquire chicken broth and some noodles.
    # Preprocessing ingredients is done to optimize cooking time. Then we 
    # will output in SOUP format via stdout.
    #
    # BEGIN ALGORITHM
    #
    # Preprocessing:
    # 1. Thaw chicken broth.
    broth = chickenstore.deserialize()

    # 2. Mix with noodles
    if not noodles in cache:
        with begin_transaction(locals=poulty) as t:
            cache[noodles] = t.buy(noodles)  # get from local store
    noodles = cache[noodles]

    # 3. Perform 4th-order Runge-Kutta numerical integration
    import kitchensink import *  # FIXME: poor form, better to from kitchensink import pan at beginning
    result = boilerplate.RK4(broth `semidirect_product` noodles)

    # 4. Serve hot
    log.debug('attempting to serve')
    return result
    log.debug('server successful')

还可以参见http://en.wikipedia.org/wiki/Literate_programming#Example

我也听说http://en.wikipedia.org/wiki/Aspect-oriented_programming试图帮助解决这个问题,不过我还没有真正研究过它。 (它似乎只是一种花哨的方式来说“将您的优化、调试和其他杂项放在您正在编写的函数之外”。)


面向切面编程不快,它有负担,并且在C语言中我不知道任何可以帮助OP的库,如果是C++的话就比较合理。 - Saeed Amiri

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