C++17并行算法与HIPSTDPAR

C++17 parallel algorithms and HIPSTDPAR — ROCm Blogs (amd.com)

C++17标准在原有的C++标准库中引入了并行算法的概念。像std::transform这样的并行版本算法保持了与常规串行版本相同的签名,只是增加了一个额外的参数来指定使用的执行策略。这种灵活性使得已经使用C++标准库算法的用户只需对代码进行最小程度的修改,就能利用多核架构的优势。

从ROCm6.1开始,只要用户愿意添加一两个额外的编译器标志,这些并行算法就能通过HIPSTDPAR无缝卸载到AMD加速器上执行。尽管HIPSTDPAR提供的功能适用于所有AMD GPU(包括消费级显卡),但本篇博客主要聚焦于使用ROCm6.1的AMD CDNA2™和CDNA3™架构(分别对应MI200和MI300系列卡)。作为示例代码,我们将关注这里提供的旅行商问题(TSP)求解器。

旅行商问题

旅行商问题旨在回答这样一个问题:“给定一系列城市及其之间的距离,访问每个城市恰好一次并返回出发城市的最短可能路线是什么?”由于指数级的复杂性,这一问题特别难以解决(属于NP难问题),每增加一个城市,需要检查的组合数量就会呈指数增长。对于超过17或18个城市的实例,仅通过枚举所有可能组合并检查每一种的计算成本是不可行的。在实际应用中,会采用高级方法(如切割平面法和分支定界技术),但为了本文的目的,我们关注的是TSP的一个极度并行化的暴力求解实现。

TSP求解器实现

我们关注的TSP求解器依赖于以下函数来检查各个城市排列,并选择成本/距离最低的那一个。以下是不使用任何并行性的详细实现:

template<int N>
route_cost find_best_route(int const* distances)
{
  return std::transform_reduce(
    counting_iterator(0),
    counting_iterator(factorial(N)),
    route_cost(),
    [](route_cost x, route_cost y) { return x.cost < y.cost ? x : y; },
    [=](int64_t i) {
    int cost = 0;

    route_iterator<N> it(i);

    // first city visited
    int from = it.first();

    // visited all other cities in the chosen route
    // and compute cost
    while (!it.done())
    {
      int to = it.next();
      cost += distances[to + N*from];
      from = to;
    }

    // update best_route -> reduction
    return route_cost(i, cost);
  });
}

std::transform_reduce算法执行两个操作:
1. 由作为最后一个参数传递的lambda函数实现的转换(相当于map操作);
2. 由作为第四个参数传递的lambda函数表示的缩减操作。

上述函数遍历从0到N!的所有元素,每个元素表示所有城市的特定排列,计算特定路径的成本,并返回一个包含路径ID和与路径关联成本的route_cost对象实例。最后,通过比较各种路径的成本并选择成本最低的路径来执行缩减操作。

在AMD Zen4处理器上,这个串行代码计算涉及12个城市的TSP实例的最佳路径大约需要11.52秒。同样的代码计算涉及13个城市的TSP实例需要大约156秒。这是由于TSP强加的搜索空间的指数增长。

执行策略和HIPSTDPAR

由于N!个路径彼此独立,计算它们的各自成本是一种令人尴尬的并行操作。C++17允许开发人员仅通过在算法调用时传递执行策略作为第一个参数来轻松并行化前面的代码。C++17标准定义了三种可能的执行策略:

  • std::execution::sequenced_policy及相应的策略对象作为参数std::execution::seq
  • std::execution::parallel_policy及相应的策略对象作为参数std::execution::par
  • std::execution::parallel_unsequenced_policy及相应的策略对象作为参数std::execution::par_unseq

执行策略允许用户向实现传达关于用户代码应强制执行/维护的不变量的信息,从而允许后者可能采用更合适/高性能的执行方式。

std::execution::sequenced_policy
顺序策略限制实现对调用算法的线程执行所有操作,禁止可能的并行执行。所有操作在调用者线程内不确定地排序,这意味着在同一个线程内对同一算法的后续调用可以以不同的顺序执行其操作。

