在您的情况下,可能无关紧要。我说可能是因为,我的意思是建设性的,事实上,你没有指定性能要求,也没有指定调用相关函数的频率,这表明你可能现在没有足够的信息来做出判断 - “不要推测:配置文件”一揽子回应实际上只是为了确保您拥有所需的所有必要信息,因为过早的微优化非常普遍而我们真正的目标是从大局为您提供帮助。
Jeremy Friesner 用his comment on another answer here 真的一针见血:
如果您不明白为什么它很慢,您将无法加快速度。
因此,考虑到所有这些,假设 A) 您的性能要求已经得到满足(例如,您正在拉 4000 FPS - 远高于任何显示刷新率)或 B) 您正在努力满足性能要求并且此函数仅被调用几次(例如每帧
使用virtual 函数最多可能会在某处的某个表中进行一次额外的查找(可能还有一些缓存未命中 - 但如果在内部循环中重复访问则不会那么多),这是几个 CPU 时钟周期最坏的情况(并且很可能仍然小于 switch,尽管这在这里真的没有实际意义),这 与您的目标帧速率相比完全微不足道,渲染帧所需的工作量,以及您正在执行的任何其他算法和逻辑。如果您想向自己证明这一点,请介绍一下。
你应该做的就是使用任何技术来生成最清晰、最干净、最易维护和可读的代码。诸如此类的微优化不会产生效果,代码可维护性的成本即使很小,也不值得收益,这基本上为零。
你应该也做的是坐下来了解你的实际情况。您需要提高性能吗?调用此函数是否足以实际产生重大影响,或者您是否应该专注于其他技术(例如更高级别的算法、其他设计策略、将计算卸载到 GPU 或使用特定于机器的优化,例如使用 SSE 的批量操作等? )?
在没有具体信息的情况下,您可以做的一件事是尝试两种方法。虽然性能因机器而异,但您至少可以大致了解这段特定代码对整体性能的影响(例如,如果您以 60 FPS 的速度拍摄,这两个选项分别为您提供 23.2 FPS 与. 23.6 FPS,那么这不是您想要关注的地方,并且选择其中一种策略而不是另一种可能做出的牺牲可能不值得)。
还可以考虑使用调用列表、顶点索引缓冲区等。OpenGL 提供了许多工具来优化对象的绘制,其中某些方面保持不变。例如,如果您有一个巨大的曲面模型,其中包含顶点坐标经常变化的小零件,请使用调用列表将模型划分为多个部分,并且仅在自上次重绘后发生更改时更新更改部分的调用列表。离开例如如果它们经常更改,则从调用列表中着色和纹理(或使用坐标数组)。这样您就可以完全避免调用您的函数。
如果你很好奇,这里有一个测试程序(它可能不代表你的实际使用情况,同样,这不可能用给出的信息来回答——这个测试是下面 cmets 中要求的)。这确实不意味着这些结果将反映在您的程序中,而且您需要有关于您的实际需求的具体信息。但是,这只是为了咯咯笑:
此测试程序比较基于开关的操作与基于虚函数的操作与指向成员的操作(从另一个类成员函数调用成员)与指向成员的操作(调用成员的位置)直接来自测试循环)。它还执行三种类型的测试:仅使用一个运算符在数据集上运行,在两个运算符之间来回交替运行,以及使用两个运算符的随机混合运行。
使用 gcc -O0 编译时的输出,迭代次数为 1,000,000,000:
$ g++ -O0 tester.cpp
$ ./a.out
--------------------
Test: time=6.34 sec (switch add) [-358977076]
Test: time=6.44 sec (switch subtract) [358977076]
Test: time=6.96 sec (switch alternating) [-281087476]
Test: time=18.98 sec (switch mixed) [-314721196]
Test: time=6.11 sec (virtual add) [-358977076]
Test: time=6.19 sec (virtual subtract) [358977076]
Test: time=7.88 sec (virtual alternating) [-281087476]
Test: time=19.80 sec (virtual mixed) [-314721196]
Test: time=10.96 sec (ptm add) [-358977076]
Test: time=10.83 sec (ptm subtract) [358977076]
Test: time=12.53 sec (ptm alternating) [-281087476]
Test: time=24.24 sec (ptm mixed) [-314721196]
Test: time=6.94 sec (ptm add (direct)) [-358977076]
Test: time=6.89 sec (ptm subtract (direct)) [358977076]
Test: time=9.12 sec (ptm alternating (direct)) [-281087476]
Test: time=21.19 sec (ptm mixed (direct)) [-314721196]
使用gcc -O3 编译时的输出,迭代次数为 1,000,000,000:
$ g++ -O3 tester.cpp ; ./a.out
--------------------
Test: time=0.87 sec (switch add) [372023620]
Test: time=1.28 sec (switch subtract) [-372023620]
Test: time=1.29 sec (switch alternating) [101645020]
Test: time=7.71 sec (switch mixed) [855607628]
Test: time=2.95 sec (virtual add) [372023620]
Test: time=2.95 sec (virtual subtract) [-372023620]
Test: time=14.74 sec (virtual alternating) [101645020]
Test: time=9.39 sec (virtual mixed) [855607628]
Test: time=4.20 sec (ptm add) [372023620]
Test: time=4.21 sec (ptm subtract) [-372023620]
Test: time=13.11 sec (ptm alternating) [101645020]
Test: time=9.32 sec (ptm mixed) [855607628]
Test: time=3.37 sec (ptm add (direct)) [372023620]
Test: time=3.37 sec (ptm subtract (direct)) [-372023620]
Test: time=13.08 sec (ptm alternating (direct)) [101645020]
Test: time=9.74 sec (ptm mixed (direct)) [855607628]
请注意,-O3 做了很多工作,如果不查看汇编程序,我们不能将其用作手头问题的 100% 准确表示。
在未优化的情况下,我们注意到:
- 在单个操作员的运行中,虚拟机的性能优于交换机。
- 在使用多个运算符的情况下,交换机的性能优于虚拟。
- 直接调用成员时指向成员的指针 (
object->*ptm_) 与 virtual 类似,但比 virtual 慢。
- 通过另一种方法(
object->doit() 其中doit() 调用this->*ptm_)调用成员时指向成员的指针会花费不到两倍的时间。
- 正如预期的那样,“混合”案例的性能会因分支预测失败而受到影响。
在优化的情况下:
- 在所有情况下,交换机的性能都优于虚拟交换机。
- 指向成员的指针与未优化情况的相似特征。
- 由于我不明白的原因,所有涉及函数指针的“交替”情况都比
-O0 慢,并且比“混合”慢。这不会发生在我家中的 PC 上。
这里特别重要的是例如分支预测胜过“虚拟”与“切换”的任何选择。同样,请确保您了解您的代码并优化正确的内容。
这里的另一个重要的事情是这表示每次操作大约 1-14 纳秒的时间差异。这种差异对于大量操作可能很重要,但与您正在做的其他事情相比可能可以忽略不计(请注意,这些函数只执行一个算术运算,任何更多的操作都会很快使虚拟与切换的效果相形见绌)。
还要注意,虽然直接调用指向成员的指针显示出比通过另一个类成员调用它的“改进”,但这可能会对整体设计产生巨大影响,因为这样的实现(至少在这种情况下,外部的东西由于调用指向成员函数的语法不同(-> vs.->*),类直接调用成员)不能直接替换为另一个实现。例如,我必须创建一整套单独的测试用例来处理它。
结论
即使是几个额外的算术运算,性能差异也很容易相形见绌。还要注意,除了 -O3 的“虚拟交替”情况外,分支预测对所有情况的影响都要大得多。但是,测试也不太可能代表实际应用(OP 对此保密),并且-O3 引入了更多变量,因此结果必须持保留态度,不太可能适用于其他场景(换句话说,测试可能很有趣,但不是特别有意义)。
来源:
// === begin timing ===
#ifdef __linux__
# include <sys/time.h>
typedef struct timeval Time;
static void tick (Time &t) {
gettimeofday(&t, 0);
}
static double delta (const Time &a, const Time &b) {
return
(double)(b.tv_sec - a.tv_sec) +
(double)(b.tv_usec - a.tv_usec) / 1000000.0;
}
#else // windows; untested, working from memory; sorry for compile errors
# include <windows.h>
typedef LARGE_INTEGER Time;
static void tick (Time &t) {
QueryPerformanceCounter(&t);
}
static double delta (const Time &a, const Time &b) {
LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
return (double)(b.QuadPart - a.QuadPart) / (double)freq.QuadPart;
}
#endif
// === end timing
#include <cstdio>
#include <cstdlib>
#include <ctime>
using namespace std;
// Size of dataset.
static const size_t DATASET_SIZE = 10000000;
// Repetitions per test.
static const unsigned REPETITIONS = 100;
// Class performs operations with a switch statement.
class OperatorSwitch {
public:
enum Op { Add, Subtract };
explicit OperatorSwitch (Op op) : op_(op) { }
int perform (int a, int b) const {
switch (op_) {
case Add: return a + b;
case Subtract: return a - b;
}
}
private:
Op op_;
};
// Class performs operations with pointer-to-member.
class OperatorPTM {
public:
enum Op { Add, Subtract };
explicit OperatorPTM (Op op) {
perform_ = (op == Add) ?
&OperatorPTM::performAdd :
&OperatorPTM::performSubtract;
}
int perform (int a, int b) const { return (this->*perform_)(a, b); }
int performAdd (int a, int b) const { return a + b; }
int performSubtract (int a, int b) const { return a - b; }
//private:
int (OperatorPTM::*perform_) (int, int) const;
};
// Base class for virtual-function test operator.
class OperatorBase {
public:
virtual ~OperatorBase () { }
virtual int perform (int a, int b) const = 0;
};
// Addition
class OperatorAdd : public OperatorBase {
public:
int perform (int a, int b) const { return a + b; }
};
// Subtraction
class OperatorSubtract : public OperatorBase {
public:
int perform (int a, int b) const { return a - b; }
};
// No base
// Addition
class OperatorAddNoBase {
public:
int perform (int a, int b) const { return a + b; }
};
// Subtraction
class OperatorSubtractNoBase {
public:
int perform (int a, int b) const { return a - b; }
};
// Processes the dataset a number of times, using 'oper'.
template <typename T>
static void test (const int *dataset, const T *oper, const char *name) {
int result = 0;
Time start, stop;
tick(start);
for (unsigned n = 0; n < REPETITIONS; ++ n)
for (size_t i = 0; i < DATASET_SIZE; ++ i)
result = oper->perform(result, dataset[i]);
tick(stop);
// result is computed and printed so optimizations do not discard it.
printf("Test: time=%.2f sec (%s) [%i]\n", delta(start, stop), name, result);
fflush(stdout);
}
// Processes the dataset a number of times, alternating between 'oper[0]'
// and 'oper[1]' per element.
template <typename T>
static void testalt (const int *dataset, const T * const *oper, const char *name) {
int result = 0;
Time start, stop;
tick(start);
for (unsigned n = 0; n < REPETITIONS; ++ n)
for (size_t i = 0; i < DATASET_SIZE; ++ i)
result = oper[i&1]->perform(result, dataset[i]);
tick(stop);
// result is computed and printed so optimizations do not discard it.
printf("Test: time=%.2f sec (%s) [%i]\n", delta(start, stop), name, result);
fflush(stdout);
}
// Processes the dataset a number of times, choosing between 'oper[0]'
// and 'oper[1]' randomly (based on value in dataset).
template <typename T>
static void testmix (const int *dataset, const T * const *oper, const char *name) {
int result = 0;
Time start, stop;
tick(start);
for (unsigned n = 0; n < REPETITIONS; ++ n)
for (size_t i = 0; i < DATASET_SIZE; ++ i) {
int d = dataset[i];
result = oper[d&1]->perform(result, d);
}
tick(stop);
// result is computed and printed so optimizations do not discard it.
printf("Test: time=%.2f sec (%s) [%i]\n", delta(start, stop), name, result);
fflush(stdout);
}
// Same as test() but calls perform_() pointer directly.
static void test_ptm (const int *dataset, const OperatorPTM *oper, const char *name) {
int result = 0;
Time start, stop;
tick(start);
for (unsigned n = 0; n < REPETITIONS; ++ n)
for (size_t i = 0; i < DATASET_SIZE; ++ i)
result = (oper->*(oper->perform_))(result, dataset[i]);
tick(stop);
// result is computed and printed so optimizations do not discard it.
printf("Test: time=%.2f sec (%s) [%i]\n", delta(start, stop), name, result);
fflush(stdout);
}
// Same as testalt() but calls perform_() pointer directly.
static void testalt_ptm (const int *dataset, const OperatorPTM * const *oper, const char *name) {
int result = 0;
Time start, stop;
tick(start);
for (unsigned n = 0; n < REPETITIONS; ++ n)
for (size_t i = 0; i < DATASET_SIZE; ++ i) {
const OperatorPTM *op = oper[i&1];
result = (op->*(op->perform_))(result, dataset[i]);
}
tick(stop);
// result is computed and printed so optimizations do not discard it.
printf("Test: time=%.2f sec (%s) [%i]\n", delta(start, stop), name, result);
fflush(stdout);
}
// Same as testmix() but calls perform_() pointer directly.
static void testmix_ptm (const int *dataset, const OperatorPTM * const *oper, const char *name) {
int result = 0;
Time start, stop;
tick(start);
for (unsigned n = 0; n < REPETITIONS; ++ n)
for (size_t i = 0; i < DATASET_SIZE; ++ i) {
int d = dataset[i];
const OperatorPTM *op = oper[d&1];
result = (op->*(op->perform_))(result, d);
}
tick(stop);
// result is computed and printed so optimizations do not discard it.
printf("Test: time=%.2f sec (%s) [%i]\n", delta(start, stop), name, result);
fflush(stdout);
}
int main () {
int *dataset = new int[DATASET_SIZE];
srand(time(NULL));
for (int n = 0; n < DATASET_SIZE; ++ n)
dataset[n] = rand();
OperatorSwitch *switchAdd = new OperatorSwitch(OperatorSwitch::Add);
OperatorSwitch *switchSub = new OperatorSwitch(OperatorSwitch::Subtract);
OperatorSwitch *switchAlt[2] = { switchAdd, switchSub };
OperatorBase *virtAdd = new OperatorAdd();
OperatorBase *virtSub = new OperatorSubtract();
OperatorBase *virtAlt[2] = { virtAdd, virtSub };
OperatorPTM *ptmAdd = new OperatorPTM(OperatorPTM::Add);
OperatorPTM *ptmSub = new OperatorPTM(OperatorPTM::Subtract);
OperatorPTM *ptmAlt[2] = { ptmAdd, ptmSub };
while (true) {
printf("--------------------\n");
test(dataset, switchAdd, "switch add");
test(dataset, switchSub, "switch subtract");
testalt(dataset, switchAlt, "switch alternating");
testmix(dataset, switchAlt, "switch mixed");
test(dataset, virtAdd, "virtual add");
test(dataset, virtSub, "virtual subtract");
testalt(dataset, virtAlt, "virtual alternating");
testmix(dataset, virtAlt, "virtual mixed");
test(dataset, ptmAdd, "ptm add");
test(dataset, ptmSub, "ptm subtract");
testalt(dataset, ptmAlt, "ptm alternating");
testmix(dataset, ptmAlt, "ptm mixed");
test_ptm(dataset, ptmAdd, "ptm add (direct)");
test_ptm(dataset, ptmSub, "ptm subtract (direct)");
testalt_ptm(dataset, ptmAlt, "ptm alternating (direct)");
testmix_ptm(dataset, ptmAlt, "ptm mixed (direct)");
}
}