3

这是一个奇怪的问题,所以我必须提供一些背景知识。我有一个我正在研究的C++项目,我想清理一下。我正在处理的主要问题使我想要陷入大规模滥用项目核心组件中使用的预处理器宏。在编译和使用该程序以切换不同算法的使用之前,有一个文件带有一堆#define,它们被注释/取消注释。我宁愿有命令行参数来做这件事,而不是每次我们想尝试不同的算法时重新编译。问题是整个代码中有太多的#ifdef交织在一起,似乎不可能简单地重构每个算法的代码。清除预处理宏

我被告知这背后的原因是,这应该是一个实时系统,将处理毫秒单位的时间,并且此组件中的代码被调用了很多次, if支票将对我们的表现造成不利影响。如果您想尝试其他算法,则必须设置适当的标志并重新编译,以便优化性能。

所以我对你所有的问题是这样的:

有没有办法,我可以摆脱这些宏的任何方式,而不是用命令行参数没有重击性能和无需再处理逻辑和码?

我正在考虑的其中一个选项是试图为每种可能的算法组合定义此组件的版本,然后选择由所提供的命令行参数组合定义的版本。但根据我的朋友,组合的数量太多了,这是可行的。我自己并没有计算出这些数字,但考虑到他在这些代码中投入了多少工作量,我会认真听取他的意见。

+2

你的朋友可能是对的。假设你有n个切换点,每个切换点都有m个不同的状态,你必须创建的“性能”构建的数量为m^n。对于任何不平凡的数字,这也被称为“比你能适应澳大利亚更多”。 – Borealid 2010-07-14 01:50:16

+0

哈哈,我想尽可能多,但这是最简单的解决方案,我可以看到这将使大部分代码保持完好。但我也绝对希望它能够适应澳大利亚。 :P – MKA 2010-07-14 03:26:05

+0

