继续内存优化——一脸懵逼

简介: 如果说之前的优化部分,数据变量和全局局部变量已经让人头大,那接下来的部分肯定会让各位感受到迎面而来的清新气息。指针 / Pointers如果可能,我们应该使用结构体的引用作为参数,也就是结构体的指针,否则,整个结构体就会被压入堆栈,然后传递,这会降低速度。

如果说之前的优化部分,数据变量和全局局部变量已经让人头大,那接下来的部分肯定会让各位感受到迎面而来的清新气息。

指针 / Pointers

如果可能,我们应该使用结构体的引用作为参数,也就是结构体的指针,否则,整个结构体就会被压入堆栈,然后传递,这会降低速度。程序适用值传递可能需要几K字节,而一个简单的指针也可以达到同样的目的,只需要几个字节就可以了。 如果在函数内部不会改变结构体的内容,那么就应该将参数声明为const型的指针。举个例子:

void print_data_of_a_structure (const Thestruct  *data_pointer)
{
     ...printf contents of the structure...
}

这个例子代码告知编译器在函数内部不会改变外部结构体的内容,访问他们的时候,不需要重读。还可以确保编译器捕捉任何修改这个只读结构体的代码,给结构体以额外的保护。

指针链 / Pointer chains

指针链经常被用来访问结构体的信息,比如,下面的这段常见的代码:

typedef struct { int x, y, z; } Point3;
typedef struct { Point3 *pos, *direction; } Object;
void InitPos1(Object *p)
{
    p->pos->x = 0;
    p->pos->y = 0;
    p->pos->z = 0;
}

代码中,处理器在每次赋值操作的时候都要重新装载p->pos,因为编译器不知道p->pos->x不是p->pos的别名。更好的办法是将p->pos缓存成一个局部变量,如下:

void InitPos2(Object *p)
{ 
    Point3 *pos = p->pos;
    pos->x = 0; 
    pos->y = 0;
    pos->z = 0;
}

另一个可能的方法是将Point3结构体包含在Object结构体中,完全避免指针的使用。

条件的执行 / Conditional Execution

条件执行主要用在if语句中,同时也会用到由关系运算(<,==,>等)或bool运算(&&, !等)组成的复杂的表达式。尽可能的保持if和else语句的简单是有好处的,这样才能很好的条件化。关系表达式应该被分成包含相似条件的若干块。 下面的例子演示了编译器如何使用条件执行:

int g(int a, int b, int c, int d)
{
    if(a > 0 && b > 0 && c < 0 && d < 0)  //分组化的条件被捆绑在一起
        return a + b + c + d;
    return -1;
}

条件被分组,便以其能够条件化他们。

Boolean表达式和范围检查 / Boolean Expressions & Range checking

有一种常见的boolean表达式被用来检查是否一个变量取值在某个特定的范围内,比方说,检查一个点是否在一个窗口内。

bool PointInRectangelArea (Point p, Rectangle *r)
{
    return (p.x >= r->xmin && p.x < r->xmax && p.y >= r->ymin && p.y < r->ymax);
}

这里还有一个更快的方法:把(x >= min && x < max) 转换成 (unsigned)(x-min) < (max-min). 尤其是min为0时,更为有效。下面是优化后的代码:

bool PointInRectangelArea (Point p, Rectangle *r)
{
    return ((unsigned) (p.x - r->xmin) < r->xmax && (unsigned) (p.y - r->ymin) < r->ymax);
}

Boolean表达式&与零的比较 / Boolean Expressions & Compares with zero 在比较(CMP)指令后,相应的处理器标志位就会被设置。这些标志位也可以被其他的指令设置,诸如MOV, ADD, AND, MUL, 也就是基本的数学和逻辑运算指令(数据处理指令)。

假如一条数据处理指令要设置这些标志位,那么N和Z标志位的设置方法跟把数字和零比较的设置方法是一样的。N标志位表示结果是不是负数,Z标志位表示结果是不是零。

在C语言中,处理器中的N和Z标志位对应的有符号数的关系运算符是x < 0, x >= 0, x == 0, x != 0,无符号数对应的是x == 0, x != 0 (or x > 0)。

C语言中,每用到一个关系运算符,编译器就会产生一个比较指令。如果关系运算符是上面的其中一个,在数据处理指令紧跟比较指令的情况下,编译器就会将比较指令优化掉。比如:

int aFunction(int x, int y)
{
    if (x + y < 0)
        return 1;
    else
        return 0;
}

这样做,会在关键循环中节省比较指令,使代码长度减少,效率增加。C语言中没有借位(carry)标志位和溢出(overflow)标志位的概念,所以如果不使用内嵌汇编语言,要访问C和V标志位是不可能的。尽管如此,编译器支持借位标志位(无符号数溢出),比方说:

int sum(int x, int y)
{
     int res;
     res = x + y;
     if ((unsigned) res < (unsigned) x) // carry set?  //
        res++;
     return res;
}

惰性评估计算 / Lazy Evaluation Exploitation

在类似与这样的 if(a>10 && b=4) 语句中, 确保AND表达式的第一部分最有可能为false, 结果第二部分极有可能不被执行.

用switch() 代替if...else...,在条件选择比较多的情况下,可以用if…else…else…,像这样:

if( val == 1)
    dostuff1();
else if (val == 2)
    dostuff2();
else if (val == 3)
    dostuff3();

使用switch可以更快:

