热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

聊聊高并发(三十四)Java内存模型那些事(二)理解CPU快速缓存的工作原理

在上一篇聊聊高并发(三十三)从一致性(Consistency)的角度理解Java内存模型我们说了Java内存模型是一个语言级别的内存模型抽象。它屏蔽了底层硬件实现内存一致性需求的差异,提供了对

在上一篇聊聊高并发(三十三)从一致性(Consistency)的角度理解Java内存模型 我们说了Java内存模型是一个语言级别的内存模型抽象。它屏蔽了底层硬件实现内存一致性需求的差异,提供了对上层的统一的接口来提供保证内存一致性的编程能力。

在一致性这个问题域中,各个层面扮演的角色大致例如以下:

1. 一致性模型,定义了各种一致性模型的理论基础

2. 硬件层,提供了实现某些一致性模型的硬件能力。硬件在默认情况下依照最主要的方式执行,比方

  • 对同一个线程没有数据依赖的指令能够重排序优化运行,有数据依赖的指令依照程序顺序运行,从而保证单线程程序运行的正确性
  • 保证读操作读到的数据肯定是之前在同一位置写入的数据

3. 语言层,少数语言提供了语言层面的满足一致性模型的编程能力,另外一些语言则直接使用硬件层提供了一致性编程的能力。提供一致性能力语言的工作方式例如以下:

  • 把满足一致性需求的编程能力作为一种资源,指定一些规则,比方volitile, synchronized,Happens-before规则等
  • 当应用层须要使用这样的编程能力的时候,须要显式地提出申请,比方显式地使用volatile来标识变量
  • 通过编译器适配底层各种硬件平台提供了一致性编程的能力,比方有些平台使用内存屏障,有些平台使用read-modified-write。须要语言层来屏蔽这样的差异性


4. 应用层,比方分布式系统,比方并发的server程序。它们在一致性问题中的工作有

  • 依据实际需求来定义应用所须要满足的一致性需求
  • 定义和选择对应的实现一致性需求的算法。比方分布式存储中通过消息协议实现的Paxos,Zab。多阶段提交等
  • 利用编程语言提供了主要的一致性编程的能力作为实现一致性需求算法的基础


说了一堆一致性需求相关的。那么问题来了。为什么有内存一致性的这个需求呢?


内存一致性需求的出现主要是由于多核CPU的出现,而且存在多级的快速缓存,这样就出现了对内存读写的并发问题,从而出现了内存的一致性问题。

所以快速缓存是造成内存一致性问题的一个重要原因。非常多写Java内存模型的文章笼统的说CPU写操作的时候存在一个写缓冲区write buffer。导致写操作不能及时写回到主存,造成了其它线程不能看到新写入的值,也就是所谓的可见性问题; 而且因为写缓存区是一种lazy write,导致了CPU能够在写没有刷新到内存的时候就開始兴许的读,也形成了重排序的场景。所谓的有序性的问题。


这篇文章写写CPU快速缓存相关的工作原理,来看看写缓存区究竟是个什么东西。本人不是研究硬件的,一些观点也是基于自己的理解。假设说的不正确请进一步查阅资料。

先来看一张图,这张就是Java内存模型的概念模型图。工作内存 work memory是对CPU寄存器和快速缓存的抽象。

再来看一张图,摘自《深入理解计算机系统》中描写叙述Intel Core i7处理器的快速缓存的概念模型。



对照这两张图。我们能够看到Java内存模型中每一个线程的工作内存实际上就是寄存器以及快速缓存的抽象。在眼下主流的多核处理器设计中。一般每一个核心都会包括1个L1缓存和L2缓存,多个核心共享一个L3快速缓存。各个核心直接通过系统总线连接。系统总线包括数据总线。地址总线,控制总线,统称系统总线。我们要记住的是总线是一种共享的资源,假设不合理的使用。比方聊聊高并发(五)理解缓存一致性协议以及对并发编程的影响 这篇中说的缓存一致性协议导致的总线流量风暴,会影响程序运行的效率。

这张图说了各级快速缓存的一些參数。有几个要点:

1. CPU仅仅直接和寄存器已经L1缓存交互

2. 现代的L1缓存分为两个单独的物理块:

  • i-cache存储指令,是仅仅读的。
  • d-cache存储数据。是读写的

