如果我的某个对象Foo
的函数Bar()
可能包含或不包含任何实际功能,那么首先检查一个公共bool成员HasBar
以确定是否存在一个点调用Bar()
或只是调用该函数,如果它什么也不做就立即返回?
即。给出:
class Foo
{
public:
bool HasBar;
void Bar()
{
}
};
这样做更好:
Foo myFoo;
if (myFoo.HasBar)
{
myFoo.Bar();
}
..或者这个:
Foo myFoo;
myFoo.Bar();
通常情况下,我认为后者会被编译器优化掉,但我们讨论的是由驻留在运行时动态加载的DLL的工厂动态实例化的对象。
由于我已经可以感觉到“过早优化”火炬和干草叉的临近,让我提供一些背景信息(除了我很想知道的事实,无论我在做什么):
这是其他应用程序运行的引擎的一部分,因此它越轻便越好(任何优化都是好的)。将会有一个多态Foo
个对象(可能是数千个)的树,每秒迭代多次。每次迭代包括3次遍历树,并且在每次传递期间,在每个对象上调用不同的函数。
例如,在第一次传递期间,将在下一个Bar1()
和最后一个Bar2()
期间为每个对象调用Bar3()
。有些对象正在这些功能中工作,有些则没有。迭代器不知道,因此它必须根据传递盲目地调用适当的函数。问题背后的原因是什么 - 它应该首先检查当前对象是否有意义地实现了相应的功能(例如通过检查每个对象暴露的标志),还是应该只是调用它?
答案 0 :(得分:1)
如果没有进行优化,检查单个bool变量比进入函数便宜得多,在堆栈上分配东西并跳回。虽然这种优化还为时过早。
答案 1 :(得分:1)
这种方式应该没有太大的区别,因为你正在为有立即回报的电话交易条件。由于您的动态库是以任一方式链接的,因此无论您选择何种方式,都需要为链接付费。
使用布尔保护的方法可以节省一些CPU周期的唯一情况是当函数采用必须在调用之前评估的参数时,评估需要很长时间。
但是,使用guard的方法存在一个缺点:它引入了方法和布尔标志之间的耦合,因此无论如何实现它必须牢记这种连接。它在代码中并不明确,因此可以使用Bar
的良好和长期实现来编写类,这不会仅仅因为您忘记将Foo
切换为{{ 1}}。
很棒的是,这不是你需要事先决定的事情:编写代码并描述一个特别难以决定的方法。
答案 2 :(得分:1)
我测试了它,因为我很好奇。我不是C ++专家所以我只是测试了一些东西来混淆编译器,不要让他优化我的代码来破坏测试结果。我在使用Visual Studio 2012的Win8机器上进行了测试:
foo.h中
#pragma once
class Foo
{
public:
Foo(void);
~Foo(void);
bool HasBar;
int Bar();
};
Foo.cpp中
Foo::Foo(void) { }
Foo::~Foo(void) { }
int Foo::Bar()
{
int a = 1, b = 1;
return a + b;
}
主:
int _tmain(int argc, _TCHAR* argv[])
{
{
unsigned int start = clock();
// call it because the bool is true
for(int i = 0; i < 100000000; i++){
bool trueBool;
int sum = 0;
int expected = 0;
for(int j = i; j < 100 + i; j++){
sum +=j;
expected +=j;
}
trueBool = sum == expected;
Foo f1;
int returnedNumber;
if(f1.HasBar){
// same code as in Bar()
int a = 1, b = 1;
returnedNumber = a + b;
}
}
unsigned int end = clock();
printf("call it because the bool is true: %d", end - start);
}
{
unsigned int start = clock();
// dont call it
for(int i = 0; i < 100000000; i++){
bool trueBool;
int sum = 0;
int expected = 0;
for(int j = i; j < 100 + i; j++){
sum +=j;
expected +=j;
}
trueBool = sum != expected;
Foo f1;
int returnedNumber;
if(f1.HasBar){
// same code as in Bar()
int a = 1, b = 1;
returnedNumber = a + b;
}
}
unsigned int end = clock();
printf("dont call it: %d", end - start);
}
{
unsigned int start = clock();
// call it anyway
for(int i = 0; i < 100000000; i++){
bool trueBool;
int sum = 0;
int expected = 0;
for(int j = i; j < 100 + i; j++){
sum +=j;
expected +=j;
}
trueBool = sum == expected;
Foo f1;
int returnedNumber;
returnedNumber = f1.Bar();
}
unsigned int end = clock();
printf("call it anyway: %d", end - start);
}
return 0;
}
结果:
调用它,因为bool为真:32446 不要叫它(因为bool是假的):32713 无论如何都要打电话:35224
如果bool为真或假,那么对函数的调用比调用稍慢一点。
订单并不重要。如果我将“将其调用” - 测试结果移至顶部,则会出现类似的测试结果。对函数调用的测试总是比bool的断言慢。
答案 3 :(得分:1)
如果可以避免大部分呼叫,则检查bool
的速度会更快。检查bool
比函数调用要快很多,但是如果以这种方式编写,代码就不那么优雅了。
但是,如果您需要在批处理中的所有Foo
个对象上调用此函数,这可以让每个人都满意:
尝试使用预先分配的数组,该数组位于DLL内部,并使用标记为true的Foo
对象填充(您甚至可能不需要再存储bool)。这可以在Foo
个对象的一次传递中一次性填充。使HasBar
对象的Foo
的getter / setter从此容器中添加和删除可能是最优雅的。您必须以适合您代码的方式决定如何处理此细节。
现在,假设你有这样一个数组,你可以循环遍历它,在数组中的所有内容上调用Bar
。
这有许多优点:
Foo
的值时,仅由HasBar == true
具有HasBar
的对象进行缩放。你的情况更快吗?你必须尝试一下才能看到。