跳转到内容

优化 C++/代码优化/指令计数

来自维基教科书,开放的书籍,为开放的世界

即使生成内联代码的语言特性也可能产生显著的成本,因为这些指令无论如何都要执行。本节介绍了一些减少处理器执行给定操作所需机器指令总数的技术。

switch 语句中的 case 排序

[编辑 | 编辑源代码]

switch 语句中,按概率降序排列 case。

在第 3.1 节的“switch 语句中的 case 排序”指南中,已经建议将最典型的 case(即被认为最有可能的 case)放在前面。作为进一步的优化,您可以在典型的运行中统计每个 case 被选择的实际次数,并按照从最频繁到最不频繁的顺序排序 case。 [存疑 ]

如果一个整数值在应用程序代码中是常量,但在库代码中是变量,请将其设置为模板参数。

假设您正在编写以下库函数,其中 xy 在库开发时没有已知的值

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),而不是两个 (xy)。
  • 用整数常量 (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() { ... }
};

使用这种技术,不可能定义指向基类的指针或引用,该基类是 Derived1Derived2 的公共基类,因为这些基类是两种不相关的类型;因此,当您想允许应用程序代码定义任意从类 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_validMyStrategy::run 就被静态绑定,也就是说避免了对 virtual 函数的调用。

不过,这种解决方案不允许在运行时选择策略,当然也不允许在对象生命周期内更改策略。此外,算法代码对于其类的每次实例化都会被复制。

位运算符

[编辑 | 编辑源代码]

如果您需要对一组位执行布尔运算,请将这些位放入一个 unsigned int 对象中,并在其上使用位运算符。

位运算符 (&, |, ^, <<>>) 会被翻译成单个快速指令,并在单个指令中对寄存器的所有位进行操作。

华夏公益教科书