全局变量_局部变量竟然比全局变量快 5 倍?

哈喽,大家好,磊哥的性能优化篇又来了!

其实写这个性能优化类的文章初衷也很简单,第一:目前市面上没有太好的关于性能优化的系列文章,包括一些付费的文章;第二:我需要写一些和别人不同的知识点,比如大家都去写 SpringBoot 了,那我就不会把重点全部放在 SpringBoot 上。而性能优化方面的文章又比较少,因此这就是我写它的理由。

至于能不能用上?是不是刚需?我想每个人都有自己的答案。就像一个好的剑客,终其一生都会对宝剑痴迷,我相信读到此文的你也是一样。

回到今天的主题,这次我们来评测一下局部变量和全局变量的性能差异,首先我们先在项目中先添加 Oracle 官方提供的 JMH(Java Microbenchmark Harness,JAVA 微基准测试套件)测试框架,配置如下:

   org.openjdk.jmh   jmh-core   {version}

然后编写测试代码:

import org.openjdk.jmh.annotations.*;import org.openjdk.jmh.runner.Runner;import org.openjdk.jmh.runner.RunnerException;import org.openjdk.jmh.runner.options.Options;import org.openjdk.jmh.runner.options.OptionsBuilder;import java.util.concurrent.TimeUnit;@BenchmarkMode(Mode.AverageTime) // 测试完成时间@OutputTimeUnit(TimeUnit.NANOSECONDS)@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 2 轮,每次 1s@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 3s@Fork(1) // fork 1 个线程@State(Scope.Thread) // 每个测试线程一个实例public class VarOptimizeTest {    char[] myChars = ("Oracle Cloud Infrastructure Low data networking fees and " +            "automated migration Oracle Cloud Infrastructure platform is built for " +            "enterprises that are looking for higher performance computing with easy " +            "migration of their on-premises applications to the Cloud.").toCharArray();    public static void main(String[] args) throws RunnerException {        // 启动基准测试        Options opt = new OptionsBuilder()                .include(VarOptimizeTest.class.getSimpleName()) // 要导入的测试类                .build();        new Runner(opt).run(); // 执行测试    }    @Benchmark    public int globalVarTest() {        int count = 0;        for (int i = 0; i 

其中 globalVarTest 方法使用的是全局变量 myChars 进行循环遍历的,而 localityVarTest 方法使用的是局部变量 localityChars 来进行遍历循环的,使用 JMH 测试的结果如下:

2e54cfb5d5210003e9bc3c4fbf7be177.png

咦,什么鬼?这两个方法的性能不是差不多嘛!为毛,你说差 5 倍?

25608a63f0ba14a93c92256e0b2aef63.gif

CPU Cache

上面的代码之所以性能差不多其实是因为,全局变量 myChars 被 CPU 缓存了,每次我们查询时不会直接从对象的实例域(对象的实际存储结构)中查询的,而是直接从 CPU 的缓存中查询的,因此才有上面的结果。

为了还原真实的性能(局部变量和全局变量),因此我们需要使用 volatile 关键来修饰 myChars 全局变量,这样 CPU 就不会缓存此变量了, volatile 原本的语义是禁用 CPU 缓存的,我们修改的代码如下:

import org.openjdk.jmh.annotations.*;import org.openjdk.jmh.runner.Runner;import org.openjdk.jmh.runner.RunnerException;import org.openjdk.jmh.runner.options.Options;import org.openjdk.jmh.runner.options.OptionsBuilder;import java.util.concurrent.TimeUnit;@BenchmarkMode(Mode.AverageTime) // 测试完成时间@OutputTimeUnit(TimeUnit.NANOSECONDS)@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 2 轮,每次 1s@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 3s@Fork(1) // fork 1 个线程@State(Scope.Thread) // 每个测试线程一个实例public class VarOptimizeTest {    volatile char[] myChars = ("Oracle Cloud Infrastructure Low data networking fees and " +            "automated migration Oracle Cloud Infrastructure platform is built for " +            "enterprises that are looking for higher performance computing with easy " +            "migration of their on-premises applications to the Cloud.").toCharArray();    public static void main(String[] args) throws RunnerException {        // 启动基准测试        Options opt = new OptionsBuilder()                .include(VarOptimizeTest.class.getSimpleName()) // 要导入的测试类                .build();        new Runner(opt).run(); // 执行测试    }    @Benchmark    public int globalVarTest() {        int count = 0;        for (int i = 0; i 

最终的测试结果是:

369a7c5177b2f0dd5cbcfbd71e695151.png

从上面的结果可以看出,局部变量的性能比全局变量的性能快了大约 5.02 倍

至于为什么局部变量会比全局变量快?咱们稍后再说,我们先来聊聊 CPU 缓存的事。

在计算机系统中,CPU 缓存(CPU Cache)是用于减少处理器访问内存所需平均时间的部件。在金字塔式存储体系中它位于自顶向下的第二层,仅次于 CPU 寄存器,如下图所示:

d2a0ee9407da04f66998c75fbdc513d8.png

CPU 缓存的容量远小于内存,但速度却可以接近处理器的频率。当处理器发出内存访问请求时,会先查看缓存内是否有请求数据。如果存在(命中),则不经访问内存直接返回该数据;如果不存在(失效),则要先把内存中的相应数据载入缓存,再将其返回处理器。

