无止境的内存优化——停不下的循环

简介: 小伙伴们是不是跟我一样,以为之前的内存优化已经完成了?不,这才刚刚开始……让我们一起进入这无休止的循环吧!switch语句和查找表 / Switch statement vs. lookup tablesswitch语句通常用于以下情况:调用几个函数中的一个设置一个变量或返回值执行几个代码片断中的一个如果case表示是密集的,在使用switch语句的前两种情况中,可以使用效率更高的查找表。

小伙伴们是不是跟我一样,以为之前的内存优化已经完成了?不,这才刚刚开始……让我们一起进入这无休止的循环吧!

switch语句和查找表 / Switch statement vs. lookup tables

switch语句通常用于以下情况:

调用几个函数中的一个

设置一个变量或返回值

执行几个代码片断中的一个

如果case表示是密集的,在使用switch语句的前两种情况中,可以使用效率更高的查找表。比如下面的两个实现汇编代码转换成字符串的例程:

char * Condition_String1(int condition) {
    switch(condition) {
         case 0: return "EQ";
         case 1: return "NE";
         case 2: return "CS";
         case 3: return "CC";
         case 4: return "MI";
         case 5: return "PL";
         case 6: return "VS";
         case 7: return "VC";
         case 8: return "HI";
         case 9: return "LS";
         case 10: return "GE";
         case 11: return "LT";
         case 12: return "GT";
         case 13: return "LE";
         case 14: return "";
         default: return 0;
    }
}
char * Condition_String2(int condition) {
    if((unsigned) condition >= 15) return 0;
    return
          "EQ\0NE\0CS\0CC\0MI\0PL\0VS\0VC\0HI\0LS\0GE\0LT\0GT\0LE\0\0" +
           3 * condition;
}

第一个例程需要240个字节,第二个只需要72个。

循环终止 / Loop termination

如果不加留意地编写循环终止条件,就可能会给程序带来明显的负担。我们应该尽量使用“倒数到零”的循环,使用简单的循环终止条件。循环终止条件相对简单,程序在执行的时候也会消耗相对少的时间。拿下面两个计算n!的例子来说,第一个例子使用递增循环,第二个使用递减循环。

int fact1_func (int n)
{
    int i, fact = 1;
    for (i = 1; i <= n; i++)
        fact *= i;
    return (fact);
}
int fact2_func(int n)
{
    int i, fact = 1;
    for (i = n; i != 0; i--)
        fact *= i;
    return (fact);
}

结果是,第二个例子要比第一个快得多。

更快的for()循环 / Faster for() loops

这是一个简单而有效的概念,通常情况下,我们习惯把for循环写成这样:

for( i = 0;  i < 10;  i++){ ... }

i 值依次为:0,1,2,3,4,5,6,7,8,9

在不在乎循环计数器顺序的情况下,我们可以这样:

for( i = 10;  i--; ) { ... }

i 值依次为: 9,8,7,6,5,4,3,2,1,0,而且循环要更快

这种方法是可行的,因为它是用更快的i--作为测试条件的,也就是说“i是否为非零数,如果是减一,然后继续”。相对于原先的代码,处理器不得不“把i减去10,结果是否为非零数,如果是,增加i,然后继续”,在紧密循环(tight loop)中,这会产生显著的区别。

这种语法看起来有一点陌生,却完全合法。循环中的第三条语句是可选的(无限循环可以写成这样for(;;)),下面的写法也可以取得同样的效果:

for(i = 10;  i;  i--){}

或者:

for(i = 10;  i != 0;  i--){}

我们唯一要小心的地方是要记住循环需要停止在0(如果循环是从50-80,这样做就不行了),而且循环的计数器为倒计数方式。

另外,我们还可以把计数器分配到寄存器上,可以产生更为有效的代码。这种将循环计数器初始化成循环次数,然后递减到零的方法,同样适用于while和do语句。

混合循环/ Loop jamming 在可以使用一个循环的场合,决不要使用两个。但是如果你要在循环中进行大量的工作,超过处理器的指令缓冲区,在这种情况下,使用两个分开的循环可能会更快,因为有可能这两个循环都被完整的保存在指令缓冲区里了。

// 原先的代码
for(i = 0; i < 100; i++){
    stuff();
}
for(i = 0; i < 100; i++){
    morestuff();
}        
//更好的做法
for(i = 0; i < 100; i++){
    stuff();
    morestuff();
}

函数循环 / Function Looping

调用函数的时候,在性能上就会付出一定的代价。不光要改变程序指针,还要将那些正在使用的变量压入堆栈,分配新的变量空间。为了提高程序的效率,在程序的函数结构上,有很多工作可以做。保证程序的可读性的同时,还要尽量控制程序的大小。

如果一个函数在一个循环中被频繁调用,就可以考虑将这个循环放在函数的里面,这样可以免去重复调用函数的负担,比如:

for(i = 0 ; i < 100 ; i++) 
{ 
    func(t,i); 
}
void func(int w, d) 
{ 
    lots of stuff. 
}

可以写成:

func(t);
void func(w) 
{ 
    for(i = 0; i < 100; i++) { 
        //lots of stuff. 
    } 
}

展开循环 / Loop unrolling

为了提高效率,可以将小的循环解开,不过这样会增加代码的尺寸。循环被拆开后,会降低循环计数器更新的次数,减少所执行的循环的分支数目。如果循环只重复几次,那它完全可以被拆解开,这样,由循环所带来的额外开销就会消失。

比如:

for(i = 0; i < 3; i++){ 
    something(i);
}
//更高效的方式:
something(0);
something(1);
something(2);

因为在每次的循环中,i 的值都会增加,然后检查是否有效。编译器经常会把这种简单的循环解开,前提是这些循环的次数是固定的。对于这样的循环:

