我正在编写一些处理数据的代码。用户可以选择许多处理函数组,然后将这些处理函数应用于数据集。我想在不同的地方实现所有这些组,但由于它们都采用相同的参数并且都做类似的事情,我希望它们有一个共同的接口。
作为一个优秀的小型c ++程序员,我首先想到的是简单地使用多态。只需使用所需的接口创建一些抽象类,然后从中派生每组处理对象。当我想到另一个皱纹时,我的希望很快就破灭了。这些数据集是巨大的,导致所讨论的函数被称为数十亿次。虽然动态查找相当便宜,但据我所知,它比标准函数调用慢得多。
我目前解决这个问题的想法是使用函数指针,其方式如下:
void dataProcessFunc1(mpz_class &input){...}
void dataProcessFunc2(mpz_class &input){...}
...
class DataProcessInterface
{
...
void (*func1)(mpz_class);
void (*func2)(mpz_class);
...
}
使用某种构造函数或某些东西来设置指向正确事物的指针。
所以我想我的问题是:这是一个好方法吗?还有另外一种方法吗?或者我应该学会停止担心并喜欢动态查找?
答案 0 :(得分:7)
虚函数调用是通过指针进行的函数调用。开销通常与通过指针的显式函数调用大致相同。换句话说,你的想法可能会获得很少(很可能根本没有)。
我的立即反应是从虚拟功能开始,只有在探查器显示虚拟呼叫的开销变得显着时才会担心其他问题。
当/如果发生这种情况,另一种可能性是在类模板中定义接口,然后将该接口的各种实现放入模板的特化中。这通常会消除所有运行时开销(尽管通常需要额外的工作量)。
答案 1 :(得分:3)
我不同意上面的一个答案,即基于模板的解决方案可能会产生最差的开销或运行时间。事实上,基于模板的解决方案允许编写更快的代码,无需虚拟函数或逐个调用(但我同意,使用这些机制仍然不会产生很大的开销。)
假设您使用一系列“特征”配置处理接口,即处理可由客户端配置以调整处理接口的部件或功能。想象一个有三个(看一个例子)处理参数化的类:
template <typename Proc1, Proc2 = do_nothing, Proc3 = do_nothing>
struct ProcessingInterface
{
static void process(mpz_class& element) {
Proc1::process(element);
Proc2::process(element);
Proc3::process(element);
}
};
如果客户端具有不同的“处理器”,其静态功能“进程”知道如何处理元素,则可以编写这样的类来“组合”这三个处理。请注意,默认的do_nothing
类具有空的处理方法:
class do_nothing
{
public:
static void process(mpz_class&) {}
};
这些电话没有开销。它们是普通呼叫,客户端可以使用ProcessingInterface<Facet1, Facet2>::process(data);
配置处理。
这仅适用于您在编译时知道不同的“构面”或“处理器”,这似乎是您的第一个示例。
另请注意,您可以使用元编程工具(例如boost.mpl库)编写更复杂的类,以包含更多类,迭代它们等等。
答案 2 :(得分:2)
从编码的角度来看,抽象接口方法当然是最干净的,并且更倾向于使用函数指针来混淆代码,这实际上是用C ++编写C语言。
您是否确实确定接口方法存在性能问题?
最好首先编写可读和可维护代码,并且只有在需要时才进行优化。
答案 3 :(得分:1)
这些数据集非常庞大,导致所涉及的函数被称为数十亿次。虽然动态查找相当便宜,但据我所知,它比标准函数调用慢得多。
数十亿次多少时间?如果你的应用程序运行一个小时,十亿次函数调用就没什么了,也不会影响性能。但如果整个数据集在100ms内处理,则十亿次函数调用是一个重要的开销来源。简单地说一下函数的调用次数是没有意义的。在性能方面重要的是如何经常被调用。每个时间单位的呼叫次数。
如果这实际上是一个性能问题,我会采用模板方法。用户不会在每次调用之间决定应用哪些操作。他将做出一次决定,然后解决所有数十亿的电话。
只需为用户可以选择的每组功能定义一个类,确保它们公开相同的接口(可能使用CRTP简化流程并轻松分解公共代码),然后根据用户的选择策略,将适当的类传递给负责进行所有处理的(模板化)函数。
但正如其他答案所说,这可能根本不是性能瓶颈。不要浪费你的时间来尝试优化不重要的代码。