我有一个带有许多数学例程的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...
};
答案 0 :(得分:4)
如果您只是在C ++类函数中包装C库调用(换句话说,C ++函数除了调用C函数之外什么都不做),那么编译器将优化这些调用,这样就不会降低性能。
答案 1 :(得分:3)
与任何有关表现的问题一样,您会被告知要测量以获得答案(这是严格正确的答案)。
但是根据经验,对于实际上可以内联的简单内联方法,您将看不到性能损失。一般来说,内联方法除了将调用传递给另一个函数之外什么都不做,这是内联的一个很好的选择。
但是,即使您的包装器方法没有内联,我怀疑您没有注意到性能损失 - 甚至不是可测量的 - 除非在某个关键循环中调用包装器方法。即使这样,如果包装函数本身没有做太多工作,它也可能只是可测量的。
这类事情是关注的最后一件事。首先担心使代码正确,可维护,并且您正在使用适当的算法。
答案 2 :(得分:2)
与所有与优化相关的内容一样,答案是在知道优化是否值得之前,您必须自己测量性能。
-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应该谨慎使用,特别是在你控制的接口上。