如何同步C& C ++库具有最小的性能损失?

时间:2008-11-13 02:41:44

标签: c++ c optimization portability maintainability

我有一个带有许多数学例程的C库,用于处理向量,矩阵,四元数等。它需要保留在C中,因为我经常将它用于嵌入式工作和Lua扩展。另外,我有C ++类包装器,以便使用C API为数学运算提供更方便的对象管理和运算符重载。包装器只包含一个头文件,尽可能多地使用内联。

对于包装C代码与移植并将实现直接内联到C ++类是否存在明显的损失?该库用于时间关键型应用程序。那么,消除间接的推动能否弥补两个端口的维护难题呢?

C接口示例:

typedef float VECTOR3[3];

void v3_add(VECTOR3 *out, VECTOR3 lhs, VECTOR3 rhs);

C ++包装器示例:

class Vector3
{
private:
    VECTOR3 v_;

public:
    // copy constructors, etc...

    Vector3& operator+=(const Vector3& rhs)
    {
        v3_add(&this->v_, this->v_, const_cast<VECTOR3> (rhs.v_));
        return *this;
    }

    Vector3 operator+(const Vector3& rhs) const
    {
        Vector3 tmp(*this);
        tmp += rhs;
        return tmp;
    }

    // more methods...
};

6 个答案:

答案 0 :(得分:4)

如果您只是在C ++类函数中包装C库调用(换句话说,C ++函数除了调用C函数之外什么都不做),那么编译器将优化这些调用,这样就不会降低性能。

答案 1 :(得分:3)

与任何有关表现的问题一样,您会被告知要测量以获得答案(这是严格正确的答案)。

但是根据经验,对于实际上可以内联的简单内联方法,您将看不到性能损失。一般来说,内联方法除了将调用传递给另一个函数之外什么都不做,这是内联的一个很好的选择。

但是,即使您的包装器方法没有内联,我怀疑您没有注意到性能损失 - 甚至不是可测量的 - 除非在某个关键循环中调用包装器方法。即使这样,如果包装函数本身没有做太多工作,它也可能只是可测量的。

这类事情是关注的最后一件事。首先担心使代码正确,可维护,并且您正在使用适当的算法。

答案 2 :(得分:2)

与所有与优化相关的内容一样,答案是在知道优化是否值得之前,您必须自己测量性能。

  • 基准测试两个不同的函数,一个直接调用C风格的函数,另一个函数通过包装器调用。查看哪一个运行得更快,或者差异是否在您的测量误差范围内(这意味着您可以测量没有差异)。
  • 查看上一步中两个函数生成的汇编代码(在gcc上,使用-S-save-temps)。看看编译器是否做了一些愚蠢的事情,或者你的包装器是否有任何性能错误。

除非性能差异太大而不支持使用包装器,否则重新实现并不是一个好主意,因为您冒险引入错误(这甚至可能导致结果看起来很清楚但是错误)。即使差别很大,只要记住C ++与C非常兼容并且即使在C ++代码中使用C风格的库也会更简单且风险更小。

答案 3 :(得分:2)

您的包装器本身将被内联,但是,您对C库的方法调用通常不会。 (这需要在技术上可行的链接时优化,但在当今的工具中最好是AFAIK最基本的)

通常,这样的函数调用并不是非常昂贵。循环成本在过去几年中大幅下降,并且可以很容易地预测,因此呼叫惩罚可以忽略不计。

但是,内联打开了更多优化的大门:如果你有v = a + b + c,你的包装类会强制生成堆栈变量,而对于内联调用,大多数数据都可以保存在FPU中堆。此外,内联代码允许简化指令,考虑常量值等。

因此,虽然衡量投资规则之前的规则是正确的,但我希望这里有一些改进空间。


一个典型的解决方案是将C实现带入一种格式,可以用作内联函数或“C”体:

// V3impl.inl
void V3DECL v3_add(VECTOR3 *out, VECTOR3 lhs, VECTOR3 rhs)
{
    // here you maintain the actual implementations
    // ...
}

// C header
#define V3DECL 
void V3DECL v3_add(VECTOR3 *out, VECTOR3 lhs, VECTOR3 rhs);

// C body
#include "V3impl.inl"


// CPP Header
#define V3DECL inline
namespace v3core {
  #include "V3impl.inl"
} // namespace

class Vector3D { ... }

这可能仅适用于具有相对简单实体的选定方法。我将这些方法移动到C ++实现的单独命名空间,因为您通常不需要直接使用它们。

(请注意,内联只是一个编译器提示,它不会强制该方法被内联。 但这很好:如果内部循环的代码大小超过指令缓存,内联很容易损害性能)

是否可以解析pass / return-by-reference取决于编译器的强度,我在很多地方看到过     foo(X * out) 强制堆栈变量,而     X foo() 将值保存在寄存器中。

答案 4 :(得分:1)

我认为你不会注意到很多差异。假设您的目标平台支持所有数据类型,

我正在编写DS和其他一些ARM设备并且浮点数是邪恶的......我不得不将typedef浮动到FixedPoint&lt; 16,8&gt;

答案 5 :(得分:1)

如果您担心调用函数的开销会降低您的速度,为什么不测试内联C代码或将其转换为宏?

另外,为什么不提高C代码的const正确性 - const_cast应该谨慎使用,特别是在你控制的接口上。​​