通过Java视角简单谈谈局部性原理

局部性原理

程序在访问数据时,都趋于聚集在一片连续的区域中,这被称为局部性原理。

按时间和空间划分为两类:

  • 时间局部性:如果一个数据正在被访问,那么近期它很可能再次被访问。
  • 空间局部性:如果某一个位置的数据被访问,那么这个问题附近的数据很可能被访问。

针对局部性原理,CPU和操作系统都有具体的实现。

本文主要总结梳理CPU和操作系统的局部性原理在Java后端中的影响与意义。

CPU空间局部性

如下图是Java的内存模型

我们知道CPU为提高从内存中读数据的性能,有L1、L2、L3三个级别的高速缓存。

CPU利用局部性原理,在从内存读取数据项到缓存时,将该内存附近的数据块也一并读取到缓存中,这一过程称为预读。

即读取连续空间的内存要比内存随机访问的性能要高,这一点用Java程序可以证明。

public static void main(String[] args) {
        int[][] arr = new int[10000][10000];
        int sum = 0;
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < arr.length; i++) {
            for (int j = 0; j < arr[0].length; j++) {
                sum += arr[i][j];
            }
        }
        System.out.println("数组顺序访问耗时:" + (System.currentTimeMillis() - startTime) + "ms");
        sum = 0;
        startTime = System.currentTimeMillis();
        for (int i = 0; i < arr.length; i++) {
            for (int j = 0; j < arr[0].length; j++) {
                sum += arr[j][i];
            }
        }
        System.out.println("数组非顺序访问耗时:" + (System.currentTimeMillis() - startTime) + "ms");
    }

这是一段对二维数组循环读取的代码。

程序的上半部分是按数组的第二维开始顺序读取,即二维数组逐行按内存连续空间顺序访问。

下半部分则是按数组的第一维按列读取,不是顺序访问。

分别经过10000*10000次的数组访问后,其运行结果如下:

由此可见,对内存的顺序访问性能优于随机访问。

磁盘空间局部性

在Java日常开发中,很多的中间件都需要跟磁盘文件打交道,这些磁盘数据的高性能访问也都依托于局部性原理,比如:

  • MySql的日志文件
  • MQ消息数据

我们知道MySql的数据最终都保存在磁盘中,为减少磁盘IO提高性能,InnoDB引擎底层依托BufferPoll+redo log机制来提高mySql读写性能(具体可参考MySql原理总结)。而针对redo log、undo log、binlog的读写避免不了磁盘IO,那么这里就利用操作系统的PageCache机制,对磁盘数据顺序读写,使得磁盘IO的性能近乎于内存性能。

我们常说kafka和rocketMQ是高性能的消息中间件,其中一部分高性能就依托于对磁盘文件的顺序读写。比如commit log的顺序写入,kafka中partition、rockerMQ中consumerQueue中消息的顺序读写。同样的也是利用操作系统的PageCache机制。

PageCache

页缓存(PageCache)是OS对文件的缓存,用于加速对文件的读写。一般来说,程序对文件进行顺序读写的速度几乎接近于内存的读写速度,主要原因就是由于OS使用PageCache机制对读写访问操作进行了性能优化,将一部分的内存用作PageCache。

对于数据的写入,OS会先写入至Cache内,随后通过异步的方式由pdflush内核线程将Cache内的数据刷盘至物理磁盘上。

对于数据的读取,如果一次读取文件时出现未命中PageCache的情况,OS从物理磁盘上访问读取文件的同时,会顺序对其他相邻块的数据文件进行预读取。

而PageCache就是局部性原理的实现。

时间局部性

时间局部性可能在我们日常业务开发中体现得更明显。

类似LRU缓存都是其具体实现。

另外CPU的指令重排序也贴点边,比如对一个数据的访问计算,优先将于这数据有关的指令排在一起处理。

参考

总结

