整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

带你区分几种并行

带你区分几种并行

要:在实际应用中,影响并行加速比的因素主要是串行计算、并行计算和并行开销三方面。

本文分享自华为云社区《高性能计算(2)——万丈高楼平地起-云社区-华为云》,作者: 我是一颗大西瓜。

存储方式

从物理划分上 共享内存和分布式内存是两种基本的并行计算机存储方式 除此之外 分布式共享内存也是一种越来越重要的并行计算机存储方式

指令和数据

  • [小粒度]根据一个并行计算机能够同时执行的指令与处理数据的多少 可以把并行计算机分为 SIMD
  • [大粒度]按同时执行的程序和数据的不同 又提出了SPMD Single-Program Multuple-Data 单程序多数据并行计算机和MPMD

根据指令的同时执行和数据的同时执行,计算机系统可以分成以下四类:

  • 单处理器,单数据 (SISD)
  • 单处理器,多数据 (SIMD)
  • 多处理器,单数据 (MISD)
  • 多处理器,多数据 (MIMD)

断续器

单处理器单数据就是“单CPU的机器”,它在单一的数据流上执行指令。在SISD中,指令被顺序地执行。

对于每一个“CPU时钟”,CPU按照下面的顺序执行:

  • Fetch: CPU 从一片内存区域中(寄存器)获得数据和指令
  • 解码: CPU对指令进行解码
  • Execute: 该执行在数据上执行,将结果保存在另一个寄存器中

这种架构(冯·诺依曼体系)的主要元素有以下:

  • 中心内存单元:存储指令和数据
  • CPU:用于从内存单元获得指令/数据,对指令解码并顺序执行它们
  • I/O系统:程序的输入和输出流

传统的单处理器计算机都是经典的SISD系统。下图表述了CPU在Fetch、Decode、Execute的步骤中分别用到了哪些单元:

管理信息系统

这种模型中,有n个处理器,每一个都有自己的控制单元,共享同一个内存单元。在每一个CPU时钟中,从内存获得的数据会被所有的处理器同时处理,每一个处理器按照自己的控制单元发送的指令处理。在这种情况下,并行实际上是指令层面的并行,多个指令在相同的数据上操作。能够合理利用这种架构的问题模型比较特殊,例如数据加密等。因此,MISD在现实中并没有很多用武之地,更多的是作为一个抽象模型的存在。

辛德

SIMD计算机包括多个独立的处理器,每一个都有自己的局部内存,可以用来存储数据。所有的处理器都在单一指令流下工作;具体说,就是有n个数据流,每个处理器处理一个。所有的处理器同时处理每一步,在不同的数据上执行相同的指令。

很多问题都可以用SIMD计算机的架构来解决。这种架构另一个有趣的特性是,这种架构的算法非常好设计,分析和实现。限制是,只有可以被分解成很多个小问题(小问题之间要独立,可以不分先后顺序被相同的指令执行)的问题才可以用这种架构解决。很多超级计算机就是使用这架构设计出来的。例如Connection Machine(1985年的 Thinking Machine)和MPP(NASA-1983).我们在第六章 GPU Python编程中会接触到高级的现代图形处理器(GPU),这种处理器就是内置了很多个SIMD处理单元,使这种架构在今天应用非常广泛。

MIMD

在费林分类中,这种计算机是最广泛使用、也是最强大的一个种类。这种架构有n个处理器,n个指令流,n个数据流。每一个处理器都有自己的控制单元和局部内存,让MIMD架构比SIMD架构的计算能力更强。每一个处理器都在独立的控制单元分配的指令流下工作;因此,处理器可以在不同的数据上运行不同的程序,这样可以解决完全不同的子问题甚至是单一的大问题。在MIMD中,架构是通过线程或进程层面的并行来实现的,这也意味着处理器一般是异步工作的。这种类型的计算机通常用来解决那些没有统一结构、无法用SIMD来解决的问题。如今,很多计算机都应用了这中间架构,例如超级计算机,计算机网络等。然而,有一个问题不得不考虑:异步的算法非常难设计、分析和实现。

