【发布时间】:2012-07-03 08:42:35
【问题描述】:
我正在尝试在应用程序架构的非常低级别对我的代码进行微优化。所以这是我的具体场景:
- 我有一个解析器类,它解析图形文件(节点、边、邻接条目等)
- 文件格式是版本化的,因此每个版本都存在解析器,它们被实现为单独的类(ParserV1、ParserV2、...)。
- 解析器为应用程序中的某些上层提供相同的功能。因此,它们实现了相同的“接口”。
- 在 C++ 中,我会将这样的接口实现为抽象类,其中所有函数都是纯虚拟。
- 由于虚函数需要另外一次内存查找,并且不能在编译时静态绑定,而且——更重要的是——将不允许内联解析器类中的小方法,使用经典的子类化习语不会带来我能达到的最佳性能。
[在描述我可能的解决方案之前,我想解释一下我为什么在这里做微优化(你可以跳过这一段):解析器类有很多小方法,其中“小”意味着它们没有'不做太多。它们中的大多数只从缓存的比特流中读取一两个字节,甚至只读取一个比特。所以应该有可能以一种非常有效的方式实现它们,其中一个函数调用,当内联时,只需要少量的机器命令。 这些方法在应用程序中被非常频繁地调用,因为它们在一个非常大的图表(全球道路网络)中查找节点属性,每个用户请求可能发生大约一百万次,并且这样的请求应该与可能。]
去这里的路是什么?我可以看到以下解决问题的方法:
- 用纯虚方法编写一个接口并将其子类化。性能会受到影响。
- 不要写这样的接口。每个解析器自己定义相同的方法。在上层(使用解析器)有指向每个版本子类的指针(作为成员)。一开始,实例化应该使用的特定解析器。每当访问函数时,使用 switch 块并将解析器实例强制转换为显式子类。性能会更好吗? (if/switch 块与虚拟表查找)。
- 混合使用两种解决方案 1. + 2.:为很少使用的方法编写一个带有纯虚拟方法的接口,其中性能不是很关键。如果很关键,不要提供虚拟方法,而是使用第二种方法。
- 改进2.:在抽象类中提供非虚方法;在抽象类中保留一个版本号作为成员变量(一种自己的运行时类型信息),并在这些方法中实现 if/switch 块和强制转换;然后调用子类中的方法。这提供了内联和静态绑定。
有没有更好的方法来解决这个问题?这有什么成语吗?
为了澄清,我有很多与版本无关的函数(至少到现在为止),因此非常适合某些超类。我将对大多数函数使用标准的子类化设计,而这个问题仅涵盖要优化的版本相关函数的解决方案。 (其中一些调用不频繁,在这些情况下我当然可以使用虚拟方法。)除此之外,我不喜欢让解析器类决定哪些方法需要高性能,哪些不需要. (虽然这样做是可能的。)
【问题讨论】:
-
@JamesMcNellis 感谢您的提示。我怎么没想到?你的意思是:
template<int V> class Parser{...},然后是Parser<1>,而不是ParserV1? -
(JamesMcNellis 写了一条评论,他删除了。他建议使用模板类。)
-
澄清一下 - 您是否尝试针对可能随时调用任何公共成员函数但其中少数比其他函数使用得更多的情况进行优化?
-
你做对了。这是一个折衷方案:您放弃了运行时动态绑定以实现编译时动态绑定。也许如果速度是绝对必要的,你可以“手动内联”小函数的所有代码......这意味着维护意大利面条函数,但解析器都已经这样了。 :)
标签: c++ architecture abstraction idioms micro-optimization