3. L2和L3缓存存储指令和数据

4. 注意快速缓存的大小,Core i7的L1缓存大小为64KB, L2缓存是256KB,L3是8MB

5. 缓存是分块,分组的

6. L1的訪问周期是4, L2是L1的3倍,L3是L2的3倍

7. 一次内存訪问的时钟周期是L3的3倍左右,和L1差2个数量级

8. 一次硬盘(普通磁盘)訪问的时间在1-10ms级别。和一次内存訪问差4个数量级,和1次快速缓存訪问差6个数量级以上

9. 一次固态硬盘訪问的时间在10-100微秒级别,比普通硬盘快1到2个数量级,和一次内存訪问差2-3个数量级左右


说到快速缓存就不得不说到计算机领域的局部性原理(Principle of Locality)。

局部性原理是缓存技术的底层理论基础。局部性包含两种形式:

1. 时间局部性,一个具有良好时间局部性的程序中。被引用过一次的存储器位置非常可能在不远的将来再被多次引用

2. 空间局部性,一个具有良好空间局部性的程序中,假设一个存储器位置被引用了一次,那么程序非常可能在不远的将来引用附近的一个存储器位置

我们知道64位机器一次内存数据读取64位,也就是8个字节。8个连续的内存位置,所以快速缓存中存放的也是连续位置的数据,这是局部性的体现


局部性对编程的一些指导:

1. 反复引用同一个变量具有良好的时间局部性

2. 对于具有步长为k的引用模式的程序,步长越短空间局部性越好。尤其是操作数组。多维数组,局部性的影响非常大

3. 对于取指令来说。循环有好的时间和空间局部性。循环体越小。循环次数越多,局部性越好


另外来看一下存储器的体系结构

有几个要点

1. 越往上存储容量越小,存取速度越快,成本越高,反之亦然

2. 一层存储器仅仅和下层存储器打交道,不会跨级訪问

3. 下层作为上层的一个缓存。CPU要訪问的数据的终于一般都经过主存,主存作为下层其它设备的一个缓存。其它设备的数据终于都要进入主存才干被CPU訪问到。比方磁盘文件读取操作,CPU仅仅发起操作请求。详细的数据操作不须要经过CPU,由DMA(Direct Memory Access)来操作IO和主存的交互。当操作完毕后,IO设备发出中断,通知CPU操作完毕

4. 每层缓存都须要一个管理器来管理缓存,比方将缓存划分为块,在不同层中传送块,判定命中不命中。管理器能够是硬件。软件或两者的集合。比方快速缓存全然由内置在缓存中的硬件来管理


以下正式进入快速缓存工作原理的主题。先看一下快速缓存的基本结构

1. 划分为S个缓存组 cache set

2. 每组里面有E个缓存行 cache line。也叫Cache线,行数E也叫缓存的相联度

3. 每行里面1个有效位来标记该缓存行是否dirty。有t个长度的标记位来辅助缓存地址定位。标识该缓存块的唯一位置。有一个B个字节的缓存块block。一行仅仅有一个块

4. 快速缓存的大小C = B * E * S,仅仅计算有效的字节数,不包含有效位及标记位的大小

4. 一个快速缓存能够用一个四元组来表示(S, E, B, m)。m表示计算机的位数。

拿Core i7的L1缓存来说,S = 64。 E = 8, B = 64, m = 64,能够表示为(64,8,64,64).

能够看到L1的大小32K = 64个字节(块大小) * 8(行数) * 64(组数)




先看快速缓存是怎样在当前缓存中定位一个目标内存地址的缓存并读命中的。分为三步

1. 组选择

2. 行匹配

3. 字抽取


这个定位的过程有点类似哈希操作。把一个m位的内存地址映射到一个快速缓存的组索引(s位),行(t位),块偏移(b位)中去。



还拿Core i7的L1缓存(64,8,64,64)来说,拿到一个64位的内存地址

1. 组选择:有64个组。那么64位的内存地址中就要拿出s=6位(000000-111111)来表示64个组号。依据这个内存地址的s位定位到一个组

2. 行匹配:每一个组有8行,大小为64B的块得到的b=6, 计算得到t = m - (b+s) = 64 - 12 = 52。也就是说64位地址的高52位作为t。用这个t标记去这个组的8个行去匹配相应t标记位,假设有匹配的行,就命中,否则不命中