并发和并行

并行类型

几种并行区分

程序、线程、进程和超线程

  • 程序程序是一组指令的有序集合。它本身没有任何运行的含义,只是存在于计算机系统的硬盘等存储空间中一个静态的实体文件。比如Linux系统下的binary excutable,windows系统下的exe
  • 进程进程是处于动态条件下由操作系统维护的系统资源管理实体。进程具有自己的生命周期, 反映了一个程序在一定的数据集上运行的全部动态过程。需要加载到内存中,点开一个exe就是开启了一个进程
  • 线程。线程则是进程的一个实体,是比进程更小的能独立运行的基本单位,是被系统调度和分配的基本单元。线程自身基本上不拥有系统资源,只拥有一点在运行中必不可少的资源 (如程序计数器、一组寄存器和调用堆栈), 但它与同属一个进程的其他线程共享所属进程所拥有的全部资源,同一个进程的多个线程可以并发执行,从而提高了系统资源的利用率
  • 超线程超线程技术就是利用特殊的硬件指令,把两个逻辑内核模拟成两个物理芯片,让单颗CPU都能进行线程级并行计算,进而兼容多线程操作系统和软件。一般一个CPU对应一个线程,通过超线程可以达到比如8核16线程

老生常谈,线程和进程的区别和联系

  1. 一个程序的执行至少有一个进程,一个进程至少包含一个线程(主线程)。
  2. 线程的划分尺度小于进程,所以多线程程序并发性更高。
  3. 进程是系统进行资源分配和调度的一个独立单位,线程是CPU调度和分派的基本单位。同一进程内允许多个线程共享其资源。
  4. 进程拥有独立的内存单元,即进程之间相互独立;同一进程内多个线程共享内存。因此,线程间能通过读写操作对它们都可见的内存进行通信,而进程间的相互通信则需要借助于消息的传递。
  5. 每个线程都有一个程序运行的入口,顺序执行序列和程序运行的出口,但线程不能单独执行,必须依存于进程中,由进程控制多个线程的执行
  6. 进程比线程拥有更多的相应状态,所以创建或销毁进程的开销要比创建或销毁线程的开销大得多。因此,进程存在的时间长,而线程则随着计算的进行不断地动态地派生和缩并。
  7. 一个线程可以创建和撤销另一个线程。而且同一进程中的多个线程共享所属进程所拥有的全部资源;同时进程之间也可以并行执行,从而更好地改善了系统资源的利用率。

线程绑定

计算机系统是由一个或多个物理处理器和内存组成,运行的程序会将内存分为两个部分,一部分是共享变量使用的存储区域, 另一部分供各线程的私有变量使用的存储区域。线程绑定是将线程绑定在固定的处理器上, 从而在线程与处理器之间建立一对一的映射关系。如果不进行线程绑定,线程可能在不同的时间片运行在不同的处理器上。我们知道,每个处理器是有自己的多级缓存的,如果线程切来切去,那么cache命中率肯定不高,程序性能也会受到影响。通过线程绑定,程序能够获得更高的cache利用率从而提高程序性能。c++中如何进行线程绑定可以参考https://www.cnblogs.com/wenqiang/p/6049978.html

并行算法评价

理论上来说,n个相同的cpu理论上能提供n倍的计算能力。

但是在实际过程中,并行开销会导致总的执行时间无法线性地减少。这些开销分别为:

  1. 线程的建立和销毁、 线程和线程之间的通信、 线程间的同步等因素造成的开销。
  2. 存在不能并行化的计算代码,造成计算由单个线程完成, 而其他线程则处于闲置状态。
  3. 为争夺共享资源而引起的竞争造成的开销
  4. 由于各cpu工作负载分配的不均衡和内存带宽等因素的限制,一个或多个线程由于缺少工作或因为等待特定事件的发生无法继续执行而处于空闲状态。

