关于JavaScript的的高速缓存未命中分析【云图智联】

免费学习视频欢迎关注云图智联:https://e.yuntuzhilian.com/

在本文中,我们将讨论创建和访问数据的方式可能对应用程序性能的影响。

介绍

JavaScript是一种非常高级的语言,在使用JavaScript开发的时候不必对存储器中的数据存储方式作过多的考虑。在本文中,我们将探讨数据如何存储在内存中,以及JavaScript中分发和访问数据的方式将如何影响CPU和内存的性能。

浪漫三角

当计算机需要进行一些计算任务时,计算机处理单元(CPU)需要数据进行处理,因此,根据手中的任务,它将发送请求到内存以通过总线获取待处理的数据,就像下面这样:

这就是我们的浪漫三角

CPU->总线->内存

浪漫三角需要第四个元素来保持稳定

由于CPU比内存快得多,因此从CPU->总线->内存->总线->CPU这样的处理方式就浪费了很多计算时间,因为查找内存时,CPU处于空闲状态而无法执行其他操作。

缓存的出现有效的缓解了这个问题。在本文中我们不会详细讨论缓存的技术细节和类型,我们只需要知道缓存可以作为CPU的一个内部存储空间。

当CPU接收到要运行的命令时,它将首先在高速缓存中搜索目标数据,如果没有搜索到目标数据,它再通过总线发起请求。

然后,总线将请求的数据加上一部分内存,将其存储在高速缓存中以供CPU快速调用。

这样一来,CPU就能够有效的处理数据,而不会浪费时间等待内存返回。

高速缓存的引用也可能导致新的问题

基于上面的架构,我们在处理大量数据时可能会出现一种名为”高速缓存未命中”的错误。

高速缓存未命中意味着在计算期间,CPU发现高速缓存中没有必要的数据,因此需要通过常规通道(即已知的慢速存储器)来请求此数据。

上图是一个很好的实例,在处理组中数据是,由于计算的数据超出了缓存限制的数据,就导致了缓存未命中。

可是这跟我作为JavaScript程序员有什么关系呢?

好问题,大多数情况下,我们JavaScript开发人员不必关心这个问题。但是随着越来越多的数据涌入Node.js服务器甚至富客户端,所以当使用JavaScript遍历大型数据集时就容易遇见缓存未命中的问题。

一个经典的例子

接下来让我们以一个例子作为说明。

