局部变量竟然比全局变量快 5 倍?(上)

简介: 局部变量竟然比全局变量快 5 倍?

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


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


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


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


<dependency>
   <groupId>org.openjdk.jmh</groupId>
   <artifactId>jmh-core</artifactId>
   <version>{version}</version>
</dependency>


然后编写测试代码:


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 < myChars.length; i++) {
            if (myChars[i] == 'c') {
                count++;
            }
        }
        return count;
    }
    @Benchmark
    public int localityVarTest() {
        char[] localityChars = myChars;
        int count = 0;
        for (int i = 0; i < localityChars.length; i++) {
            if (localityChars[i] == 'c') {
                count++;
            }
        }
        return count;
    }
}


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


image.png


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


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 < myChars.length; i++) {
            if (myChars[i] == 'c') {
                count++;
            }
        }
        return count;
    }
    @Benchmark
    public int localityVarTest() {
        char[] localityChars = myChars;
        int count = 0;
        for (int i = 0; i < localityChars.length; i++) {
            if (localityChars[i] == 'c') {
                count++;
            }
        }
        return count;
    }
}


最终的测试结果是:


image.png


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


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


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


image.png


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


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


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


image.png


(图片来源:cenalulu)


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


image.png

相关文章
|
2月前
初始化局部变量和全局变量
【10月更文挑战第3天】初始化局部变量和全局变量。
38 5
|
4月前
|
存储
全局变量和局部变量在堆和栈的区别
全局变量和局部变量在堆和栈的区别
475 0
|
存储 C语言 Perl
西门子S7-1200的变量如何使用?什么是局部变量和全局变量?临时变量和静态变量有什么区别?
今天给大家讲一下什么是局部变量、全局变量、临时变量、静态变量,这些变量都有什么区别,以及在西门子S7-1200中这些变量如何来使用。
西门子S7-1200的变量如何使用?什么是局部变量和全局变量?临时变量和静态变量有什么区别?
|
C++
39.【C/C++ 全局变量和局部变量 (详解)】
39.【C/C++ 全局变量和局部变量 (详解)】
88 0
|
存储 C语言 C++
函数的内部处理及全局变量和局部变量
函数的内部处理及全局变量和局部变量
129 0
函数的内部处理及全局变量和局部变量
|
存储
全局变量和局部变量
全局变量和局部变量
82 0
|
存储 数据可视化 小程序
在函数中使用局部变量并且通过局部变量返回函数值的方案来了
感谢小游戏可视化体验官群中的一位叫做“@天羽地王”的朋友提供的思路。本文内容主要包括如何在函数中使用局部变量并且通过局部变量来返回函数的结果值。 如果你没有任何的游戏开发经验,欢迎阅读我的“人人都能做游戏”系列教程,它会手把手的教你做出自己的第一个小游戏。
94 0
|
C++
【C++】局部变量和全局变量
有关C++局部变量和全局变量的记录
137 0
【C++】局部变量和全局变量
|
Java 编译器
成员变量和局部变量在内存中的变化
今天我们从成员变量和局部变量来加深印象,彻底弄懂成员变量和局部变量在内存中的变化
262 2
成员变量和局部变量在内存中的变化