并行加速比(加速比)

加速比的定义是顺序程序执行时间除以计算同一结果的并行程序的执行时间

式中,t_sts为一颗CPU程序完成该任务所需串行执行时间;t_ptp为n颗CPU并行执行完成该任务所需时间。由于串行执行时间t_sts?为n颗CPU并行执行完成该 和并行执行时间t_ptp?有多种定义方式。 这样就产生了五种不同的加速比的定义,即相对加速比、实际加速比、绝对加速比、渐近实际加速比和渐近相对加速比。

并行效率(效率)

在实际应用中,影响并行加速比的因素主要是串行计算、并行计算和并行开销三方面。一般情况下, 并行加速比小于CPU的数量。但是,有时会出现一种奇怪的现象,即并行程序能以串行程序快n倍的速度运行,称为超线性加速比。产生超线性加速的原因在于CPU访问的数据都驻留在各自的高速缓存Cache中, 而高速缓存的容量比内存要小, 但读写速度却远高于内存。

衡量并行算法的另一个主要标准是并行效率,它表示的是多颗CPU在进行并行计算时单颗CPU的平均加速比。

理想并行效率为1表明全部CPU都在满负荷工作。通常情况下,并行效率会小于1, 且随CPU数量的增加而减小。

伸缩性

伸缩性用于度量并行机器高效运行的能力,代表跟处理器数量成比例的计算能力 (执行速度)。如果问题的规模和处理器的数量同时增加,性能不会下降。

阿姆德尔定律

阿姆德尔定律广泛使用于处理器设计和并行算法设计。它指出程序能达到的最大加速比被程序的串行部分限制。$S=1/(1-p) $中 1-p1?p 指程序的串行部分。它的意思是,例如一个程序90%的代码都是并行的,但仍存在10%的串行代码,那么系统中即使由无限个处理器能达到的最大加速比仍为9。

古斯塔夫森定律

古斯塔夫森定律在考虑下面的情况之后得出的:

  • 当问题的规模增大时,程序的串行部分保持不变。
  • 当增加处理器的数量时,每个处理器执行的任务仍然相同。

古斯塔夫森定律指出了加速比S(P)=P-\alpha (P-1)SP)=P?αP?1), PP 为处理器的数量, SS S 为加速比,\alphaα 是并行处理器中的非并行的部分。作为对比,阿姆德尔定律将单个处理器的执行时间作为定量跟并行执行时间相比。因此阿姆德尔定律是基于固定的问题规模提出的,它假设程序的整体工作量不会随着机器规模 (也就是处理器数量) 而改变。古斯塔夫森定律补充了阿姆德尔定律没有考虑解决问题所需的资源总量的不足。古斯塔夫森定律解决了这个问题, 它表明设定并行解决方案所允许耗费的时间的最佳方式是考虑所有的计算资源和基于这类信息。

点击下方,第一时间了解华为云新鲜技术~

华为云博客_大数据博客_AI博客_云计算博客_开发者中心-华为云

Hamsters是一个能让JavaScript代码并行执行的原生库,它可以让你面向高性能的JavaScript编程,它是一个开源项目,Github上标星4k+。它的目的就是让你更加容易的利用多线程的强大功能来并行编程实现性能的提升!



Github

https://github.com/austinksmith/Hamsters.js

特性

  • 多功能,通过跨多个线程来最大限度地提高性能。

  • 自动数据聚合,自动将问题分解成较小的部分,并与单个输出并行执行。

  • 自动分类,按字母顺序或数字自动排序输出。

  • LEGACY 支持。

  • 备忘,计算一次后不再浪费 cpu 周期做同样的工作

  • 开放源代码,100%开源代码库,根据Artistic License 2.0发布

支持的环境

  • 所有主流浏览器、IE9 +

  • 现有 Web workers

  • Javascript shell 环境

  • React Native

  • Node.js


