【问题标题】:how to handle optimizations in code如何处理代码中的优化
【发布时间】:2010-10-22 10:01:29
【问题描述】:

我目前正在为一些代码编写各种优化。这些优化中的每一个都对代码效率(希望如此)以及源代码产生重大影响。但是,我想保留启用和禁用其中任何一个以进行基准测试的可能性。

我传统上使用#ifdef OPTIM_X_ENABLE/#else/#endif 方法,但代码很快变得难以维护。

还可以为每个优化创建 SCM 分支。在您想要启用或禁用多个优化之前,代码可读性要好得多。

还有其他更好的优化方法吗?

编辑: 一些优化不能同时工作。我可能需要禁用旧的优化来换取新的优化,看看我应该保留哪一个。

【问题讨论】:

  • 对我来说听起来很可怕:)
  • 为什么要禁用优化?要么它们起作用,产生影响,然后它们应该成为代码库的一部分,要么它们没有任何作用,那么启用它们就没有意义了。
  • 如上所述,这是一件奇怪的事情。这些是特定于平台的优化吗?您是否要确保保留某些算法的安全版本,以防在优化代码中发现错误?或者您是否想保留易于理解的代码以用于文档目的。

标签: c++ c optimization


【解决方案1】:

我会为优化创建一个分支,对其进行基准测试,直到您知道它有显着改进,然后简单地将其合并回主干。一旦它回到主干上,我就不会打扰#ifdefs;为什么一旦知道它很好就需要禁用它?如果您希望能够回滚特定更改,则始终拥有存储库历史记录。

【讨论】:

  • 这种方法也有其局限性。如果您想针对不同的平台进行优化,考虑不同的操作系统或硬件怎么办。然后,您必须保留所有分支(或#ifdefs),以免出现代码分歧的风险,并且不可能合并回来。
  • @kriss:我同意!但如果是这种情况,提问者应该在问题中说明这一点......
  • 有时两个优化不能同时工作。我可能需要禁用旧的优化来换取新的优化,看看我应该保留哪一个。
  • @Ben:我个人的偏好是不要让所有这些基础设施把代码弄得乱七八糟,以防万一我想尝试一下。我坚信这是源代码库存在的原因之一。
  • 是的,我当然也坚信我们必须杀死死代码,这不仅适用于优化。
【解决方案2】:

有很多方法可以选择要执行的代码部分。根据我的经验,使用预处理器的条件包含通常是最难维护的。因此,如果可以的话,尽量减少它。您可以在不同的功能中分离功能(优化的、未优化的)。然后根据标志有条件地调用函数。或者您可以创建继承层次结构并使用虚拟调度。当然,这取决于你的具体情况。也许如果你能更详细地描述它,你会得到更好的答案。

但是,这里有一个可能对您有用的简单方法:创建两组函数(或类,无论您使用哪种范例)。将函数分成不同的命名空间,一个用于优化代码,一个用于可读代码。然后只需通过有条件地using 他们来选择要使用的集合。像这样的:

#include <iostream>
#include "optimized.h"
#include "readable.h"

#define USE_OPTIMIZED

#if defined(USE_OPTIMIZED)
using namespace optimized;
#else
using namespace readable;
#endif

int main()
{
   f();
}

然后在optimized.h:

namespace optimized
{
   void f() { std::cout << "optimized selected" << std::endl; }
}

readable.h:

namespace readable
{
   void f() { std::cout << "readable selected" << std::endl; }
}

不幸的是,这种方法确实需要使用预处理器,但使用量很少。当然,您可以通过引入包装头来改进这一点:

wrapper.h:

#include "optimized.h"
#include "readable.h"

#define USE_OPTIMIZED

#if defined(USE_OPTIMIZED)
using namespace optimized;
#else
using namespace readable;
#endif

现在只需包含此标头并进一步减少潜在的预处理器使用。顺便说一句,header/cpp 的通常分离仍然应该完成。

祝你好运!

【讨论】:

    【解决方案3】:

    我会在类级别(或 C 的文件级别)工作,并将所有各种版本嵌入同一个工作软件中(没有 #ifdef),并在运行时通过一些配置文件或命令行选项选择一个实现或另一个实现。 这应该很容易,因为优化不应该在内部 API 级别改变任何东西。

    如果您使用 C++,另一种方法是实例化模板以避免重复高级代码或在运行时选择分支(即使这通常是一个可接受的选项,这里和那里的一些开关通常不是这样的)大问题)。

    最终各种优化的后端最终可以转向库。

    单元测试应该能够在不使用每个实现变体修改它们的情况下工作。

    我的理由是,嵌入每个变体都会改变软件的大小,而且很少会出现问题。这种方法还有其他好处:您可以轻松应对不断变化的环境。对某些操作系统或某些硬件的优化可能不是一个对另一个的优化。在许多情况下,甚至可以很容易地在运行时选择最佳版本。

    【讨论】:

    • 这种方法的问题是,如果你正在优化一个紧密的内部循环的主体,那么你要么在每次迭代时有条件地分支到两个实现中的每一个,要么产生开销,或者你最终会复制更高级别的代码。
    • @Oli Charlesworth:你说得对,这是这个解决方案的极限,我相应地更新了我的答案。然而,真正遇到这种情况是非常罕见的。如果时间紧迫,这可能是一个问题,您可能应该考虑重构内部循环和调用者,因为通常会出现新的可能优化这样做。换句话说,如果这确实是一个问题,那么您可能在错误的级别上进行了优化。关于这个主题还有另一个非常有趣的答案,我会尝试再次找到它以提供链接。
    • @Oli Charlesworth:好的,我找到了stackoverflow.com/questions/926266/… 这个答案是我最喜欢的答案之一。 :-)
    【解决方案4】:

    您可能有两个(三个/更多)版本的函数进行优化,名称如下: 功能 功能优化 它们具有相同的参数并返回相同的结果。

    然后您可以在 som 标头中 #define 选择器,例如:

     #if OPTIM_X_ENABLE
     #define OPT(f) f##_optimized
     #else
     #define OPT(f) f
     #endif
    

    然后将具有优化变体的函数称为 OPT(function)(argument, argument...)。这种方法不是很美观,但确实如此。

    您可以更进一步,为所有优化的函数使用 re#define 名称:

     #if OPTIM_X_ENABLE
     #define foo foo_optimized
     #define bar bar_optimized
     ...
     #endif
    

    并保持调用者代码不变。预处理器为您执行功能替换。我最喜欢它,因为它在每个函数(以及每个数据类型和每个变量)粒度化时透明地工作,这在大多数情况下对我来说已经足够了。

    更奇特的方法是为未优化和优化的代码制作单独的 .c 文件并只编译其中一个。它们可能具有相同的名称但具有不同的路径,因此可以通过在命令行中更改单个选项来进行切换。

    【讨论】:

      【解决方案5】:

      我很困惑。您为什么不找出每个性能问题的位置,修复它,然后继续。 Here's an example.

      【讨论】:

        猜你喜欢
        • 2022-11-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-10-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-07-11
        相关资源
        最近更新 更多