对不起,如果我误解你的问题,但如果程序启动时间不关键,你总是可以做一个包装,实际上用选定的算法构建程序并启动它。 对代码清理并没有什么帮助,但:-((看一下编译器缓存或ccache) – MattBianco 2010-07-14 08:33:59

回答

2

这被认为是将要处理的时间

毫秒为单位这是一个实时 系统真正的问题。

如果检查 会对我们的 性能产生负面影响。

这不是一个很好的理由。

如果您的代码已针对性能进行基准测试并因此得到了优化(因为它应该是),那么这将适用。我无法想象任何情况下,通过用#defines替代ifs(除非比较字符串内容,使用顺序搜索或类似的性能灾难性事件),您将获得显着的性能增益。因此,我敢打赌,在设计时选择了使用宏的决定,这可能会使其成为过早优化的一种情况(“不成熟的优化是所有宏定义的根源”: d)

有没有办法,我可以摆脱 这些宏,而是使用 命令行参数,而不沉重 命中性能和不 再处理逻辑和代码什么办法?

是。

这里有一些可能的步骤(有其他的解决方案,但是这一次没有使用if s的一切:

  1. 定义您的代码的基准,并运行它(存储结果)

  2. 找到在多于一个的可能#define条款中实现的代码的一个区域。

  3. 移动后面与共同接口功能的定义。

  4. 在运行时,将参数与常量进行比较,并将指向所选函数的指针传递给客户端代码。

    观光避免:

    • 执行比较一次以上;在比较之后,你应该有一个选定的函数指针;该函数指针应该传递,而不是你的参数。
    • 使用字符串(或char*或任何不是数字的)执行比较。比较字符串 - 或者在常量时间内不进行任何比较 - 对性能至关重要的代码是灾难性的。不要使用if来比较参数值,请考虑使用switch语句来执行此操作。
    • 将大型结构作为参数传递给您的策略函数。传递应该由(const)引用或指针完成。
  5. 通过函数指针而不是直接调用策略代码。

  6. 在步骤0重复基准测试并比较性能。

此时你应该有一个强烈的情况下,呈现给你的老板/经理:

  • 可以使代码运行速度(增加一个函数调用的成本,你的表现 - 关键代码应该没什么大问题 - 在汇编级别,函数调用应该包括在堆栈上传递几个指针和一条指令 - 我认为)。您可以使用您的基准测试结果显示它运行得很快。

  • 你的代码会更容易维护(更加模块化,分离功能块的背后接口,集中化的变化等)

  • 你的代码应该是更容易扩展(同上原因)

  • 您不必再重新编译您的代码库。

  • 你摆脱了一个大问题(由过早优化引起)。

  • 随着其他领域的开发/维护工作的进行,您可以继续重构代码库(并去除更多的宏),而功能行为几乎没有变化。

1

你是否分析了有问题的代码?假设if语句减慢程序听起来像过早优化,这是一种代码味道。

+0

不,我们没有对它进行太深的描述,我们监视了多少次注意到代码被调用的频率一次运行以及它在最差情况下的表现如何,所以我可以用它作为基准,并尝试通过相同的测试条件,并至少替换一些#ifdefs。这不仅仅是一个if语句,但是你是对的:我们无法确切地知道它没有真正地分析它... – MKA 2010-07-14 03:58:06

1

啊,人们可以通过CPP创造的暴行。

首先,您需要决定您要如何做到这一点。在C++中。处理这样的情况正确方法是建立的代表,其中有一些区别的地方类的集合,然后隐藏接口背后的差异,你有,说

DifferentialEquationIntegrator <: 
    Runge-Kutta Integrator 
    Eulers Method Integrator 

等(阅读<:为业箭头,或“规定,满足” - 一个<: B表示“A提供了一个行为规范,B满足。”

它本质上是一个机械的翻译从该计划你去描述一个“正确”的方案,但它会变得毛茸茸并且延伸,并且会像重写代码一样花费很多费用。

如果你不想这样做,你将需要分析现有的代码。我与代码分析工具的当前状态脱节,但有许多供应商和不少开源工具。他们可以帮助很多。

是的另一种选择是使用预处理器来帮助。您通常可以针对代码运行预处理器,从而生成生成的代码。例如,在gcc -E标志执行此操作。由于历史原因,结果包含所有新行(使wc -l更有意义),所以如果你想阅读它,请通过indent(1)或类似的东西运行输出。

如果你使用所有不同的标志集来做这件事,你会通过一些差异学习一些关于共享代码的知识。

第四种选择是构建自己的工具,将东西分解成碎片并帮助您重新排列它们。这应该为某人创造一个很好的博士项目。

+0

起初,我曾希望我们能够提取大部分逻辑并抽象出接口的不同实现中的差异,与你之前提到的类似,虽然在看到代码和理解交错的一切后,我可以理解我的队友对此的犹豫,但我希望我们仍然能够以正确的方式做到这一点。这个想法分析-E'd文件。那并且使用这些代码分析工具绝对是直接的选择。我喜欢为我的博士学习最后一种选择的想法。 :) – MKA 2010-07-14 04:24:04

1

有一种模式可以解决这类问题:策略。

您选择您希望使用ONCE的策略(算法),然后传递该对象。这当然需要工厂来构建正确的策略对象。

至于交错码,应该对通用码进行因式分解。

无论如何,如果您不确定并担心可能会破坏某些东西,请在尝试更改程序之前先编写一堆单元测试。这样,如果在重构过程中发生任何事情,那么希望它会被测试抓住。此外,尝试和一点一点地重构(例如通过文件存档,或者任何单位有意义)。

1

如果我需要使用可选算法,在运行时选择它们,我会利用某种散列表。我的算法是具有相同签名的函数,以便我可以创建一个指向函数的指针数组,并通过数组中的索引来调用它们(索引可以被解析为命令行参数)。虚拟性(如许多设计模式)没有性能损失,也不会受到“if”(如手动选择算法)的惩罚。

一些代码:

// type of my funcs: 
typedef void (*SolverFunc)(const SolverParams &sp); 

// implementation for the algorithms: 
void EulerSolver(const SolverParams &sp) { ... } 

void RhungeSolver(const SolverParams &sp) { ... } 

// my array of solvers: 
static SolverFunc s_solvers [] = { EulerSolver, RhungeSolver }; 

// parsing command line params: 
int main(int argc, char** argv) 
{ 
    int solverIndex = ParseIndex(argv); 
    s_solvers[solverIndex] (.. params ..); 
    return 0; 
} 

好了,代码是C风格的,而不是C++ - 风格,但这个想法是值得考虑的。附:我不知道,该示例在语法上是否正确,对不起=)