std::execution::parallel_policy
并行策略允许实现采用并行执行。操作可以在调用算法的线程上执行,也可以在标准库实现创建的线程上执行。对于描述算法调用的计算所使用的所有线程,所有操作在线程内不确定地排序。此外,对元素访问函数调用本身不提供排序保证。与顺序策略相比,对算法使用的各种组件施加了额外的约束。特别是,迭代器、值和可调用对象的操作及其传递闭包必须是数据竞争自由的。

在前面的示例中,可以通过将std::execution:par策略作为第一个额外参数传递给find_best_route函数来并行化:

return std::transform_reduce(
  std::execution::par, // THE SIMPLE CHANGE
  counting_iterator(0),
  counting_iterator(factorial(N)),
  route_cost(),
  [](route_cost x, route_cost y) { return x.cost < y.cost ? x : y; },
  [=](int64_t i)

通过进行这一简单更改,代码现在将在所有可用CPU核心上运行。在配备了48个Zen4逻辑核心的MI300A的CPU部分上,解决涉及12个城市的TSP实例大约需要0.34秒。与串行版本所需的11.52秒相比,这种并行运行速度提高了近34倍!对于涉及13个城市的TSP实例,并行版本大约需要5秒。最后,对于涉及14个城市的更大问题,48个Zen4逻辑核心大约需要77秒。
std::execution::parallel_unsequenced_policy
此策略确保用户提供的代码满足最严格的要求。使用此策略调用的算法可能会以无序且未排序的方式执行各个步骤。这意味着各种操作可以在同一线程上相互交错。此外,任何给定的操作都可以在一个线程上开始并在另一个线程上结束。在指定并行未排序策略时,用户保证不采用涉及调用与另一个函数同步的函数的操作。实际上,这意味着用户代码不执行任何内存分配/释放,仅依赖于std::atomic的无锁特化,并且不依赖于诸如std::mutex之类的同步原语。

此策略目前是唯一一个可以选择将并行性卸载到AMD加速器的策略。要触发使用并行未排序策略调用的所有并行算法的GPU卸载,必须在编译时传递--hipstdpar标志。此外,对于除当前默认值(gfx906)之外的GPU目标,用户还必须传递--offload-arch=,指定正在使用的GPU。

在MI300A上,只需切换策略并使用上述标志重新编译,涉及13个城市的TSP实例的执行时间就减少到0.5秒。当使用14个城市时,使用MI300A的GPU部分将执行时间从并行版本的48个Zen4逻辑核心所需的77秒减少到4.8秒。因为每个人都喜欢一个好的表格,让我们通过总结从CPU上的顺序执行到卸载到加速器的并行未排序执行的进展来结束本节:

14-city TSP

Timing (s)

seq

2337

par

77

par_unseq on CPU

75

par_unseq on GPU

4.8

TeaLeaf

一个展示HIPSTDPAR使用和性能的更复杂例子是TeaLeaf。这个代码是来自英国布里斯托大学的TeaLeaf热传导小应用程序的一个C++实现。多个实现示例展示了各种并行编程范式,包括HIP和并行化标准算法。这使得我们能够在优化的基于HIP实现和一个基于HIPSTDPAR的实现之间,进行公平的性能比较。为了这个测试,我们选择了`tea_bm_5.in`基准测试,包含一个4000x4000单元的2D网格和10个时间步。

对于HIPSTDPAR版本,在一张MI300A卡上,获得了以下输出:

Timestep 10
CG:                    3679 iterations
Wallclock:             40.884s
Avg. time per cell:    2.555271e-06
Error:                 9.805532e-31

Checking results...
Expected 9.546235158221428e+01
Actual   9.546235158231138e+01
This run PASSED (Difference is within 0.00000000%)

至于HIP版本,它的性能如下:

Timestep 10
CG:                    3679 iterations
Wallclock:             34.286s
Avg. time per cell:    2.142853e-06
Error:                 9.962546e-31

Checking results...
Expected 9.546235158221428e+01
Actual   9.546235158231144e+01
This run PASSED (Difference is within 0.00000000%)

两个版本之间的性能差异源于处理非常驻内存初次分页的开销。为了“使事情更公平”,可以调整HIP版本也使用 hipMallocManaged() ,而不是 hipMalloc()。这种特定的配置已经在 TeaLeaf 的 HIP 版本中可用,并且可以通过在编译时传递一个简单的标志来启用。以下是当使用 hipMallocManaged() 和 XNACK 为所有GPU分配时 TeaLeaf 的 HIP 版本的输出。

Timestep 10
 CG:                    3679 iterations
 Wallclock:             39.573s
 Avg. time per cell:    2.473331e-06
 Error:                 9.962546e-31

 Checking results...
 Expected 9.546235158221428e+01
 Actual   9.546235158231144e+01
 This run PASSED (Difference is within 0.00000000%)

正如预期的那样,当引入 hipMallocManaged() 时,HIP 版本的性能与 HIPSTDPAR 版本观察到的性能相当。最后,我们将指出,正在进行的工作有望减少开销,从而使得卸载版本的性能更接近 HIP 版本。

HIPSTDPAR的核心机理

(“nuts and bolts”是一个英语习语,意思是“基本的工作原理”或者“详细的实际细节”。它通常用于描述某件事情的具体、基础的组成部分,而非它的广义或概念性内容。这个习语源自于实际的螺母(nuts)和螺栓(bolts),它们是用来物理连接各种构件的基础硬件。因此,在这种情况下,提及 “HIPSTDPAR的nuts and bolts” 就是指想要深入了解HIPSTDPAR如何实际工作的所有基础和技术细节。翻译成“HIPSTDPAR的核心机理”是为了捕捉这种想要详细了解它的工作原理和组件的意图。因此,“Nuts”(螺母)在这里不是指实际的螺母,而是泛指细节或基本部分;“bolts”(螺栓)也是如此。这个习语与硬件本身无关,而是关于理解事物的基本组成部分和工作方式。 )

HIPSTDPAR的C++标准并行算法执行能够卸载到GPU,取决于LLVM编译器、HIPSTDPAR以及rocThrust之间的交互。从ROCm6.1开始,用来编译常规HIP代码的LLVM编译器将可以在传递了`--hipstdpar`标记的情况下,将调用带有`parallel_unsequenced_policy`执行策略的标准算法转发至HIPSTDPAR头文件库。这个仅头文件的库负责将C++标准库使用的并行算法映射为等价的rocThrust算法调用。这种非常简单的设计为并行标准算法的卸载实现提供了低开销。此时一个自然的问题是:“计算是很好,但是它操纵的内存怎么办?”默认情况下,HIPSTDPAR假设底层系统启用了HMM(异构内存管理),并且通过实现在XNACK(例如,导出HSA_XNACK=1)之上的可以重试的页错误处理机制,可以进行页面迁移。这种模式被称为HMM模式。

当这两个要求都得到满趀时,卸载到GPU的代码(通过rocThrust实现)触发页面迁移机制,数据会自动从主机迁移到设备。在MI300A上,虽然物理迁移既不需要也没有用处,但通过XNACK处理页面错误仍然是必要的。有关页面迁移的更多详情,请参考以下博客文章。("Post"在这个上下文中通常被用来指代网站或者博客上发布的一篇文章或记录。在网络术语中,"post"可以是一个名词,指的就是所发布的内容;也可以是一个动词,指的是发布信息的行为。所以,链接AMD Instinct™ MI200 GPU memory space overview - amd-lab-notes - AMD GPUOpen引向的内容是AMD官方网站上的一个页面,其中包含了关于MI200系列加速器内存空间的详绽解读和如何启用页面迁移的信息,因此将其译为"博客文章"是合理的,因它确实是一篇公开发布、围绕具体主题的文章。翻译为"博客文章"是为了传达这是一篇可能相对非正式或以教育、信息共享为目的的文章,它与学术论文或新闻报道的语调和格式不同。在中文中,博客文章也常被简称为"博文"。在这种语境下,直接翻译为"文章"也是可以接受的,只是"博客文章"提供了关于文章发布平台的额外信息。)

在没有启用HMM/XNACK的系统上,我们仍然可以通过传递一个额外的编译标记:`--hipstdpar-interpose-alloc`来使用HIPSTDPAR。这个标记会指导编译器用在HIPSTDPAR头文件库中实现的兼容的hipManagedMemory分配来替换所有的动态内存分配。例如,如果正在编译的应用程序或其传递性包含的内容通过`operator new`分配了自由存储内存,那么这个调用会被替换为对`__hipstdpar_operator_new`的调用。通过查看该函数在HIPSTDPAR库中的实现,我们可以看到实际的分配是通过`hipMallocManaged()`函数执行的。这样在一个没有启用HMM的系统上,主机内存被固定并且能够直接被GPU访问,而不需要任何页面错误驱动的迁移到GPU内存。这种模式被称为"Interposition模式"。

限制

对于HMM和Interposition模式,以下限制适用:

1. 函数指针和所有相关功能,例如动态多态性,不能被传递给算法调用的用户提供的可调用对象(直接或传递性地)使用;
2. 全局/命名空间范围/静态/线程存储期变量不能被用户提供的可调用对象(直接或传递性地)以名称使用;
   - 当在HMM模式下执行时,它们可以通过地址被使用,例如:

namespace { int foo = 42; }

bool never(const vector<int>& v) {
  return any_of(execution::par_unseq, cbegin(v), cend(v), [](auto&& x) {
    return x == foo;
  });
}

bool only_in_hmm_mode(const vector<int>& v) {
  return any_of(execution::par_unseq, cbegin(v), cend(v),
                [p = &foo](auto&& x) { return x == *p; });
}

3. 只有那些使用`parallel_unsequenced_policy`调用的算法才能成为卸载的候选;
4. 只有那些使用模型为`random_access_iterator`的迭代器参数调用的算法才能成为卸载的候选;
5. 用户提供的可调用对象不能使用异常;
6. 用户提供的可调用对象不能使用动态内存分配(例如`operator new`);

7. 不可能有选择地卸载,即不能指示只有一些使用`parallel_unsequenced_policy`调用的算法在加速器上执行。

除上述限制外,使用Interposition模式还带来以下额外的限制:

1. 所有期望互操作的代码都必须使用`--hipstdpar-interpose-alloc`标志重新编译,即不安全地组合已独立编译的库;
2. 自动存储期(即栈分配的)变量不能被用户提供的可调用对象(直接或传递性地)使用,例如:
 

bool never(const vector<int>& v, int n) {
  return any_of(execution::par_unseq, cbegin(v), cend(v),
                [p = &n](auto&& x) { return x == *p; });
}

但为什么?

在经历了一段快速旅程之后,提出“但这对我这个C++开发者有什么好处?”并不是没有道理的。[HIPSTDPAR](GitHub - ROCm/roc-stdpar)的目标是让任何使用标准算法的C++开发者都能够在不增加认知负担的情况下利用GPU加速。应用程序开发者可以坚定地留在标准C++世界中,而不必跨入HIP或SYCL等GPU特定语言的崭新世界。幸运的是,我们的特定例子能够提供一些有限的、定量的洞察,让我们了解我们离这个目标有多近。Tealeaf的作者已经通过多种编程接口实现了求解器,这意味着我们可以使用`cloc`工具计算`tsp.cpp`实现所需的代码行数:

Programming Interface

LoC

Kokkos

145

OpenACC

142

OpenMP

116

Standard C++ Serial

112

Standard C++ Parallel Algorithms

107

SYCL

169

显然,使用由编译器标志驱动的卸载(如HIPSTDPAR所启用)可以节省相当多的打字工作 - 例如,与SYCL相比,最多可以节省57%。这使向GPU加速执行的转变过程更自然。因此,程序员至少在初始阶段可以专注于算法/问题解决,并发现适用于GPU的通用算法优化,而无需深入GPU“奥秘”。

太长了,没读。直接告诉我怎么快速进行吧

要快速地利用GPU加速,可以在Linux环境下使用HIPSTDPAR(未来也会支持Windows)。假设你已经根据ROCm的快速开始教程设置好了环境,通过包管理器安装`hipstdpar`包就可以得到所有必需的组件。由于标准库实现细节问题(参见例如备注3),可能还需要安装TBB库。例如,在Ubuntu上安装`libtbb-dev`。接着,如果你有一个主程序文件`main.cpp`,它使用标准算法解决了某个问题,那么只需简单地运行以下编译命令:

clang++ --hipstdpar main.cpp -o main

就可以自动将使用`std::execution::parallel_unsequenced_policy`执行策略的所有算法调用进行GPU加速,假定你的目标GPU与`gfx906`兼容(即Vega20)。如果是另外的GPU目标,则需要指定:

clang++ --hipstdpar --offload-arch=gfx90a main.cpp -o main

结论

我们在本文中提供了一个关于ROCm支持C++标准并行算法进行卸载加速的高层次概述,展示了现有的C++开发者如何利用GPU加速,而无需采用任何新的、特定于GPU的语言(例如HIP)或指令(例如OpenMP)。我们相信,这种标准且极其易于访问的方式,以利用硬件并行性,对于针对MI300A加速器的应用程序将特别有益,其中CPU和GPU共享同一池的HBM。虽然今天没有演示,但APU架构和HIPSTDPAR的结合可以实现CPU与GPU之间的细粒度合作,通过统一的编程接口使它们成为真正的对等实体。

如果想更深入了解HIPSTDPAR的编译器支持,可以阅读相关的AMD-LLVM文档。

本文作者感谢Bob Robey和Justin Chang对文章的有益审阅。如果有任何问题,可以在GitHub Discussions上与我们联系。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/707442.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AI数字人的开源解决方案

目前&#xff0c;国内外已经涌现出一些优秀的数字人开源解决方案&#xff0c;这些解决方案为开发者提供了构建数字人应用的工具和基础设施。以下是一些比较知名的数字人开源解决方案。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 1…

Sklearn中逻辑回归建模

分类模型的评估 回归模型的评估方法&#xff0c;主要有均方误差MSE&#xff0c;R方得分等指标&#xff0c;在分类模型中&#xff0c;我们主要应用的是准确率这个评估指标&#xff0c;除此之外&#xff0c;常用的二分类模型的模型评估指标还有召回率&#xff08;Recall&#xff…

振弦采集仪在隧道工程中的安全监测与控制研究

振弦采集仪在隧道工程中的安全监测与控制研究 隧道工程的安全监测与控制是保障隧道施工和运营安全的重要工作。隧道工程常面临的问题包括地层变形、地下水位变化、地震影响等&#xff0c;这些问题对隧道结构的安全性和使用寿命有着重要影响。因此&#xff0c;隧道工程中的安全…

JVM性能优化案例:减少对象频繁创建

JVM性能优化案例&#xff1a;减少对象频繁创建 案例背景 某金融应用系统在处理大量并发交易时&#xff0c;响应时间过长&#xff0c;并且有时出现内存溢出&#xff08;OutOfMemoryError&#xff09;的问题。经过分析&#xff0c;发现问题主要出在频繁的对象创建和较差的内存管…

OpenCV查找图像中的轮廓并且展示

1、查找轮廓随机用不同的颜色画出 import cv2 import numpy as npdef get_contour_colors(num_contours):# 定义颜色表 (BGR 格式)colors [(255, 0, 0),(255, 50, 0),(255, 100, 0),(255, 150, 0),(255, 200, 0),(255, 255, 0),(200, 255, 0),(150, 255, 0),(100, 255, 0),(5…

Linux常⽤服务器构建-ssh和scp

目录 1.ssh <1>ssh介绍 <2>安装ssh A.安装ssh服务器 B.远程登陆 <3>使⽤ssh连接服务器 2.scp 本地⽂件复制到远程&#xff1a; 本地⽬录复制到远程&#xff1a; 远程⽂件复制到本地&#xff1a; 远程⽬录复制到本地&#xff1a; 1.ssh <1>…

【LLM之RAG】Self-RAG论文阅读笔记

研究背景 尽管大型语言模型&#xff08;LLM&#xff09;展示出了显著的能力&#xff0c;但它们在生成回答时经常包含事实错误&#xff0c;因为它们仅依赖于封装在模型中的参数知识。增强型检索生成&#xff08;Retrieval-Augmented Generation, RAG&#xff09;是一种方法&…

leetcode695 岛屿的最大面积

题目 给你一个大小为 m x n 的二进制矩阵 grid 。 岛屿 是由一些相邻的 1 (代表土地) 构成的组合&#xff0c;这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0&#xff08;代表水&#xff09;包围着。 岛屿的面积是岛上值…

ubuntu18.04离线源制作

给客户部署有时需要纯内网环境&#xff0c;那这样就连不了网络。 一些包就下载不下来&#xff0c;而大家都知道用deb离线安装是非常麻烦的&#xff0c;各种依赖让你装不出来。 这里教大家打包源。 我准备2台机器&#xff0c;42和41 42可以联网&#xff0c;41不能联网。我想在…

在AI云原生时代应该如何应对复杂的算力环境

引言 随着在2019年ChatGPT4的爆火,AI这个之前常常被人觉得非常高深的技术渐渐的被越来越多的人们所了解,越来越多的公司、组织和开发者开始投入AI的使用和开发中来.随着AI和LLM的火热,算力资源也变的越来越紧缺,所以如何高效的管理和使用算力资源也变成了必须要面对的问题。 …

2024全站焕新,重塑3D轻量体验!

3D模型当前应用广泛&#xff0c;正以惊人的速度实现数据增长&#xff0c;轻量化需求随之增多。老子云团队一直在探索如何借助自研轻量化技术的能力&#xff0c;打破用户模型处理思维惯性&#xff0c;构建更高效、实用、简单的体验范式&#xff0c;来帮助用户解决3D素材数据处理…

教学辅助系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;教师管理&#xff0c;作业管理&#xff0c;学生管理&#xff0c;管理员管理&#xff0c;作业提交管理&#xff0c;教学视频管理 教室账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0…

L1-098 再进去几个人

L1-098 再进去几个人 分数 5 全屏浏览 切换布局 作者 陈越 单位 浙江大学 数学家、生物学家和物理学家坐在街头咖啡屋里&#xff0c;看着人们从街对面的一间房子走进走出。他们先看到两个人进去。时光流逝。他们又看到三个人出来。 物理学家:“测量不够准确。” 生物学家:“…

不到2毛钱的常用小功率功放AiP8002带关断模式的 2W 音频功率放大器

前言&#xff1a; SOP-8 8002封装和丝印 8002是当前小功率音频功放的不二选择&#xff0c;性能较好&#xff0c;价格低廉&#xff0c;不到2毛钱&#xff0c;国内有大把厂家生产&#xff0c;不同厂家生产的最大功率有2W、3W两种。本文以无锡中微爱芯的AIP8002做介绍。 1、概 述…

11.2 Go 常用包介绍

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

充电桩出口:跨国贸易的机遇与挑战之旅

在新能源浪潮席卷全球的今天&#xff0c;充电桩作为电动汽车的“加油站”&#xff0c;正逐渐从幕后走向台前。 而在这场跨国贸易的舞台上&#xff0c;充电桩的出口之路&#xff0c;既充满了诱人的机遇&#xff0c;也伴随着不小的挑战。 机遇&#xff0c;源自日益增长的全球市场…

stable-diffusion.cpp 文字生成图片

纯 C/C 中 [Stable Diffusion] 的推断 https://github.com/CompVis/stable-diffusion ## 特点 - 基于 [ggml]&#xff08;https://github.com/ggerganov/ggml&#xff09; 的普通 C/C 实现&#xff0c;工作方式与 [llama.cpp]&#xff08;https://github.com/ggerganov/llam…

ArcGIS for js 4.x FeatureLayer 点选查询

示例&#xff1a; 代码如下&#xff1a; <template><view id"mapView"></view></template><script setup> import "arcgis/core/assets/esri/themes/light/main.css"; import Map from "arcgis/core/Map.js"; im…

NPM常见问题

文章目录 NPM常见问题1. 使用淘宝源安装包出错2. listen EADDRINUSE 服务端口被占用报错3. npm start 启动后过一会崩溃结束&#xff1a;内存溢出4. npm install的时候使用特定的源安装5. npm安装指定版本、最新版本6. npm ERR! cb() never called! 解决7. Unable to authentic…

java线程池讲解!核心参数

创建方式 | 构造方法 Executor构造方法 存放线程的容器&#xff1a; private final HashSet<Worker> workers new HashSet<Worker>(); 构造方法&#xff1a; public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit uni…