switch( val )
{
    case 1: dostuff1(); break;
    case 2: dostuff2(); break;
    case 3: dostuff3(); break;
}

在if语句中,即使是最后一个条件成立,也要先判断所有前面的条件是否成立。Switch语句能够去除这些额外的工作。如果你不得不使用if…else,那就把最可能的成立的条件放在前面。

二分分解 / Binary Breakdown

把判断条件做成二进制的风格,比如,不要使用下面的列表:

if(a == 1) { 
    } else if(a == 2) { 
    } else if(a == 3) { 
    } else if(a == 4) { 
    } else if(a == 5) { 
    } else if(a == 6) { 
    } else if(a == 7) { 
    } else if(a == 8) { 
    }
}

而采用:

if(a <= 4) { 
    if(a == 1) { 
    } else if(a == 2) { 
    } else if(a == 3) { 
    } else if(a == 4) { 
    } 
} else { 
    if(a == 5) { 
    } else if(a == 6) { 
    } else if(a == 7) { 
    } else if(a == 8) { 
    } 
}

甚至:

if(a <= 4) { 
    if(a <= 2) { 
        if(a == 1) { 
                /* a is 1 */ 
        } else { 
                /* a must be 2 */ 
        } 
    } else { 
        if(a == 3) { 
                /* a is 3 */ 
        } else { 
                /* a must be 4 */ 
        } 
    } 
} else { 
    if(a <= 6) { 
        if(a == 5) { 
                /* a is 5 */ 
        } else { 
                /* a must be 6 */ 
        } 
    } else { 
        if(a == 7) { 
                /* a is 7 */ 
        } else { 
                /* a must be 8 */ 
        } 
    } 
}

慢速、低效:

c = getch();
switch(c){
    case 'A': {
        do something;  
        break;  
    } 
    case 'H': {
        do something;
        break;
    }  
    case 'Z': { 
        do something; 
        break; 
    }
}

快速、高效:

c = getch();
switch(c) {
    case 0: {
        do something;
        break;
    }  
    case 1: {
        do something; 
        break;
    } 
    case 2: {
        do something; 
        break; 
    }
}

以上是两个case语句之间的比较

惰性评估和二分分解是小编心里挥不去的痛啊!!不要放弃,共同进步,还请持续关注更新,更多干货和资料请直接联系我,也可以加群710520381,邀请码:柳猫,欢迎大家共同讨论

目录
相关文章
|
7月前
|
安全 Java 编译器
《Java核心卷1》慢慢啃!读第3,4章 | 第12版
第三章 Java的基本程序设计结构 1、变量与运算
53 0
|
设计模式 运维 架构师
我懵了!架构描述是个啥玩意?
我懵了!架构描述是个啥玩意?
100 0
|
存储 算法 安全
看完这篇垃圾回收,和面试官扯皮没问题了
看完这篇垃圾回收,和面试官扯皮没问题了
|
存储 缓存 算法
学完了这篇JVM,面试官真拿我没办法了!
在我们面试中经常会遇到面试官问一些有关JVM的问题,下面我大概从运行时数据域、类加载机制、类加载器、垃圾收集器、垃圾收集算法、JVM堆内存模型、JVM内存结构、JVM调优等几个方面来讲一下JVM。
156 0
学完了这篇JVM,面试官真拿我没办法了!
|
算法
一看就懂,一写就懵?搞懂回溯算法,一口气刷了20多道题(中)
回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。——摘自《百度百科》
400 0
一看就懂,一写就懵?搞懂回溯算法,一口气刷了20多道题(中)
|
机器学习/深度学习 算法
一看就懂,一写就懵?搞懂回溯算法,一口气刷了20多道题(上)
回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。——摘自《百度百科》
217 0
一看就懂,一写就懵?搞懂回溯算法,一口气刷了20多道题(上)
|
自然语言处理 Oracle Java
拜托搞清楚,JVM是程序员必'备',不是必'背',每天20分钟带你从入门到揭秘JVM
大部分Java开发人员,除会在项目中使用到与Java平台相关的各种高精尖技术,对于Java技术的核心Java虚拟机了解甚少,一些有一定工作经验的开发人员,打心眼儿里觉得SSM、微服务等上层技术才是重点,基础技术并不重要,这其实是一种本末倒置的“病态”。如果我们把核心类库的API比做数学公式的话,那么Java虚拟机的知识就好比公式的推导过程。
257 0
拜托搞清楚,JVM是程序员必'备',不是必'背',每天20分钟带你从入门到揭秘JVM
|
网络协议 Linux
又被鹅厂搞懵了!
客户端(主动关闭方)在 TIME_WAIT 状态下,如果收到服务端的数据包时,会怎么处理?
又被鹅厂搞懵了!
|
存储 编译器
懂了嘎嘎乱杀,但我赌你会懵——指针进阶终极版
正片开始👀 细化指针这一部分内容,现在着重把一些指针的运用情景搬出来康康,如果对指针盘的非常熟练了,或者指针还出于入门阶段的铁子请绕道(晕头警告) 直接给大家盘个套餐: 一维数组👏
懂了嘎嘎乱杀,但我赌你会懵——指针进阶终极版
|
存储 缓存 运维
JVM面试题,面试官直呼内行!!!
这篇文章给大家分享一下Java中的核心技术JVM。作为一个Java程序员,相比或多或少的都会接触到一些关于Java底层的知识,这些底层知识是非常重要的,相比之下这些知识也是比较难以理解的