关于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/

热门文章

暂无图片
编程学习 ·

Unity2D教程:菜单界面、文字设置、常用界面功能

菜单界面创建一个UI-Image,会自动生成Canvas。 设置Canvas的设置模式为随屏幕改变 设置Image的伸展模式,在这个界面按下Alt键会变成这样,选择右下角那个就是填充整个Canvas了在当前Image下创建Button,将Button下面的Text和Button自身都弄成预置物Text可以加一个Outline组件…
暂无图片
编程学习 ·

MapReduce详细分析

一、MapReduce概述 1、定义 MapReduce核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在一个Hadoop集群 上。 2、MR进程 一个完整的MapR educe程序在分布式运行时有三类实例进程:**Mr AppMaster:**负责整个程序的过程调度及状态协调…
暂无图片
编程学习 ·

【考试记录】Apsara Clouder基础技能认证:实现调用API接口

从今天开始,准备把阿里的认证尽可能多的考出来。原因有这么几个:研究生要毕业了,除了把论文写好,还有找工作的压力,所以想尽可能多的考出几个证来证明自己的学习能力;研究生毕业后想找个教师的工作,所以得以身作则,多学习知识,这样教学生才能有底气。知道自己现在能力…
暂无图片
编程学习 ·

工科中的设计思维

超星学习通app工科中的设计思维网课答案,工科中的设计思维尔章节测验网课答案1.1 走近设计思维1【单选题】本门课程讲述的主要内容不包括()。A、设计思维这一学习形式和思维方式B、一系列有用的创新工具和创造技法C、工科学生需要用到的专业设计软件D、系统化的设计流程和与众…
暂无图片
编程学习 ·

COMP暴涨 风险暗藏

作者|JX kin编辑|文刀6月29日,在以太坊上构建的DeFi借贷协议Compound,因资金规模首超10亿美元引起市场关注,更受关注的是该协议的治理代币COMP近期的暴涨暴跌。从几十美元到400多美元再到如今的200多美元,COMP这趟过山车仅仅开了不到半个月。6月16日,自Compound以“流动性…
暂无图片
编程学习 ·

几种室内定位方案技术对比,高精度室内定位方案-新导智能

从古至今,人类的方方面面就离不开“定位”技术,从古代远洋航海罗盘,再到现如今每个电子终端都有的GPS,定位技术在我们身边可谓是无处不在。但人类已经演变成生活在钢筋混凝土森林的动物,工作和生活在室内的时间要远远超过室外,而且室内同样有定位和导航的需求.室内定位方…
暂无图片
编程学习 ·

UE4中让某个UI位于窗口的最顶端

1.处于同一嵌套层级的UI 可以使用Set ZOrder 设置那个Widget位于屏幕的最前面2.创建一个user widget 叫做ui_umg,里面加上两个按钮3.创建另外一个widget 叫做ui_pic,里面加上一个image4.这步是重点,ui_umg中按钮点击的时候 使用create widget 生成一个ui_pic, 但是这个时…
暂无图片
编程学习 ·

SQL存储过程

什么是存储过程,如何创建一个存储过程 * Stored Procedure * 存储过程=SQL语句+流控制语句定义存储过程定义 create procedure 存储过程名称(【参数列表】) begin 需要执行的语句 end. 创建CREATE PROCEDURE `get_hero_scores`( OUT max_max_hp FLOAT, OUT min_max_mp FLO…
暂无图片
编程学习 ·

IO基础篇:自动关闭接口AutoCloseable

介绍 在没有AutoCloseable之前,我调用资源对象,调用完毕后,必须要关闭,否则可能出现资源耗尽的情况 从名字就可以看出,AutoCloseable是一个可以自动保存资源并且关闭资源对象的接口,那么实现它的类就可以自动关闭资源,那怎样自动关闭呢?我们可以看下面例子: 例子 publ…
暂无图片
编程学习 ·

Arduino项目实战——基于Arduino【智能垃圾桶】设计

Arduino项目实战——基于Arduino【智能垃圾桶】设计第一次接触Arduino是在大一的时候,距离现在已经五年,当时一个简单的“电子琴”项目就让我抓耳挠腮,根本不具备“面向百度编程”能力的我,用老师提供的文档跟我的组员用了好几天,才让Arduino跟蜂鸣器想起一首简单版的《小…
暂无图片
编程学习 ·

Layui 扩展字体图标

layui 目前(2020-06-28)提供了168个图标,但是很多时候这些图标中没有自己想要的,今天在项目中想找一个二维码的图标,但是在layui提供的图标中并没有,此时我们可以扩展图标(阿里巴巴矢量图标库 www.iconfont.cn)layui提供的图标也是取材于此文章目录1. 进入阿里巴巴矢量…
暂无图片
编程学习 ·

AppcompaActivity 相对于 Activity 的区别

1、AppcompaActivity 带 ActionBar 标题栏,Activity 则不带。参考文档显示ActionBarActivity已经过时,使用AppCompatActivity代替。2、theme 主题只能用 android:theme=”@style/AppTheme (appTheme主题或者其子类),不能使用 android:style。
暂无图片
编程学习 ·

算法 动态规划 0-1背包

动态规划2 给定n种物品和1个背包,物品i的重量是wi,其价值为vi,背包的容量为C。要求选择装入背包的物品,使得装入背包中物品的总价值最大。 例如5个物品,wi[] = { 2,4,6,3,8},vi[]={6,6,3,7,5},背包的容量为10 #include<iostream> using namespace std; #include<…
暂无图片
编程学习 ·

Spring-boot 使用undertow代替tomcat

Undertow是Red Hat公司的开源产品, 是一款灵活的高性能Web服务器,它完全采用Java语言开发,可以直接嵌入到Java项目中使用,支持阻塞IO和非阻塞IO。由于Undertow采用Java语言开发。 Undertow在高并发业务场景中,性能优于Tomcat,对于并发要求不高的情况下,二者差别不大。 Un…
暂无图片
编程学习 ·

Flink原理与实现:Flink中的状态管理,keygroup,namespace

namespace维护每个subtask的状态上面Flink原理与实现的文章中,有引用word count的例子,但是都没有包含状态管理。也就是说,如果一个task在处理过程中挂掉了,那么它在内存中的状态都会丢失,所有的数据都需要重新计算。从容错和消息处理的语义上(at least once, exactly onc…
暂无图片
编程学习 ·

SpringBoot切换不同的实现时,出现nullPointer问题

1,有时候算法需要迭代,相同的接口需要多个实现,自己可以随意切换,接口类:package com.xxx.shortvideo.manager;public interface VideoRecommendedManger {FeedVideoByUserDTO getVideoByUser(FeedVideoByUserReq req);}2, 针对接口有两个实现3,通过配置config类来实现pa…
暂无图片
编程学习 ·

php 一句话木马简介

一句话木马就是一段简单的代码,就这短短的一行代码,就能做到和大马相当的功能。一句话木马短小精悍,而且功能强大,隐蔽性非常好,在入侵中始终扮演着强大的作用。一句话木马工作原理<?php @eval($_POST[shell]);?> 这是php的一句话后门中最普遍的一种。它的工作原理…