3. 假设命中。再由这个内存地址的低b位计算出这个地址在块中的偏移位置。

块能够理解为一个字节数组,64个字节的块就有块[0]....块[63]个偏移量。有内存地址的低b位能够计算得到这个地址相应的偏移量,从而找到这个数


比方对于一个32个元素的int数组int[32]来说,int[0] - int[15]存放到快速缓存组[0]的第0行,一个块是64个字节,正好能够存储16个int数据。int[16] - int[31]存放到快速缓存组[0]的第1行。当訪问int[0]的时候。没有命中,会从下一层存储器载入0行的缓存块,这样int[0]-int[15]都载入到缓存块中了,下一次訪问int[1] - int[15]的时候都命中。訪问到Int[16]的时候没有命中,相同从下一层存储中载入int[16] - int[31]到第1行,这样下次訪问int[16] - int[31]时就都命中


快速缓存有直接映射快速缓存,E路相联快速缓存,全相联快速缓存之分,差别是直接相联快速缓存每一组仅仅有1行,所以仅仅要定位到组就能知道是否命中。全相联快速缓存则相反。仅仅有1组,仅仅要匹配到t位的标记位就知道是否命中。

E路相联快速缓存则是折中。比方Core i7的L1 d-cache就是8路相联快速缓存,每组有8行,这样定位到组之后。还须要在组的8个行里面去匹配标记位来推断是否命中。


缓存的经常使用术语命中hit表示在当前缓存中定位到了目标地址的缓存,不命中表示在当前缓存中没有找到目标地址的缓存。

结合读写动作,所以有4个状态

1. 读命中

2. 读不命中

3. 写命中

4. 写不命中


知道了怎样把一个内存地址映射到快速缓存块中之后,我们来分析这4种情况各自的表现

读命中

最简单的情况,依照组选择。行匹配。数据抽取的步骤返回命中的数据


读不命中

读不命中的话就须要从下一层存储去载入相应的数据项来相应的缓存行中,注意载入的时候是整个缓存块都会被新的缓存块所取代。替换的时候比較复杂。要推断替换掉哪个缓存行。最经常使用的作法是使用LRU(least recently used)算法,近期最少使用算法,替换最后一次訪问时间最久远的那一行。

然后返回载入后找到的数据


关于写。情况就更复杂。这也是常说的CPU lazy write的原因。CPU写快速缓存有两种方式

1. 直写 write-through, 这样的方式会写快速缓存和内存

2. 写回,也有叫回写的,write-back,这样的方式仅仅写快速缓存。将对应的缓存行标记为脏dirty,我们前面说了每一个缓存行有一个有效位。0表示dirty/空, 1表示有效。仅仅有当这个脏的缓存行要被替换掉时,才会写到内存中去


写命中的情况下。因为write-through要写快速缓存和内存,每次写都会造成总线流量。write-back仅仅写快速缓存,不产生总线流量

写不命中的情况下,有两种方法:写分配 write-allocate 和非写分配 not-write-allocate。

写分配会从下一层存储载入对应的块到快速缓存。然后更新这个缓存块。非写分配会直接避开快速缓存。直接写到主存。一般都是write-back使用write-allocate的方式,write-through使用not-write-allocate的方式。


我们比較一下write-through和write-back的特点

write-through: 每次写都会写内存,造成总线流量,性能较差,长处是实时性强。不会由于断电丢失数据

write-back: 充分利用局部性原理,脏的缓存线也能被后面的读立马读取,性能较高。缺点是实时性不高。出现问题可能会丢失数据


眼下基本上CPU的写缓存都採用write-back的方式。只是能够通过BIOS或者操作系统内核參数来配置CPU採取哪种写的方式。


以下这两张来自wiki的图说清了write-through和write-back的流程




那么别人常常提到的写缓冲区write-buffer究竟是个什么东西呢,write-buffer被write-through时使用,用来缓存写回到主内存的数据,我们知道写一次内存要100ns左右,CPU不会等待写直到写入内存才继续运行兴许指令。它是把要写到主存的数据放到write-buffer,然后就运行后面的指令了,能够理解为一种异步的方式,来优化write-through的性能。

