优化 C++/代码优化/指令计数
即使生成内联代码的语言特性也可能产生显著的成本,因为这些指令无论如何都要执行。本节介绍了一些减少处理器执行给定操作所需机器指令总数的技术。
在 switch
语句中,按概率降序排列 case。
在第 3.1 节的“switch
语句中的 case 排序”指南中,已经建议将最典型的 case(即被认为最有可能的 case)放在前面。作为进一步的优化,您可以在典型的运行中统计每个 case 被选择的实际次数,并按照从最频繁到最不频繁的顺序排序 case。 [存疑 ]
如果一个整数值在应用程序代码中是常量,但在库代码中是变量,请将其设置为模板参数。
假设您正在编写以下库函数,其中 x
和 y
在库开发时没有已知的值
int f1(int x, int y) { return x * y; }
该函数可能从以下应用程序代码中调用,其中 x
没有常数值,但 y
是常量 4
int a = f1(b, 4);
如果在编写库时,您知道调用方肯定会为参数 y
传递一个常量,您可以将函数转换为以下函数模板
template <int Y> int f2(int x) { return x * Y; }
该函数可能从以下应用程序代码中调用
int a = f2<4>(b);
这种调用会自动实例化以下函数
int f2(int x) { return x * 4; }
后一个函数比前一个函数 f1
更快,原因如下
- 只有一个参数传递给函数 (
x
),而不是两个 (x
和y
)。 - 用整数常量 (4) 乘法总是比用整数变量 (
y
) 乘法更快。 - 由于常数值 (4) 是 2 的幂,编译器会执行位移运算,而不是整数乘法。
一般来说,整数模板参数对于实例化模板的人(因此对于编译器)来说是常量,而常量比变量处理得更有效率。此外,一些涉及常量的操作在编译时预先计算。
如果已经有一个普通函数,而是函数模板,只需向该模板添加一个额外的参数即可。
如果您需要编写一个库抽象基类,以便在应用程序代码中的每个算法中仅使用一个从该基类派生的类,请使用 奇怪的递归模板模式。
假设您正在编写以下库基类
class Base {
public:
void g() { f(); }
private:
virtual void f() = 0;
};
在这个类中,函数 g
执行一个算法,该算法将函数 f
作为算法的抽象操作调用。用设计模式术语来说,g
是一个 模板方法 设计模式。该类的目的是允许编写以下应用程序代码
class Derived1: public Base {
private:
virtual void f() { ... }
};
...
Base* p1 = new Derived1;
p1->g();
在这种情况下,可以将以前的库代码转换为以下代码
template <class Derived> class Base {
public:
void g() { f(); }
private:
void f() { static_cast<Derived*>(this)->f(); }
};
因此,应用程序代码将变为以下代码
class Derived1: public Base<Derived1> {
private:
void f() { ... }
};
...
Derived1* p1 = new Derived1;
p1->g();
这样一来,Base<Derived1>::g
中对 f
的调用就静态绑定到成员函数 Derived1::f
,也就是说对该函数的调用不再是 virtual
,可以内联。
不过,假设您想要添加以下定义
class Derived2: public Base<Derived2> {
protected:
void f() { ... }
};
使用这种技术,不可能定义指向基类的指针或引用,该基类是 Derived1
和 Derived2
的公共基类,因为这些基类是两种不相关的类型;因此,当您想允许应用程序代码定义任意从类 Base 派生的对象的容器时,此技术不适用。
其他限制包括
Base
必须是抽象类型;- 类型
Derived1
的对象不能转换为类型Derived2
的对象,反之亦然; - 对于每个
Base
的派生类,为Base
生成的所有机器代码都会被复制。
如果实现 策略 设计模式(又名策略)的对象在应用程序代码的每个算法中都是常量,请消除该对象,将所有成员设置为 static
,并将它的类添加为模板参数。
假设您正在编写以下库代码,该代码实现了策略设计模式
class C;
class Strategy {
public:
virtual bool is_valid(const C&) const = 0;
virtual void run(C&) const = 0;
};
class C {
public:
void set_strategy(const Strategy& s) { s_ = s; }
void f() { if (s_.is_valid(*this)) s_.run(*this); }
private:
Strategy s_;
};
这个库代码的目的是允许以下应用程序代码
class MyStrategy: public Strategy {
public:
virtual bool is_valid(const C& c) const { ... }
virtual void run(C& c) const { ... }
};
...
MyStrategy s; // Object representing my strategy.
C c; // Object containing an algorithm with customizable strategy.
c.set_strategy(s); // Assignment of the custom strategy.
c.f(); // Execution of the algorithm with assigned strategy.
在这种情况下,可以将以前的库代码转换为以下代码
template <class Strategy>
class C {
public:
void f() {
if (Strategy::is_valid(*this)) Strategy::run(*this);
}
};
因此,应用程序代码将变为以下代码
class MyStrategy {
public:
static bool is_valid(const C<MyStrategy>& c) { ... }
static void run(C<MyStrategy>& c) { ... }
};
...
C<MyStrategy> c; // Object with statically assigned strategy.
c.f(); // Execution with statically assigned strategy.
这样一来,策略对象就消失了,成员函数 MyStrategy::is_valid
和 MyStrategy::run
就被静态绑定,也就是说避免了对 virtual
函数的调用。
不过,这种解决方案不允许在运行时选择策略,当然也不允许在对象生命周期内更改策略。此外,算法代码对于其类的每次实例化都会被复制。
如果您需要对一组位执行布尔运算,请将这些位放入一个 unsigned int
对象中,并在其上使用位运算符。
位运算符 (&
, |
, ^
, <<
和 >>
) 会被翻译成单个快速指令,并在单个指令中对寄存器的所有位进行操作。