Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >cache line对代码性能的影响

cache line对代码性能的影响

作者头像
程序那些事
发布于 2020-07-08 08:32:37
发布于 2020-07-08 08:32:37
48100
代码可运行
举报
文章被收录于专栏:程序那些事程序那些事
运行总次数:0
代码可运行

简介

读万卷书不如行万里路,讲了这么多assembly和JVM的原理与优化,今天我们来点不一样的实战。探索一下怎么使用assembly来理解我们之前不能理解的问题。

一个奇怪的现象

小师妹:F师兄,之前你讲了那么多JVM中JIT在编译中的性能优化,讲真的,在工作中我们真的需要知道这些东西吗?知道这些东西对我们的工作有什么好处吗?

um…这个问题问得好,知道了JIT的编译原理和优化方向,我们的确可以在写代码的时候稍微注意一下,写出性能更加优秀的代码,但是这只是微观上了。

如果将代码上升到企业级应用,一个硬件的提升,一个缓存的加入或者一种架构的改变都可能比小小的代码优化要有用得多。

就像是,如果我们的项目遇到了性能问题,我们第一反应是去找架构上面有没有什么缺陷,有没有什么优化点,很少或者说基本上不会去深入到代码层面,看你的这个代码到底有没有可优化空间。

第一,只要代码的业务逻辑不差,运行起来速度也不会太慢。

第二,代码的优化带来的收益实在太小了,而工作量又非常庞大。

所以说,对于这种类似于鸡肋的优化,真的有必要存在吗?

其实这和我学习物理化学数学知识是一样的,你学了那么多知识,其实在日常生活中真的用不到。但是为什么要学习呢?

我觉得有两个原因,第一是让你对这个世界有更加本质的认识,知道这个世界是怎么运行的。第二是锻炼自己的思维习惯,学会解决问题的方法。

就像算法,现在写个程序真的需要用到算法吗?不见得,但是算法真的很重要,因为它可以影响你的思维习惯。

所以,了解JVM的原理,甚至是Assembly的使用,并不是要你用他们来让你的代码优化的如何好,而是让你知道,哦,原来代码是这样工作的。在未来的某一个,或许我就可能用到。

好了,言归正传。今天给小师妹介绍一个很奇怪的例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private static int[] array = new int[64 * 1024 * 1024];

    @Benchmark
    public void test1() {
        int length = array.length;
        for (int i = 0; i < length; i=i+1)
            array[i] ++;
    }
    @Benchmark
    public void test2() {
        int length = array.length;
        for (int i = 0; i < length; i=i+2)
            array[i] ++;
    }

小师妹,上面的例子,你觉得哪一个运行的更快呢?

小师妹:当然是第二个啦,第二个每次加2,遍历的次数更少,肯定执行得更快。

好,我们先持保留意见。

第二个例子,上面我们是分别+1和+2,如果后面再继续+3,+4,一直加到128,你觉得运行时间是怎么样的呢?

小师妹:肯定是线性减少的。

好,两个问题问完了,接下来让我们来揭晓答案吧。

两个问题的答案

我们再次使用JMH来测试我们的代码。代码很长,这里就不列出来了,有兴趣的朋友可以到本文下面的代码链接下载运行代码。

我们直接上运行结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Benchmark               Mode  Cnt   Score   Error  Units
CachelineUsage.test1    avgt    5  27.499 ± 4.538  ms/op
CachelineUsage.test2    avgt    5  31.062 ± 1.697  ms/op
CachelineUsage.test3    avgt    5  27.187 ± 1.530  ms/op
CachelineUsage.test4    avgt    5  25.719 ± 1.051  ms/op
CachelineUsage.test8    avgt    5  25.945 ± 1.053  ms/op
CachelineUsage.test16   avgt    5  28.804 ± 0.772  ms/op
CachelineUsage.test32   avgt    5  21.191 ± 6.582  ms/op
CachelineUsage.test64   avgt    5  13.554 ± 1.981  ms/op
CachelineUsage.test128  avgt    5   7.813 ± 0.302  ms/op