如何使用?

安装使用


 bower install WebHamsters
//OR
npm install hamsters.js

  • 1、普通HTMl项目中使用
<!-- HTML4 and (x)HTML -->
 <script type="text/javascript" src="path/to/hamsters.js">
 <!-- HTML5 -->
 <script src="path/to/hamsters.js"></script>

  • 2、React Native
 import hamsters from 'path/to/hamsters';

 import Worker from '...';
 import hamsters from 'hamsters.js';
 hamsters.init({
 Worker: Worker
 });

  • 3、Nodejs
var hamsters=require('hamsters.js');

 var Worker=require('...').Worker;
 var hamsters=require('hamsters.js');
 hamsters.init({
 Worker: Worker
 });

  • 4、入门使用

第一个要理解的是Hamsters.js是一个传递interfafce的消息,因此在使用库调用函数时,我们需要通过将params对象(消息)传递给库来指示库如何操作。


 var params={
 bar: 'foo'
 };
 hamsters.run(params, ....);

我们将使用的下一个参数将是我们想要在一个线程或线程中执行的逻辑,我们之前传递的params对象将在我们的函数的上下文中可访问。现在应该能够看到如何确保可以在线程中访问变量和函数等不同的东西。

 hamsters.run(params, function() {
 var foo=params.bar;
 }); 

第三个也是最后一个参数将是我们的onSuccess回调方法,此函数所需的唯一参数是输出。

 hamsters.run(params, function() {
 var foo=params.bar;
 }, function(results) {
 console.log(results);
 });

回到原始的params对象,为了从库中获得最佳性能和可靠性,需遵循一些约定。Hamsters.js的构建目标是并行而不是并发,尽管库很好地实现了并行执行的主要目标。由于这样做的各种设计决策是为了帮助实现这一目标,其中一个决定是库如何在线程之间分割数据以便执行,因此您希望在多个线程中访问的任何数组必须在您的参数内具有数组索引宾语。

var params={
 array: [1, 2, 3, 4];
 };
 hamsters.run(params, function() {
 for(var i=0; i < params.array; i++) {
 rtn.data.push(params.array[i] * 4);
 }
 }, function(results) {
 });

使用此约定,通过简单地更改params对象中的一个选项,可以非常简单地并行化上述方法。现在使用下面的方法,4个线程将完成相同的任务,每个线程仅在数组的一个数字上运行。

 var params={
 array: [1, 2, 3, 4];
 threads: 4
 };
 hamsters.run(params, function() {
 for(var i=0; i < params.array; i++) {
 rtn.data.push(params.array[i] * 4);
 }
 }, function(results) {
 });

更进一步,库使用一个名为rtn的内部返回对象,这个rtn对象对于库具有一致的方式来处理线程输出是至关重要的。因此,当我们想要从线程返回一个值时,我们需要将结果推送到rtn.data数组中。或者你可以让你的rtn.data输出,但只有你的输出已经是一个数组。

 hamsters.run(params, function() {
 rtn.data.push(params.bar);
 }, function(results) {
 console.log(results); // 'foo';
 });

通过以上代码来看下它传递的参数

var params={
 threads: Integer,
 aggregate: Boolean,
 dataType: String,
 memoize: Boolean
 sort: String,
 };

1、threads这个可选参数将告诉库执行先前声明的函数的线程数,这允许在非常简单的级别上更改您执行的线程数。如果您在此处未提供值,则库默认值为1。

2、aggregate此可选参数将告诉库我们是否要在执行后将各个线程输出聚合在一起,这仅在您跨多个线程执行并且默认为相关时才相关false。

3、dataType此可选参数将通知库我们的数据数组是JavaScript的类型化数组之一,在使用此参数时,库将自动格式化输出以匹配指定的输出dataType。

4、memoize此可选参数旨在与memoization模式结合使用,当启用memoization模式时,此参数允许用户控制单个函数级别是否缓存该函数的结果,其默认值为false。