假设write buffer满了,那么兴许的写要等待write buffer中有空位置才干继续写。

理解下缓冲区的概念,缓冲区是用来适配两个流速不同的组件经常使用的方式,比方IO中的BufferedWriter,生产者-消费者模式的缓冲队列等等,它能够非常好地提高系统的性能。


能够看到。无论是write-through,还是write-back,因为快速缓存和写缓冲区的存在。它们都造成了lazy write的现象,写不是立即就写回到主内存,从而造成了数据可见性和有序性的问题。所以须要定义内存模型来提供一些手段来保证一些一致性需求,比方通过使用内存屏障强制把快速缓存/写缓冲区中的数据写回到内存。或者强制把快速缓存中的数据刷新。来保证数据的可见性和有序性。


这篇分析了快速缓存的原理,应该能对Java内存模型的起因有了更深刻认识。这些缓存的原理不仅适合快速缓存。并且适合全部的缓存系统。


參考资料:

《深入理解计算机系统》

Cache

Write Buffer

Writing into Cache



推荐阅读
  • 上图是InnoDB存储引擎的结构。1、缓冲池InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可以看作是基于磁盘的数据库系统。在数据库系统中,由于CPU速度 ... [详细]
  • 本文介绍了解决Netty拆包粘包问题的一种方法——使用特殊结束符。在通讯过程中,客户端和服务器协商定义一个特殊的分隔符号,只要没有发送分隔符号,就代表一条数据没有结束。文章还提供了服务端的示例代码。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • 本文讨论了在iOS平台中的Metal框架中,对于if语句中的判断条件的限制和处理方式。作者提到了在Metal shader中,判断条件不能写得太长太复杂,否则可能导致程序停留或没有响应。作者还分享了自己的经验,建议在CPU端进行处理,以避免出现问题。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • 篇首语:本文由编程笔记#小编为大家整理,主要介绍了软件测试知识点之数据库压力测试方法小结相关的知识,希望对你有一定的参考价值。 ... [详细]
  • MySQL数据库锁机制及其应用(数据库锁的概念)
    本文介绍了MySQL数据库锁机制及其应用。数据库锁是计算机协调多个进程或线程并发访问某一资源的机制,在数据库中,数据是一种供许多用户共享的资源,如何保证数据并发访问的一致性和有效性是数据库必须解决的问题。MySQL的锁机制相对简单,不同的存储引擎支持不同的锁机制,主要包括表级锁、行级锁和页面锁。本文详细介绍了MySQL表级锁的锁模式和特点,以及行级锁和页面锁的特点和应用场景。同时还讨论了锁冲突对数据库并发访问性能的影响。 ... [详细]
  • OpenMap教程4 – 图层概述
    本文介绍了OpenMap教程4中关于地图图层的内容,包括将ShapeLayer添加到MapBean中的方法,OpenMap支持的图层类型以及使用BufferedLayer创建图像的MapBean。此外,还介绍了Layer背景标志的作用和OMGraphicHandlerLayer的基础层类。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 本文介绍了一种划分和计数油田地块的方法。根据给定的条件,通过遍历和DFS算法,将符合条件的地块标记为不符合条件的地块,并进行计数。同时,还介绍了如何判断点是否在给定范围内的方法。 ... [详细]
  • 本文介绍了多因子选股模型在实际中的构建步骤,包括风险源分析、因子筛选和体系构建,并进行了模拟实证回测。在风险源分析中,从宏观、行业、公司和特殊因素四个角度分析了影响资产价格的因素。具体包括宏观经济运行和宏经济政策对证券市场的影响,以及行业类型、行业生命周期和行业政策对股票价格的影响。 ... [详细]
  • 解决Sharepoint 2013运行状况分析出现的“一个或多个服务器未响应”问题的方法
    本文介绍了解决Sharepoint 2013运行状况分析中出现的“一个或多个服务器未响应”问题的方法。对于有高要求的客户来说,系统检测问题的存在是不可接受的。文章详细描述了解决该问题的步骤,包括删除服务器、处理分布式缓存留下的记录以及使用代码等方法。同时还提供了相关关键词和错误提示信息,以帮助读者更好地理解和解决该问题。 ... [详细]
author-avatar
ds87vdsa
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有