通用优化
这里是我最喜欢的一些优化。通过使用这些,我实际上增加了执行时间并减少了程序大小。
将小函数声明为inline 或宏
对函数(或方法)的每次调用都会产生开销,例如将变量压入堆栈。某些函数也可能会在返回时产生开销。效率低下的函数或方法在其内容中的语句少于组合开销。无论是作为#define 宏还是inline 函数,这些都是内联的好候选。 (是的,我知道inline 只是一个建议,但在这种情况下,我认为它是对编译器的提醒。)
删除死代码和冗余代码
如果代码没有被使用或对程序的结果没有贡献,请删除它。
简化算法设计
我曾经写下程序计算的代数方程,然后简化代数表达式,从而从程序中删除了大量汇编代码和执行时间。简化代数表达式的实现比原函数占用更少的空间和时间。
循环展开
每个循环都有递增和终止检查的开销。要估计性能因素,请计算开销中的指令数(最少 3 个:递增、检查、转到循环开始)并除以循环内的语句数。数字越低越好。
编辑:提供循环展开的示例
之前:
unsigned int sum = 0;
for (size_t i; i < BYTES_TO_CHECKSUM; ++i)
{
sum += *buffer++;
}
展开后:
unsigned int sum = 0;
size_t i = 0;
**const size_t STATEMENTS_PER_LOOP = 8;**
for (i = 0; i < BYTES_TO_CHECKSUM; **i = i / STATEMENTS_PER_LOOP**)
{
sum += *buffer++; // 1
sum += *buffer++; // 2
sum += *buffer++; // 3
sum += *buffer++; // 4
sum += *buffer++; // 5
sum += *buffer++; // 6
sum += *buffer++; // 7
sum += *buffer++; // 8
}
// Handle the remainder:
for (; i < BYTES_TO_CHECKSUM; ++i)
{
sum += *buffer++;
}
在这个优势中,获得了第二个好处:在处理器必须重新加载指令缓存之前执行更多语句。
当我将一个循环展开为 32 条语句时,我得到了惊人的结果。这是瓶颈之一,因为程序必须计算 2GB 文件的校验和。这种优化与块读取相结合,将性能从 1 小时提高到 5 分钟。循环展开在汇编语言中也提供了出色的性能,我的memcpy 比编译器的memcpy 快很多。 -- T.M.
减少if 语句
处理器讨厌分支或跳转,因为它会强制处理器重新加载其指令队列。
布尔算术(编辑:将代码格式应用于代码片段,添加示例)
将if 语句转换为布尔赋值。一些处理器可以有条件地执行指令而无需分支:
bool status = true;
status = status && /* first test */;
status = status && /* second test */;
如果status 是false,逻辑与 运算符(&&) 的短路 会阻止执行测试。
例子:
struct Reader_Interface
{
virtual bool write(unsigned int value) = 0;
};
struct Rectangle
{
unsigned int origin_x;
unsigned int origin_y;
unsigned int height;
unsigned int width;
bool write(Reader_Interface * p_reader)
{
bool status = false;
if (p_reader)
{
status = p_reader->write(origin_x);
status = status && p_reader->write(origin_y);
status = status && p_reader->write(height);
status = status && p_reader->write(width);
}
return status;
};
循环外的因子变量分配
如果在循环中动态创建变量,请将创建/分配移至循环之前。在大多数情况下,不需要在每次迭代期间分配变量。
循环外的因子常量表达式
如果计算或变量值不依赖于循环索引,请将其移到循环之外(之前)。
块中的 I/O
以大块(块)读取和写入数据。越大越好。例如,一次读取一个 八位字节 的效率低于一次读取 1024 个八位字节的效率。
示例:
static const char Menu_Text[] = "\n"
"1) Print\n"
"2) Insert new customer\n"
"3) Destroy\n"
"4) Launch Nasal Demons\n"
"Enter selection: ";
static const size_t Menu_Text_Length = sizeof(Menu_Text) - sizeof('\0');
//...
std::cout.write(Menu_Text, Menu_Text_Length);
可以直观地展示这种技术的效率。 :-)
不要将printf family用于常量数据
可以使用块写入输出恒定数据。格式化写入将浪费时间扫描文本以格式化字符或处理格式化命令。参见上面的代码示例。
格式化到内存,然后写入
使用多个sprintf 格式化为char 数组,然后使用fwrite。这也允许将数据布局分解为“恒定部分”和可变部分。想想邮件合并。
将常量文本(字符串文字)声明为static const
在没有static 的情况下声明变量时,一些编译器可能会在堆栈上分配空间并从ROM 中复制数据。这是两个不必要的操作。这可以通过使用static 前缀来解决。
最后,像编译器这样的代码
有时,编译器可以优化几个小语句,而不是一个复杂的版本。此外,编写代码来帮助编译器优化也有帮助。如果我希望编译器使用特殊的块传输指令,我将编写看起来应该使用特殊指令的代码。