5、sort此可选参数将告诉库按字母顺序或数字顺序自动对最终输出进行排序,此参数的默认值为,null并且可以使用排序选项进行配置。

params对象中包含的任何其他内容都可以在线程的执行上下文或多个线程中访问,具体取决于您使用库的方式。


除了以上基本使用方式,你可以查看官方的相关文档,有详细的介绍和使用方式,如Promise、排序、memoization、可转移对象、persistence、线程池、限制、设备等


总结

多线程和并行编程在Javascript中本身不是一件容易的事情,但是可借助第三方原生库来弥补它,能让你更加方便的进行多线程的编程,笔者可能并未介绍的非常清楚,如果你有这方面的需求,可以去查案Hamsters.js的相关文档来体验这种编程,希望对你有所帮助!

要:在本文中,将重点关注网页的初始渲染,即它从解析 HTML 开始。 我将探索可能导致高渲染时间的问题,以及如何解决它们。

本文分享自华为云社区《页面首屏渲染性能指南-云社区-华为云》,作者:Ocean2022。

我们知道渲染页面是一个将服务器的响应内容翻译成图片的过程。但是,如果你页面的渲染性能比较糟糕的话,可能会带来相对较高的跳出率。

在本文中,我将重点关注网页的初始渲染,即它从解析 HTML 开始。 我将探索可能导致高渲染时间的问题以及如何解决它们。

关键渲染路径(CRP)

关键渲染路径 (CRP) 是浏览器将代码转换为屏幕上可显示像素的过程。 它有几个阶段,其中一些可以并行执行以节省时间,但有些部分必须依次完成。 如下图所示:

首先,一旦浏览器得到响应,它就会开始解析它。 当它遇到依赖项时,它会尝试下载它。 如果它是一个样式表文件,浏览器必须在渲染页面之前完全解析它,这就是为什么 CSS 会阻塞渲染的原因。

如果是脚本,浏览器必须:停止解析,下载脚本,然后运行。 只有在那之后它才能继续解析,因为 JavaScript 程序可以改变网页的内容(尤其是 HTML)。 这就是为什么 JS 会阻塞解析的原因。

完成所有解析后,浏览器将构建文档对象模型 (DOM) 和级联样式表对象模型 (CSSOM)。 将它们组合在一起得到渲染树。 页面的不显示部分不会进入渲染树,因为它只包含绘制页面所需的数据。

倒数第二步是将渲染树进行布局, 这个阶段也称为回流:就是计算每个渲染树节点的每个位置及其大小的地方。

最后一步是绘制。 它会根据浏览器在前一阶段计算得到的数据对像素进行着色。

优化相关结论

因此,根据这一过程,我们在优化性能方面,得出了一些结论。如果你要提升页面初始化渲染的性能,你需要:

  • 减少传输的数据量
  • 减少浏览器必须下载的资源数量(尤其是阻塞的资源)
  • 减小 CRP 的长度

同时,我们会根据下面 3 个指标来衡量优化的效率:

  • FP(First Paint)
  • FCP(First Contentful Paint)
  • FMP(First Meaningful Paint)

除了渲染时间之外,还有其他一些因素也需要考虑。例如,你的页面使用了多少阻塞资源以及下载它们需要多长时间

性能优化策略

鉴于我们在上面得出的结论,我们得出网站性能优化有三种主要策略:

  1. 尽量减少通过网络传输的数据量;
  2. 减少通过网络传输的资源总数;
  3. 缩短关键渲染路径;

1. 减少要传输的数据量

首先,移除所有未使用的部分,例如 JavaScript 中无法访问的函数、带有从不匹配任何元素的选择器的样式以及被 CSS 永远隐藏的 HTML 标签。 其次,删除所有重复项。

然后,我建议建立一个自动压缩过程。 例如,它应该从你的后端服务中删除所有注释(但不是源代码)以及每个不包含附加信息的字符(例如 JS 中的空白字符)。