到此这篇通过Java视角简单谈谈局部性原理的文章就介绍到这了,更多相关Java局部性原理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 通过Java视角简单谈谈局部性原理

    局部性原理 程序在访问数据时,都趋于聚集在一片连续的区域中,这被称为局部性原理. 按时间和空间划分为两类: 时间局部性:如果一个数据正在被访问,那么近期它很可能再次被访问. 空间局部性:如果某一个位置的数据被访问,那么这个问题附近的数据很可能被访问. 针对局部性原理,CPU和操作系统都有具体的实现. 本文主要总结梳理CPU和操作系统的局部性原理在Java后端中的影响与意义. CPU空间局部性 如下图是Java的内存模型 我们知道CPU为提高从内存中读数据的性能,有L1.L2.L3三个级别的高速缓

  • Core Java 简单谈谈HashSet(推荐)

    同学们在看这个问题的时候,我先提出者两个问题,然后大家带着问题看这个文章会理解的更好. 1.HashSet为什么添加元素时不能添加重复元素? 2.HashSet是否添加null元素? 打开源码, 我们看到如下代码,我们看到HashSet也有一个HashMap做为属性,HashSet()的构造方法就是将这个map实例化.如果大家对HashMap还不了解话,可以看我的这篇博文.还要注意有一个静态final的对象PRESENT,这个是干什么用的,咱们继续往下看. private transient H

  • 简单谈谈java自定义注解

    Java在1.5开始引入了注解,目前流行的框架都在用注解,可想而知注解的强大之处. 以下通过自定义注解来深入了解java注解. 一.创建自定义注解 package com.sam.annotation; import java.lang.annotation.*; /** * @author sam * @since 2017/7/13 */ @Target({ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.R

  • 简单谈谈Java中的方法和方法重载

    今天我们就讲一点内容,来说说Java中的方法和方法重载以及需要注意的一些地方: 方法: Java的方法类似与其他语言的函数,是一段用来完成特定功能的代码片段, 声明格式: [修饰符1 修饰符2 ....] ,返回值类型 方法名 (形式参数列表) { Java语句: - - -} 形式参数:在方法被调用时用于接受外界输入的数据: 实参: 调用方法时世界传给方法的数据: 返回值: 方法在执行完毕后返回给调用他的环境的数据: 返回值类型: 事先约定好的返回值的数据类型,如无返回值必须给出返回值类型vo

  • 简单了解Java多态向上转型相关原理

    这篇文章主要介绍了简单了解Java多态向上转型相关原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 多态性是面向对象的第三大特征. 多态的优点 改善代码的组织结构和可读性. 能够创建可扩展的程序.(随时可以加入新功能) 消除类型之间的耦合关系. 说实话,作为小白的我,并不太能够理解上面三个优点.随着深入学习,理解应该会越来越深吧,共勉. 向上转型 概念 Java允许把子类对象赋值给父类的引用变量,不用做任何强制转换,系统自动完成.向上转型来自

  • 简单分析Java的求值策略原理

    形参和实参 java在定义方法时可以设置参数,参数分为形参和实参,形参是指在定义函数时用于接收外部传入数据的参数,而实参是指在调用方法时主调函数向被调函数传递的数据参数. 例如: public class Main { public static void main(String[] args) { int a = 2; int b = 3; System.out.println(sum(a,b));//5 } private static int sum(int i1,int i2){ ret

  • Java中volatile关键字实现原理

    前言 我们知道volatile关键字的作用是保证变量在多线程之间的可见性,它是java.util.concurrent包的核心,没有volatile就没有这么多的并发类给我们使用. 本文详细解读一下volatile关键字如何保证变量在多线程之间的可见性,在此之前,有必要讲解一下CPU缓存的相关知识,掌握这部分知识一定会让我们更好地理解volatile的原理,从而更好.更正确地地使用volatile关键字. CPU缓存 CPU缓存的出现主要是为了解决CPU运算速度与内存读写速度不匹配的矛盾,因为C

  • 简单谈谈ThreadPoolExecutor线程池之submit方法

    jdk1.7.0_79 在上一篇<ThreadPoolExecutor线程池原理及其execute方法>中提到了线程池ThreadPoolExecutor的原理以及它的execute方法.本文解析ThreadPoolExecutor#submit. 对于一个任务的执行有时我们不需要它返回结果,但是有我们需要它的返回执行结果.对于线程来讲,如果不需要它返回结果则实现Runnable,而如果需要执行结果的话则可以实现Callable.在线程池同样execute提供一个不需要返回结果的任务执行,而对

  • 详解Java线程池和Executor原理的分析

    详解Java线程池和Executor原理的分析 线程池作用与基本知识 在开始之前,我们先来讨论下"线程池"这个概念."线程池",顾名思义就是一个线程缓存.它是一个或者多个线程的集合,用户可以把需要执行的任务简单地扔给线程池,而不用过多的纠结与执行的细节.那么线程池有哪些作用?或者说与直接用Thread相比,有什么优势?我简单总结了以下几点: 减小线程创建和销毁带来的消耗 对于Java Thread的实现,我在前面的一篇blog中进行了分析.Java Thread与内

  • Java编程Iterator迭代器设计原理及实现代码示例

    我们知道迭代器(Iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素.那么Iterator迭代器的设计原理是什么呢?迭代器问什么定义了一个借口,而不是一个类呢? 我们假设迭代器迭代数据的功能定义为了一个类,那么,会有这样的问题.不同的集合,由于数据结构不一样,所以他们的存储方式也是不一样的.也就是说,迭代器获取的时候,获取的方式是变化的,也就是不固定的.所以把这种方式定义为具体的实现是不合理的. 无论何种集合,他们肯定都有获取的功能,而且不知道什么时候就没有数据了.所有他

随机推荐