CPU 缓存可以分为一级缓存(L1),二级缓存(L2),部分高端 CPU 还具有三级缓存(L3),这三种缓存的技术难度和制造成本是相对递减的,所以其容量也是相对递增的。当 CPU 要读取一个数据时,首先从一级缓存中查找,如果没有找到再从二级缓存中查找,如果还是没有就从三级缓存或内存中查找。

以下是各级缓存和内存响应时间的对比图:

354644f8e23507b5f7ff4f292140c1df.png

(图片来源:cenalulu)

从上图可以看出内存的响应速度要比 CPU 缓存慢很多

3decb25f9245406587523ace69a8fc2d.gif

局部变量为什么快?

要理解为什么局部变量会比全局变量快这个问题,我们只需要使用 javac 把他们编译成字节码就可以找到原因了,编译的字节码如下:

javap -c VarOptimize警告: 文件 ./VarOptimize.class 不包含类 VarOptimizeCompiled from "VarOptimize.java"public class com.example.optimize.VarOptimize {  char[] myChars;  public com.example.optimize.VarOptimize();    Code:       0: aload_0       1: invokespecial #1                  // Method java/lang/Object."":()V       4: aload_0       5: ldc           #7                  // String Oracle Cloud Infrastructure Low data networking fees and automated migration Oracle Cloud Infrastructure platform is built for enterprises that are looking for higher performance computing with easy migration of their on-premises applications to the Cloud.       7: invokevirtual #9                  // Method java/lang/String.toCharArray:()[C      10: putfield      #15                 // Field myChars:[C      13: return  public static void main(java.lang.String[]);    Code:       0: new           #16                 // class com/example/optimize/VarOptimize       3: dup       4: invokespecial #21                 // Method "":()V       7: astore_1       8: aload_1       9: invokevirtual #22                 // Method globalVarTest:()V      12: aload_1      13: invokevirtual #25                 // Method localityVarTest:()V      16: return  public void globalVarTest();    Code:       0: iconst_0       1: istore_1       2: iconst_0       3: istore_2       4: iload_2       5: aload_0       6: getfield      #15                 // Field myChars:[C       9: arraylength      10: if_icmpge     33      13: aload_0      14: getfield      #15                 // Field myChars:[C      17: iload_2      18: caload      19: bipush        99      21: if_icmpne     27      24: iinc          1, 1      27: iinc          2, 1      30: goto          4      33: return  public void localityVarTest();    Code:       0: aload_0       1: getfield      #15                 // Field myChars:[C       4: astore_1       5: iconst_0       6: istore_2       7: iconst_0       8: istore_3       9: iload_3      10: aload_1      11: arraylength      12: if_icmpge     32      15: aload_1      16: iload_3      17: caload      18: bipush        99      20: if_icmpne     26      23: iinc          2, 1      26: iinc          3, 1      29: goto          9      32: return}

其中关键的信息就在 getfield 关键字上,getfield 在此处的语义是从堆上获取变量,从上述的字节码可以看出 globalVarTest 方法在循环的内部每次都通过 getfield 关键字从堆上获取变量,而 localityVarTest 方法并没有使用 getfield 关键字,而是使用了出栈操作来进行业务处理,而从堆中获取变量比出栈操作要慢很多,因此使用全局变量会比局部变量慢很多。关于堆、栈的内容关注公众号「Java中文社群」我在后面的 JVM 优化的章节会单独讲解。

关于缓存

有人可能会说无所谓,反正使用全局变量会使用 CPU Cache,这样性能也和局部变量差不多,那我就随便用吧,反正也差不多。

但磊哥的建议是,能用局部变量的绝不使用全局变量,因为 CPU 缓存有以下 3 个问题:

  1. CPU Cache 采用的是 LRU 和 Random 的清除算法,不常使用的缓存和随机抽取一部分缓存会被删除掉,如果正好是你用的那个全局变量呢?
  2. CPU Cache 有缓存命中率的问题,也就是有一定的几率会访问不到缓存;
  3. 部分 CPU 只有两级缓存(L1 和 L2),因此可以使用的空间是有限的。

综上所述,我们不能把程序的执行性能完全托付给一个不那么稳定的系统硬件,所以能用局部变量坚决不要使用全局变量

关键点:编写适合你的代码,在性能、可读性和实用性之间,找到属于你的平衡点!

总结

本文我们讲了局部变量的和全局变量的区别,如果使用全局变量会用 getfield 关键字从堆中获取变量,而局部变量则是通过出栈来获取变量的,因为出栈操作要比堆操作快很多,因此局部变量操作也会比全局变量快很多,所以建议你使用局部变量而不是全局变量。

高手之间对决,比拼的就是细节。

原创不易,觉得有帮助,点个「赞」让我知道,谢谢你!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【优质项目推荐】 1、项目代码均经过严格本地测试,运行OK,确保功能稳定后才上传平台。可放心下载并立即投入使用,若遇到任何使用问题,随时欢迎私信反馈与沟通,博主会第一时间回复。 2、项目适用于计算机相关专业(如计科、信息安全、数据科学、人工智能、通信、物联网、自动化、电子信息等)的在校学生、专业教师,或企业员工,小白入门等都适用。 3、该项目不仅具有很高的学习借鉴价值,对于初学者来说,也是入门进阶的绝佳选择;当然也可以直接用于 毕设、课设、期末大作业或项目初期立项演示等。 3、开放创新:如果您有一定基础,且热爱探索钻研,可以在此代码基础上二次开发,进行修改、扩展,创造出属于自己的独特应用。 欢迎下载使用优质资源!欢迎借鉴使用,并欢迎学习交流,共同探索编程的无穷魅力! 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值