完成后,我们剩下的可以是文本字符串。 这意味着我们可以安全地应用诸如 GZIP(大多数浏览器都理解)之类的压缩算法。

最后,还有缓存。 浏览器第一次呈现页面时它不会有帮助,但它会在以后的访问中节省很多。 但是,记住两点至关重要:

  • 如果你使用 CDN,请确保支持缓存并在正确设置。
  • 与其等待资源的到期,不如 将文件的“指纹”嵌入到其 URL 中,以使本地缓存无效。

当然,应该为每个资源定义缓存策略。 有些可能很少改变或根本不会改变,有的则是变化的很快,还有些文件包含敏感的信息(可以使用 “private” 防止 CDN 缓存私有数据)

2. 减少关键资源的总数

“关键”仅指网页正确呈现所需的资源。 因此,我们可以直接跳过所有流程中没有涉及的样式以及脚本文件。

样式

为了告诉浏览器不需要特定的 CSS 文件,我们应该为所有引用样式表的链接设置媒体属性。 使用这种方法,浏览器将只根据需要处理与当前媒体(设备类型、屏幕尺寸)匹配的资源同时降低所有其他样式表的优先级。 例如,如果你将 media=“print” 属性添加到引用样式以打印页面的样式标记,则这些样式不会在不打印媒体时干扰你的关键渲染路径

为了进一步改进该过程,你还可以将一些样式内联这可以为我们节省了至少一次到服务器的往返行程

脚本

如上所述,脚本会阻塞解析,因为它们可以改变 DOM 和 CSSOM。 为了避免这一点,所有脚本标签都必须用属性标记——异步或延迟。

标有 async 的脚本不会阻塞 DOM 构建或 CSSOM,因为它们可以在 CSSOM 构建之前执行。 但请记住,内联脚本无论如何都会阻止 CSSOM,除非你将它们放在 CSS 之上。

相比之下,标有 defer 的脚本将在页面加载结束时进行执行

换句话说,使用 defer,脚本直到页面加载事件被触发后才会执行,而 async 让脚本在文档被解析时就会在后台运行。

3.缩短关键渲染路径长度

最后,应将 CRP 长度缩短到可能的最小值。

作为样式标签属性的媒体查询将减少必须下载的资源总数。 script 标签属性 defer 和 async 将防止相应的脚本阻塞解析。

使用 GZIP 压缩、压缩和归档资源将减少传输数据的大小(从而也减少数据传输时间)。

内联一些样式和脚本也可以减少浏览器和服务器之间的往返次数。

按照最新的最佳性能实践理念,一个网站应该做的最快的第一件事就是展示 ATF 内容。 ATF 代表首屏这是立即可见的区域,无需滚动。 因此,最好以首先加载所需样式和脚本的方式重新排列与渲染相关的所有内容,而其他所有内容都停止(既不解析也不渲染)。

结尾

总而言之,网站性能优化包含了网站响应的各个方面,例如缓存、设置 CDN、重构、资源优化等,但是所有这些都可以逐步完成。 作为 Web 开发人员,你可以将本文作为参考,并始终记住在实验之前和之后测量性能。

浏览器开发人员尽最大努力优化你访问的每个页面的网站性能,这就是浏览器通常实现所谓的“预加载器”的原因。 这部分程序会在你以 HTML 格式请求的资源之前进行扫描,以便一次发出多个请求并让它们并行运行。 这就是为什么在 HTML(逐行)以及脚本标签中保持样式标签彼此靠近的原因。

此外,尝试批量更新 HTML 以避免多个布局事件这些事件不仅由 DOM 或 CSSOM 中的更改触发,而且在设备方向更改和窗口大小调整时也会触发。

点击下方,第一时间了解华为云新鲜技术~

华为云博客_大数据博客_AI博客_云计算博客_开发者中心-华为云