这是一个叫做Boom的类:

  1. class Boom { 
  2.   constructor(id) { 
  3.     this.id = id; 
  4.   } 
  5.   setPosition(x, y) { 
  6.     this.x = x; 
  7.     this.y = y; 
  8.   } 

此类(Boom)仅具有3个属性:id,x和y。

现在,让我们创建一个填充x和y的方法。

让我们构建设置:

  1. const ROWS = 1000; 
  2. const COLS = 1000; 
  3. const repeats = 100; 
  4. const arr = new Array(ROWS * COLS).fill(0).map((a, i) => new Boom(i)); 

现在,我们将在一种方法中使用此设置:

  1. function localAccess() { 
  2.   for (let i = 0; i < ROWS; i++) { 
  3.     for (let j = 0; j < COLS; j++) { 
  4.       arr[i * ROWS + j].x = 0; 
  5.     } 
  6.   } 

本地访问的作用是线性遍历数组并将x设置为0。

如果我们重复执行此功能100次(请查看设置中的重复常数),则可以测量运行时间:

  1. function repeat(cb, type) { 
  2.   console.log(`%c Started data ${type}`, 'color: red'); 
  3.   const start = performance.now(); 
  4.   for (let i = 0; i < repeats; i++) { 
  5.     cb(); 
  6.   } 
  7.   const end = performance.now(); 
  8.   console.log('Finished data locality test run in ', ((end - start) / 1000).toFixed(4), ' seconds'); 
  9.   return end - start; 
  10. repeat(localAccess, 'Local'); 

日志输出是这样的:

丢失缓存要付出的代价

现在,根据上面的了解,如果我们处理迭代过程中距离较远的数据,则会导致缓存丢失。远处的数据是不在相邻索引中的数据,如下所示:

  1. function farAccess() { 
  2.   for (let i = 0; i < COLS; i++) { 
  3.     for (let j = 0; j < ROWS; j++) { 
  4.       arr[j * ROWS + i].x = 0; 
  5.     } 
  6.   } 

在这里发生的是,在每次迭代中,我们都处理上次迭代距ROWS的索引。因此,如果ROWS为1000(在我们的例子中),我们将得到以下迭代:[0,1000,2000,…,1,1001,2001,…]。

让我们将其添加到速度测试中:

  1. repeat(localAccess, 'Local'); 
  2. setTimeout(() => { 
  3.   repeat(farAccess, 'Non Local'); 
  4. }, 2000); 

这是最终结果:

非本地迭代速度几乎慢了4倍。随着数据量的增加,这种差异将越来越大。发生这种情况的原因是由于高速缓存未命中,CPU处于空闲状态。

那么您要付出的代价是什么?这完全取决于您的数据大小。

好吧,我发誓我永远不会那样做!

您通常可能不这么认为,但在某些情况下,您可能会希望使用非线性(例如1,2,3,4,5)或非偶然性。比如( for (let i = 0; i < n; i+=1000))

例如,您从服务或数据库中获取数据,并需要通过某种复杂的逻辑对数据进行排序或过滤。这可能导致访问数据的方式与farAccess函数中显示的方式类似。

如下所示:

查看上图,我们看到了存储在内存中的数据(顶部灰色条)。在下面,我们看到了当数据从服务器到达时创建的数组。最后,我们看到排序后的数组,其中包含对存储在内存中各个位置的对象的引用。

这样,对排序后的数组进行迭代可能会导致在上面的示例中看到的多个缓存未命中。

请注意,此示例适用于小型阵列。高速缓存未命中与更大的数据有关。

在当今世界中,您需要在前端使用精美的动画,并且可以在后端(无服务器或其他服务器)中为CPU的每毫秒时间计费(这很关键)。

不好了!都没了!!!

并不是,有多种解决方案可以解决此问题,现在您已经知道造成性能下降的原因,您可以自己考虑解决方案。比如只需要将处理在一起的数据更紧密地存储在内存中。

这种技术称为数据局部性设计模式。

让我们继续我们的例子。假设在我们的应用程序中,最常见的过程是使用farAccess函数中显示的逻辑来遍历数据。我们希望对数据进行优化,以使其在最常见的for循环中快速运行。我们将像这样排列相同的数据:

  1. const diffArr = new Array(ROWS * COLS).fill(0); 
  2. for (let col = 0; col < COLS; col++) { 
  3.   for (let row = 0; row < ROWS; row++) { 
  4.     diffArr[row * ROWS + col] = arr[col * COLS + row]; 
  5.   } 

所以现在在diffArr中,原始数组中索引为[0,1,2,…]的对象现在被设置为[0,1000,2000,…,1,1001,2001,…,2, 1002,2002,…]。数字表示对象的索引。这模拟了对数组进行排序的方法,这是实现数据局部性设计模式的一种方法。

为了方便测试,我们将稍微更改farAccess函数以获得一个自定义数组:

  1. function farAccess(array) { 
  2.   let data = arr; 
  3.   if (array) { 
  4.     data = array; 
  5.   } 
  6.   for (let i = 0; i < COLS; i++) { 
  7.     for (let j = 0; j < ROWS; j++) { 
  8.       data[j * ROWS + i].x = 0; 
  9.     } 
  10.   } 

现在将场景添加到我们的测试中:

  1. repeat(localAccess, 'Local'); 
  2. setTimeout(() => { 
  3.   repeat(farAccess, 'Non Local') 
  4.   setTimeout(() => { 
  5.     repeat(() => farAccess(diffArr), 'Non Local Sorted') 
  6.   }, 2000); 
  7. }, 2000); 

我们运行它,我们得到:

我们已经优化了数据,以适应需要查看的更常见的方式。

免费学习视频欢迎关注云图智联:https://e.yuntuzhilian.com/

热门文章

暂无图片
编程学习 ·

传说中的中国复杂报表都长什么样?有什么特点?

最开始中国式报表这个术语是针对国外的报表工具来说的,当时国外的报表工具只能做一些格式很规整的报表,到了中国以后发现有很多报表做不出来,或者做起来极不方便,还需要大量的人工编程。这些报表成了国外报表工具的恶梦,于是把复杂难做的报表称为“中国式”的。这些很难做…
暂无图片
编程学习 ·

css基础学习记录

<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>CSS</title><!--也可在head style标签里--><!--<style type="text/css">--><!--p{--><!--color: bisqu…
暂无图片
编程学习 ·

LeetCode题解(0744):寻找比目标字母大的最小字母(Python)

题目:原题链接(简单)解法 时间复杂度 空间复杂度 执行用时Ans 1 (Python) O(logN)O(logN)O(logN) O(1)O(1)O(1) 136ms (69.15%)Ans 2 (Python) O(logN)O(logN)O(logN) O(1)O(1)O(1) 136ms (69.15%)Ans 3 (Python)LeetCode的Python执行用时随缘,只要时间复杂度没有明显差异,…
暂无图片
编程学习 ·

深入浅出计算机组成原理 函数调用(自我提升第二十二天)

昨天6月30号,菜鸟去搞了半天的手机套餐什么的,然后我妈换了个华为的手机,忍不住想玩玩,所以鸽了/(ㄒoㄒ)/~~ 今天就补昨天的咯,然后再写一篇或者两篇,具体看看有没有什么事,话不多说,冲冲冲 这里我一篇是菜鸟提取的,不然就是讲单片机了,不是菜鸟自己总结的/(ㄒoㄒ)/~…
暂无图片
编程学习 ·

mysql的语句执行原理详解

需求:select user,host from mysql.user; 以上面的一条命令为例,如何将数据返回的,下面进行详细的阐述:SQL层总结: 语法、语义(数据XX语言)、权限(grant)检查完毕后—> 根据解析器生成解析树—>优化器代价评估—>然后得出执行计划—>执行器执行—>在那…
暂无图片
编程学习 ·

【实习日志】The last Day总结篇

实习总结2020-6-15 → 2020-07-1 hr: 你实习到现在多久了? I: 6-15开始至今,期间有一个周六日和三天端午假期,包含今天累计到您公司:11天 hr: 下午工资转你卡,有机会再合作本次实习虽只有11天,期间自己技术性问题还未领悟到实在的东西,还没感受到进步,但是换做自己是领导…
暂无图片
编程学习 ·

mac mysql更改了目录所遇到的坑

之前安装的目录为/usr/local/develope/mysql后来改了下目录 同时也改了MySQL文件夹名现在为/usr/local/develope/develop/mysql5.6 同时data目录还是在的配置文件已经修改 MySQL在安装或者启动的时候没有指定配置文件时候 默认找的配置文件/etc/my.cnf将basedir目录和data目录修…
暂无图片
编程学习 ·

源代码编译安装

源代码编译 使用源代码安装软件的优点获得最新的软件版本,计时修复bug 根据用户需要,灵活定制软件功能应用场景举例安装较新版本的应用时 当前安装的程序无法满足需要时 需要为应用程序添加新的功能时Tarball封包.tar.gz和.tar.bz2格式居多 软件素材参考:http://sourceforg…
暂无图片
编程学习 ·

2016 年实验班选拔试题

SUM(10 分) 题目描述:求出在 1 到 N 之间的所有整数的总和。 输入格式:输入包含多组测试数据。每行是一组测试数据,该数据是一个绝对值不 大于 10000 的整数 N。N=0 时,表示输入结束。 输出格式:对于每组测试数据,输出一行,改行包含一个整数,是所有在 1 到 N 之 间的…
暂无图片
编程学习 ·

maven 有时候parent项目版本没更新的版本问题

对于parent工程,一般规定了版本,并且包含了子模块。如果首次编译整个项目,可能导致编译不成功,因为子模块需要父工程版本号。父工程想连同子模块一起编译,所以首次编译的时候,注释掉parent工程的子模块。先编译版本,成功后放开子模块。就可以了。如果parent的版本发生变…
暂无图片
编程学习 ·

04 | 如何处理消费过程中的重复消息?

1.应用场景见: https://blog.csdn.net/william_n/article/details/1040254082.学习/操作2.1 阅读文档你好,我是李玥。上节课我们讲了如何确保消息不会丢失,课后我给你留了一个思考题,如果消息重复了怎么办?这节课,我们就来聊一聊如何处理重复消息的问题。在消息传递过程中…
暂无图片
编程学习 ·

jdbcTemplate.queryForObject 没查到抛异常

当结果集合的size为0或者大于1时,就会抛出异常。 解决方法有两个: (1)通过修改数据库:删除数据库中对应名称(column)相同的记录,留下只剩"1"条。 (2)通过更换方法:使用query方法返回list对象(该方法能返回所有查询记录)
暂无图片
编程学习 ·

sed详解

1. Sed简介sed 是一种在线编辑器,它一次处理一行内容。处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾。文件内容并没有…
暂无图片
编程学习 ·

数据库系统原理练习题(一)

一、【问题描述】 1、 已知三个域:男人={李基,张鹏},女人={任方,刘玉},子女={李键,张睿,张峰}。这一-组域(男人,女人,子女)的笛卡尔积的基数为 A.12 B.9 C.7 D.27 2、现有如下关系:患者(患者编号,患者姓名,性别,出生日期),医疗(患者编号,患者姓名,医生编号,医生姓名…
暂无图片
编程学习 ·

萝卜小姐——知乎上看到的好用的IOT平台

作者:码云 Gitee 链接:https://www.zhihu.com/question/266251753/answer/827948303 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。码云Gitee 上有几个不错的物联网平台项目推荐给你,希望对你有帮助:)推荐项目1、开源的、分布式的物联…
暂无图片
编程学习 ·

HDFS之WordCount

1.pom.xml <?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/…
暂无图片
编程学习 ·

Volatile关键字

volatile关键字 volatile 关键字是java提供的一种轻量级同步机制。他能够保证可见性和有序性,但是不能保证原子性。 volatile可见性 可见性表示被这个关键字所修饰的实例,在被修改后,其他的线程均可见。```javaclass MyData { // 如果没有volatile关键字的话,那我们在修…
暂无图片
编程学习 ·

软件测试(软件测试生命周期,描述一个bug,定义bug级别,bug生命周期,如何开始第一次测试,测试执行和bug管理,测试工作中的人际关系处理)

一、软件测试的生命周期 对比软件的生命周期和bug的生命周期 软件的生命周期:需求分析——计划——设计——编码——测试——运行维护 软件测试的生命周期:需求分析——测试计划——测试设计、测试开发——测试执行——测试评估 bug的生命周期: 软件测试&软件开发生…