好吧,不够直观,我们用一个图表来表示:

从图表可以看出,步长在1到16之间的时候,执行速度都还相对比较平稳,在25左右,然后就随着步长的增长而下降。

01

CPU cache line

那么我们先回答第二个问题的答案,执行时间是先平稳再下降的。

为什么会在16步长之内很平稳呢?

CPU的处理速度是有限的,为了提升CPU的处理速度,现代CPU都有一个叫做CPU缓存的东西。

而这个CPU缓存又可以分为L1缓存,L2缓存甚至L3缓存。

其中L1缓存是每个CPU核单独享有的。在L1缓存中,又有一个叫做Cache line的东西。为了提升处理速度,CPU每次处理都是读取一个Cache line大小的数据。

怎么查看这个Cache line的大小呢?

在mac上,我们可以执行:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
sysctl machdep.cpu

从图中我们可以得到,机子的CPU cache line是64byte,而cpu的一级缓存大小是256byte。

好了,现在回到为什么1-16步长执行速度差不多的问题。

我们知道一个int占用4bytes,那么16个int刚好占用64bytes。所以我们可以粗略的认为,1-16步长,每次CPU取出来的数据是一样的,都是一个cache line。所以,他们的执行速度其实是差不多的。

02

inc 和 add

小师妹:F师兄,上面的解释虽然有点完美了,但是好像还有一个漏洞。既然1-16使用的是同一个cache line,那么他们的执行时间,应该是逐步下降才对,为什么2比1执行时间还要长呢?

这真的是一个好问题,光看代码和cache line好像都解释不了,那么我们就从Assembly的角度再来看看。

还是使用JMH,打开PrintAssembly选项,我们看看输出结果。

先看下test1方法的输出:

再看下test2方法的输出:

两个有什么区别呢?

基本上的结构都是一样的,只不过test1使用的是inc,而test2方法使用的add。

本人对汇编语言不太熟,不过我猜两者执行时间的差异在于inc和add的差异,add可能会执行慢一点,因为它多了一个额外的参数。

总结

