性能优化,我们应该了解更多

发布者:上海IT外包来源:http://www.lanmon.net点击数:1927

当我们谈论性能优化时,更多的学生可能会想到系统级性能优化。例如,在Web服务程序中,使用Redis或其他缓存来提高网站访问速度。程序代码本身的优化程度较低。在这方面,编译器为我们做了很多优化工作。另一方面,认为系统级的优化效果更明显,更高。实际上,除了系统级性能优化之外,程序代码级别的性能优化也非常好。
没什么废话,我们说实话。我们来看看以下两个程序。这两个程序具有完全相同的功能,即在二维数组中为每个元素添加1。我们来看看两个程序之间是否存在性能差异。实际测试结果几乎是两者之间性能差异的四倍。
性能优化,我们应该了解更多
分析性能差异的原因
让我们考虑为什么存在如此大的性能差异。结合代码,我们看到两段代码之间的区别在于访问数组元素的顺序,前者是逐列访问,后者是逐行访问。结合图1可以更清楚地理解这一点。然后,我们将C语言中的二维数据数据的规则结合起来(你可以通过在上面的代码中打印地址来验证它),你可以知道前者正在访问连续地址空间,而后者正在访问跳转。地址空间。
性能优化,我们应该了解更多
图1两种访问形式
以整形数组为例,即前者访问的地址是X,X + 4,X + 8等。后者访问地址X,X + 4096和X + 8192。后者每次跳过4KB的地址空间。
在了解了上述差异之后,您是否考虑过性能差异的原因?我们知道,为了提高访问内存的性能,CPU在它和内存之间添加了一个缓存。现代CPU缓存通常是3级缓存,即L1,L2和L3,其中L1和L2是CPU内核唯一的,L3由同一CPU的多个内核共享。基本架构如图2所示。
性能优化,我们应该了解更多
图2 CPU缓存架构
由于缓存分布的特性,有必要确保多个CPU之间的一致性。简而言之,需要削减缓存以便以相对较小的粒度进行管理。这种小粒度的管理单元称为缓存行(可以在页面缓存中缓存)。由于缓存容量远小于内存容量,缓存无法加载内存的内容。缓存可以工作的主要原因是所使用的传统业务访问数据的两个特征,即空间局部性和时间局部性。空间局部性:对于刚刚访问过的数据,将来访问其相邻数据的概率很高。
时间局部性:对于刚刚访问过的数据,将来访问自身的概率很高。
理解了上面的原理,我们知道对于上面的程序代码,因为第二个程序跳得太远,就是不满足空间局部性,导致缓存命中失败。换句话说,第二个程序不能访问缓存中的数据,而是直接访问内存。内存访问性能远低于缓存访问性能,因此在文章开头几乎有4倍的性能差异。
关于计划绩效的其他考虑因
对我们的程序进行非常小的更改会对性能产生非常大的影响。因此,我们应该注意代码中是否存在不适当的代码,导致日常开发中出现性能问题。下面我们列出了一个与性能相关的程序示例,以便您可以在将来的开发中参考它。
计划结构
不合理的计划结构对绩效的影响有时是灾难性的。如果字符串很长,则以下两个函数之间的性能差异将非常大。函数lower1计算每个循环中字符串的长度,并且不需要进行此计算。函数lower2在循环开始之前计算字符串的长度,然后使用常量变量进行条件判断。问题的根源在于strlen函数,它通过循环计算字符串的长度。如果字符串很长,则该功能将非常耗时。
性能优化,我们应该了解更多
2.进程(函数)调用
我们知道在调用过程时会有诸如推送堆栈和弹出之类的操作。这些操作通常是对内存的操作,并且过程很复杂。换句话说,函数的调用过程是一个耗时的操作,并且函数调用被最小化。
幸运的是,现代编译器可以对函数调用进行大量的优化工作,并且通常可以通过编译器优化简单的函数调用。所谓的优化键是在机器语言(汇编语言)级别没有高级语言的函数。
让我们看一个具体的例子,通过C语言实现一个简单的函数调用,其中函数fun_1调用函数fun_2,函数fun_2调用printf。这里fun_2没有太多工作,只需添加两个参数并将其传递给printf。
性能优化,我们应该了解更多
图3函数调用优化
如图所示,在gcc没有进行任何优化的情况下,反汇编代码(图3的左下角)显示整个逻辑非常清晰,只是对函数的逐步调用。但是,在优化-O2之后,汇编代码变得非常简洁(图3的右下角)。从fun_1的汇编代码可以看出它根本不调用fun_2,而是直接调用printf函数。因此,编译器可以优化函数的调用而不影响其功能。但这不是绝对的,稍微复杂的函数调用编译器可能无能为力,这可能导致性能损失。3.运营商差异
不同操作之间的耗时差异也非常大。例如,乘法时间是加法时间的两倍或三倍,除法时间是加法时间的十倍以上。因此,将显着改善在相对高的访问频率下减少逻辑划分的使用。
在Java的HashMap实现中,散列的Key是通过按位运算计算的,而不是通过模运算来计算的。因为模运算本身是除法运算,所以性能比位运算差十倍以上。
静态final int hash(Object key){
Int h;
返回(键==null)? 0:(h=key.hashCode())^(h>
>
>
16);
}
有关更详细的处理逻辑,请参阅JDK源代码。这篇文章只是一块砖头。
4.参考文献和副本
支持类的高级语言涉及传递对象参数时的复制过程,并且对象的副本也是相对昂贵的操作。当然,高级语言通过成为引用的机制实现对象地址的传输,从而避免了复制过程(这是值和地址之间的差异)。
在项目开发过程中,仍有很多关于绩效的问题。本文不能逐一列出。但是,关键问题是掌握技术的基本实施原则。任何其他高级内容都可以通过基本原则来解释。
IT外包
>
400-635-8089
立即
咨询
电话咨询
服务热线
400-635-8089
微信咨询
微信咨询
微信咨询
公众号
公众号
公众号
返回顶部