for(i = 0; i <  limit; i++) { ... }

就不可能被拆解,因为我们不知道它循环的次数到底是多少。不过,将这种类型的循环拆解开并不是不可能的。

与简单循环相比,下面的代码的长度要长很多,然而具有高得多的效率。选择8作为分块大小,只是用来演示,任何合适的长度都是可行的。例子中,循环的成立条件每八次才被检验一次,而不是每次都要检验。如果需要处理的数组的大小是确定的,我们就可以使用数组的大小作为分块的大小(或者是能够整除数组长度的数值)。不过,分块的大小跟系统的缓存大小有关。

#include<stdio.H> 
#define BLOCKSIZE (8) 
int main(void)
{ 
    int i = 0; 
    int limit = 33;  /* could be anything */ 
    int blocklimit;

    /* The limit may not be divisible by BLOCKSIZE, 
      go as near as we can first, then tidy up.
     */ 
    blocklimit = (limit / BLOCKSIZE) * BLOCKSIZE;

    /* unroll the loop in blocks of 8 */ 
    while(i < blocklimit) { 
        printf("process(%d)\n", i); 
        printf("process(%d)\n", i+1); 
        printf("process(%d)\n", i+2); 
        printf("process(%d)\n", i+3); 
        printf("process(%d)\n", i+4); 
        printf("process(%d)\n", i+5); 
        printf("process(%d)\n", i+6); 
        printf("process(%d)\n", i+7); 
        /* update the counter */ 
        i += 8; 
    } 
    /* 
     * There may be some left to do.
     * This could be done as a simple for() loop, 
     * but a switch is faster (and more interesting) 
     */ 
    if( i < limit ) 
    { 
        /* Jump into the case at the place that will allow
         * us to finish off the appropriate number of items. 
         */ 
        switch( limit - i ) 
        { 
            case 7 : printf("process(%d)\n", i); i++; 
            case 6 : printf("process(%d)\n", i); i++; 
            case 5 : printf("process(%d)\n", i); i++; 
            case 4 : printf("process(%d)\n", i); i++; 
            case 3 : printf("process(%d)\n", i); i++; 
            case 2 : printf("process(%d)\n", i); i++; 
            case 1 : printf("process(%d)\n", i); 
        }
    } 
    return 0;
}

经过惰性评估和二分分解煎熬,小编以为自己已经逃出生天了,哪知这才刚刚开始,小伙伴们,还请持续关注更新,更多干货和资料请直接联系我,也可以加群710520381,邀请码:柳猫,欢迎大家共同讨论

目录
相关文章
|
5月前
|
缓存 Java
《JVM由浅入深学习九】 2024-01-15》JVM由简入深学习提升分(生产项目内存飙升分析)
《JVM由浅入深学习九】 2024-01-15》JVM由简入深学习提升分(生产项目内存飙升分析)
49 0
|
3月前
|
编译器 开发者 UED
"解锁hyengine编译性能新纪元:快路径优化技术揭秘,直击热点代码效率瓶颈,让你的应用飞起来!"
【8月更文挑战第21天】hyengine是一款现代化编程引擎,其编译性能直接影响应用质量。针对直接翻译opcode至机器码效率低下的问题,hyengine采用快路径优化策略,专注于热点代码的深度优化,以减少运行时开销。例如,通过循环展开技术减少控制指令开销,智能判断循环展开时机与程度。此外,还运用函数内联等技术进一步提升性能,如内联小函数以减少调用开销。未来将持续探索更多优化方案,提升用户体验,并欢迎开发者共同参与技术进步。
38 2
|
6月前
|
设计模式 监控 安全
Java多线程编程优化实践
本文将探讨在Java多线程编程中如何进行优化实践,通过合理的设计和技巧,提高程序性能、避免常见的问题。从线程池的使用、锁的选择到并发数据结构的应用,全面介绍优化多线程编程的方法与技巧。
|
缓存 小程序 前端开发
小程序优化
评测步骤: 微信开发者工具­——》调试器——》Audits——》运行(操作)——》结束 点击运行以后,尽可能多的对小程序进行操作,最好覆盖全页面和所有的交互动作。操作完成以后,点击结束,出行评测结果以及评测的具体列表,需要优化的地方。
278 0
小程序优化
|
Arthas 缓存 安全
我是如何把一个15分钟的程序优化到了10秒的
今天这篇文章是讲性能优化的。前段时间我优化了一个程序,感觉收获还是蛮大的,所以总结了一些用到的优化思路,主要集中在代码层面,希望可以和大家一起交流探讨。
230 0
|
编译器 C语言 C++
编程之美-内存函数
memcpy内存拷贝 • 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。 • 这个函数在遇到 ‘\0’ 的时候并不会停下来。 • 如果source和destination有任何的重叠,复制的结果都是未定义的。
151 0
编程之美-内存函数
|
缓存 程序员
程序猿小枫的故事:while循环导致的CPU暴涨问题优化实践
程序猿小枫最近接到TL分配的新任务,维护一个之前的新应用,在开发新需求的同时,不免也需要排查一些前人代码中埋下的坑。这不最近就出现了线上环境服务CPU较高的情况,让我们一起来围观下程序猿小枫是怎么对CPU过高问题进行分析以及解决的。
程序猿小枫的故事:while循环导致的CPU暴涨问题优化实践
|
缓存
CPU是如何解决冒险问题的?(下)
CPU是如何解决冒险问题的?
294 0
CPU是如何解决冒险问题的?(下)
|
缓存 C语言
CPU是如何解决冒险问题的?(上)
CPU是如何解决冒险问题的?
404 0
CPU是如何解决冒险问题的?(上)
|
设计模式 SQL 监控
优化Java性能的五种方法
优化Java性能的五种方法