非虚拟接口?(需要一个非常高性能的低级抽象)

lee*_*mes 7 c++ architecture abstraction idioms micro-optimization

我正在尝试在应用程序架构中的非常低级别对我的代码进行微优化.所以这是我的具体情况:

  • 我有一个解析图形文件的解析器类(节点,边,邻接条目等)
  • 文件格式是版本化的,因此每个版本都存在解析器,这些解析器实现为单独的类(ParserV1,ParserV2,...).
  • 解析器为应用程序中的某些上层提供相同的功能.因此,它们实现相同的" 界面 ".
  • 在C++中,我将这样的接口实现为抽象类,所有函数都是纯虚拟的.
  • 由于虚函数需要另一个内存查找,并且在编译时不能静态绑定 - 更重要的是 - 不允许在解析器类中内联小方法,使用经典的子类成语不会导致我能达到的最佳表现.

[在描述我可能的解决方案之前,我想解释为什么我在这里进行微优化(你可以跳过这一段):解析器类有很多小方法,其中"小"意味着它们没有做太多.它们中的大多数只从缓存的比特流中读取一个或两个字节,甚至只读取一个比特.所以应该可以以非常有效的方式实现它们,其中函数调用在内联时只需要少量的机器命令.这些方法在应用程序中经常被调用,因为它们在一个非常大的图形(全球道路网络)中查找节点属性,每个用户请求可能会发生大约一百万次,并且这样的请求应该像可能.]

去哪儿路?我可以看到以下方法来解决问题:

  1. 使用纯虚方法编写接口并将其子类化.表现将受到影响.
  2. 不要写这样的界面.每个解析器自己定义相同的方法.在上层(使用解析器)具有指向每个版本子类的指针(作为成员).在开始时,实例化应该使用的特定解析器.使用switch块并在访问函数时将解析器实例强制转换为显式子类.表现会更好吗?(if/switch block与虚拟表查找).
  3. 混合两种解决方案1. + 2:使用纯虚方法编写一个接口,用于很少使用的方法,其中性能不是很关键.如果它很重要,请不要提供虚方法,而是使用第二种方法.
  4. 改进2:在抽象类中提供非虚方法; 将版本号作为成员变量保留在抽象类中(一种自己的运行时类型信息),并在这些方法中实现if/switch块和强制转换; 然后调用子类中的方法.这提供了内联和静态绑定.

有没有更好的方法来解决这个问题?这有什么成语吗?

为了澄清,我有很多与版本无关的函数(至少到现在为止),因此非常适合某些超类.我将对大多数函数使用标准的子类设计,而这个问题仅涵盖要优化的版本相关函数的解决方案.(其中一些不经常被调用,我当然可以在这些情况下使用虚方法.)除此之外,我不喜欢让解析器类决定哪些方法需要高性能而哪些方法无效.(虽然有可能这样做.)

val*_*ldo 2

首先,当然,您应该分析您的代码,以确定在您的特定情况下 vcall 的性能损失有多大(除了可能较弱的优化之外)。

抛开优化主题不谈,我几乎可以肯定,通过用调用编译时的开关替换虚拟函数调用(或通过指针变量调用函数,这几乎是相同的),您不会获得任何显着的性能提升 -不同情况下的已知函数。

如果你真的想要显着的改进 - 恕我直言,这些是最有希望的变体:

  1. 尝试重新设计您的界面以启用更复杂的功能。例如,如果您有一个读取单个顶点的函数 - 将其修改为一次读取(最多)N 个顶点。等等。

  2. 您可以将整个解析代码(使用解析器)制作为一个template类/函数,它将使用模板参数来实例化所需的解析器。在这里您既不需要接口也不需要虚拟函数。在最开始(您标识版本的地方) - 放置一个switch,对于每个识别的版本,使用适当的模板参数调用此函数。

从性能的角度来看,后者可能会更优越,但这会增加代码大小

编辑:

以下是 (2) 的示例:

template <class Parser>
void MyApplication::HandleSomeRequest(Parser& p)
{
    int n = p.GetVertexCount();
    for (iVertex = 0; iVertex < n; iVertex++)
    {
        // ...    
        p.GetVertexEdges(iVertex, /* ... */);    
        // ...    
    }
}

void MyApplication::HandleSomeRequest(/* .. */)
{
    int iVersion = /* ... */;
    switch (iVersion)
    {
    case 1:
        {
            ParserV1 p(/* ... */);
            HandleSomeRequest(p);
        }
        break;

    case 2:
        {
            ParserV2 p(/* ... */);
            HandleSomeRequest(p);
        }
        break;

    // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

ParserV1ParserV2没有函数。virtual它们也不继承任何接口。他们只是实现一些功能,例如GetVertexCount