Assembly虽然没太大用处,但是在解释某些神秘现象的时候,还是挺好用的。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-07-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序那些事 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
JMH - Java 代码性能测试的终极利器、必须掌握
现在的 JVM 已经越来越为智能,它可以在编译阶段、加载阶段、运行阶段对代码进行优化。比如你写了一段不怎么聪明的代码,到了 JVM 这里,它发现几处可以优化的地方,就顺手帮你优化了一把。这对程序的运行固然美妙,却让开发者不能准确了解程序的运行情况。在需要进行性能测试时,如果不知道 JVM 优化细节,可能会导致你的测试结果差之毫厘,失之千里,同样的,Java 诞生之初就有一次编译、随处运行的口号,JVM 提供了底层支持,也提供了内存管理机制,这些机制都会对我们的性能测试结果造成不可预测的影响。
未读代码
2020/08/25
5.8K0
JAVA拾遗 — JMH与8个代码陷阱
JMH (http://openjdk.java.net/projects/code-tools/jmh/) 是 Java Microbenchmark Harness(微基准测试)框架的缩写(2013年首次发布)。与其他众多测试框架相比,其特色优势在于它是由 Oracle 实现 JIT 的相同人员开发的。在此,我想特别提一下 Aleksey Shipilev (http://shipilev.net/)(JMH 的作者兼布道者)和他优秀的博客文章。笔者花费了一个周末,将 Aleksey 大神的博客,特别是那些和 JMH 相关的文章通读了一遍,外加一部公开课视频 《"The Lesser of Two Evils" Story》 ,将自己的收获归纳在这篇文章中,文中不少图片都来自 Aleksey 公开课视频。
kirito-moe
2018/09/30
1.6K1
JAVA拾遗 — JMH与8个代码陷阱
深入理解编译优化之循环展开和粗化锁
之前在讲JIT的时候,有提到在编译过程中的两种优化循环展开和粗化锁,今天我们和小师妹一起从Assembly的角度来验证一下这两种编译优化方法,快来看看吧。
程序那些事
2020/07/08
8860
深入理解编译优化之循环展开和粗化锁
try catch 对性能影响
之前一直没有去研究try catch的内部机制,只是一直停留在了感觉上,正好这周五开会交流学习的时候,有人提出了相关的问题。借着周末,正好研究一番。
全栈程序员站长
2022/07/04
1.6K0
try catch 对性能影响
一文告诉你CPU分支预测对性能影响有多大
来源于stackoverflow上的一个问题为什么处理有序数组比处理无需数组快,原文中已经有了一些探讨,这里我们首先来复现下结果,然后再解释下为什么!
xindoo
2021/01/21
1.6K0
性能调优必备利器之 JMH
if 快还是 switch 快?HashMap 的初始化 size 要不要指定,指定之后性能可以提高多少?各种序列化方法哪个耗时更短?
用户4172423
2020/06/12
5400
JUC学习之预热知识
同一台计算机的进程通信称为 IPC(Inter-process communication) 不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP
大忽悠爱学习
2021/12/13
6530
JUC学习之预热知识
不要再用main方法测试代码性能了,用这款JDK自带工具
作为软件开发人员,我们通常会写一些测试程序用来对比不同算法、不同工具的性能问题。而最常见的做法是写一个main方法,构造模拟场景进行并发测试。
程序新视界
2021/01/13
4600
关于CPU Cache -- 程序猿需要知道的那些事
随着工艺的提升最近几十年CPU的频率不断提升,而受制于制造工艺和成本限制,目前计算机的内存主要是DRAM并且在访问速度上没有质的突破。因此,CPU的处理速度和内存的访问速度差距越来越大,甚至可以达到上万倍。这种情况下传统的CPU通过FSB直连内存的方式显然就会因为内存访问的等待,导致计算资源大量闲置,降低CPU整体吞吐量。同时又由于内存数据访问的热点集中性,在CPU和内存之间用较为快速而成本较高的SDRAM做一层缓存,就显得性价比极高了。
Linux阅码场
2019/10/08
8250
关于CPU Cache -- 程序猿需要知道的那些事
Java性能测试利器:JMH入门与实践|得物技术
在软件开发中,性能测试是不可或缺的一环。但是编写基准测试来正确衡量大型应用程序的一小部分的性能却又非常困难。当基准测试单独执行组件时,JVM或底层硬件可能会对您的组件应用许多优化。当组件作为大型应用程序的一部分运行时,这些优化可能无法应用。因此,实施不当的微基准测试可能会让您相信组件的性能比实际情况更好。编写正确的Java微基准测试通常需要防止JVM和硬件在微基准测试执行期间应用的优化,而这些优化在实际生产系统中是无法应用的。这就是JMH(Java 微基准测试工具)可以帮助您实现的功能。这篇文章我会全面给大家介绍下JMH的各个方面。
得物技术
2024/11/21
1690
Java并发计数器探秘
一提到线程安全的并发计数器,AtomicLong 必然是第一个被联想到的工具。Atomic* 一系列的原子类以及它们背后的 CAS 无锁算法,常常是高性能,高并发的代名词。本文将会阐释,在并发场景下,使用 AtomicLong 来充当并发计数器将会是一个糟糕的设计,实际上存在不少 AtomicLong 之外的计数器方案。近期我研究了一些 Jdk1.8 以及 JCTools 的优化方案,并将它们的对比与实现细节整理于此。
kirito-moe
2018/09/30
1.5K0
Java并发计数器探秘
string和stringbuffer和stringbuilder的性能(Java是什么意思)
当然,除了验证三者的字符串拼接效率之外,还会对这三者的特性及常见面试问题进行分析和总结,希望加深自己对这三者的认知,分享出来,也希望能帮助到有需要的小伙伴~
全栈程序员站长
2022/07/26
4540
string和stringbuffer和stringbuilder的性能(Java是什么意思)
7 个示例科普 CPU Cache
CPU Cache一直是理解计算机体系架构的重要知识点,也是并发编程设计中的技术难点,而且相关参考资料如同过江之鲫,浩瀚繁星,阅之如临深渊,味同嚼蜡,三言两语难以入门。正好网上有人推荐了微软大牛Igor Ostrovsky一篇博文《漫游处理器缓存效应》,文章不仅仅用7个最简单的源码示例就将CPU cache的原理娓娓道来,还附加图表量化分析做数学上的佐证,个人感觉这种案例教学的切入方式绝对是俺的菜,故而忍不住贸然译之,以飨列位看官。
逆锋起笔
2021/10/19
5730
JMH基准测试
使用JMH就可以回答第一个问题。JMH是方法级别的性能测试工具,并且是openjdk官方开发的(值得信赖),它有很多针对性能测试的功能,例如预热,该功能就可以解决StopWatch测试不准确的问题。
Yuyy
2022/09/21
3140
性能测试JMH
JMH,即(Java Microbenchmark Harness) 用于代码微基准测试的工具套件,主要是基于方法层面的基准测试,精度可以达到纳秒级。 基准测试:是指通过设计科学的测试方法、测试工具和测试系统,实现对一类测试对象的某项性能指标进行定量的和可对比的测试。
全栈程序员站长
2022/09/18
5060
JVM中的Safepoints
java程序员都听说过GC,大家也都知道GC的目的是扫描堆空间,然后将那些标记为删除的对象从堆空间释放,以提升可用的堆空间。
程序那些事
2020/07/08
5880
【底层原理】深入理解Cache (下)
得到了我的PC的cache参数如下: L1 Cache : 32KB , 8路组相连,linesize为 64Byte 64个组
233333
2018/11/22
6570
多核环境下cache line的测试
前阵子接触到一道关于数组内部链表(多用于内存池技术)的数据结构的题, 这种数据结构能够比普通链表在cache中更容易命中, 理由很简单, 就是因为其在地址上是连续的(=.=!), 借这个机会, 就对cpu cache进行了一个研究, 今天做一个简单的分享, 首先先来普及一下cpu cache的知识, 这里的cache是指cpu的高速缓存. 在我们程序员看来, 缓存是一个透明部件. 因此, 程序员通常无法直接干预对缓存的操作. 但是, 确实可以根据缓存的特点对程序代码实施特定优化, 从而更好地利用高速缓存. 
猿人谷
2018/01/17
1.6K0
你也许还不知道的 CPU Cache
Gallery of Processor Cache Effects 用 7 个源码示例生动的介绍 cache 原理,深入浅出!但是可能因操作系统的差异、编译器是否优化,以及近些年 cache 性能的提升,第 3 个样例在 Mac 的效果与原文相差较大。另外 Berkeley 公开课 CS162 图文并茂,非常推荐。本文充当搬运工的角色,集二者之精华科普 CPU cache 知识。
架构师修行之路
2020/06/04
1.2K0
更高效的反射调用方式被我找到了!
在使用Java进行开发时,我们会不可避免的使用到大量的反射操作,比如Spring Boot会在接收到HTTP请求时,利用反射Controller调用接口中的对应方法,或是Jackson框架使用反射来解析json中的数据给对应字段进行赋值,我们可以编写一个简单的JMH测试来评估一下通过反射调用来创建对象的性能,与直接调用对象构造方法之间的差距:
程序员波特
2024/03/21
3440
相关推荐
JMH - Java 代码性能测试的终极利器、必须掌握
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验