通用优化
这是我最喜欢的一些优化。通过使用这些,实际上我增加了执行时间,并减小了程序大小。
将小函数声明为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
。 - TM值
减少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
家庭来获取恒定数据
可以使用块写入输出恒定数据。格式化写入将浪费时间扫描文本以格式化字符或处理格式化命令。参见上面的代码示例。
格式化到内存,然后写入
char
使用倍数将其格式化为数组sprintf
,然后使用fwrite
。这也允许将数据布局分为“恒定部分”和“可变部分”。想想邮件合并。
将常量文本(字符串文字)声明为 static const
当声明变量时不带时static
,某些编译器可能会在堆栈上分配空间并从ROM复制数据。这是两个不必要的操作。可以通过使用static
前缀来解决。
最后,像编译器一样的代码
有时,编译器可以比一个复杂的版本更好地优化几个小语句。此外,编写代码以帮助编译器优化也有帮助。如果我希望编译器使用特殊的块传输指令,我将编写看起来应该使用特殊指令的代码。