『PyTorch学习笔记』分布式深度学习训练中的数据并行(DP/DDP) VS 模型并行

news2024/11/16 15:49:13
分布式深度学习训练中的数据并行(DP/DDP) VS 模型并行

文章目录

  • 一. 介绍
  • 二. 并行数据加载
    • 2.1. 加载数据步骤
    • 2.2. PyTorch 1.0 中的数据加载器(Dataloader)
  • 二. 数据并行
    • 2.1. DP(DataParallel)的基本原理
      • 2.1.1. 从流程上理解
      • 2.1.2. 从模式角度理解
      • 2.1.3. 从操作系统角度看
      • 2.1.4. 低效率
    • 2.2. DDP(DistributedDataParallel)的基本原理
    • 2.3. DP和DDP对比
    • 2.4. 分布式中的几个概念
  • 三. 模型并行(ModelParallel)
  • 四. 参考文献

一. 介绍

  • 现代深度学习模型中的参数数量越来越大,数据集的规模也急剧增加。要在大型数据集上训练复杂的现代深度学习模型,必须使用多节点训练,否则会花费很长时间。在分布式深度学习训练中,人们可能总会看到数据并行模型并行。在这篇博文中,将讨论这两种深度学习并行方法的理论、逻辑和一些误导点。

二. 并行数据加载

2.1. 加载数据步骤

  • 流行的深度学习框架(例如Pytorch和Tensorflow)为分布式培训提供内置支持。从广义上讲,从磁盘读取输入数据开始,加载数据涉及四个步骤:
  1. 将数据从磁盘加载到主机:在这个阶段,数据从磁盘(可能是HDD或SSD)读取到主机的内存中。这个过程涉及文件系统的I/O操作,通常使用高级API,如Python的open函数,或者在深度学习框架中,可以使用数据加载器(如PyTorch的DataLoader或TensorFlow的tf.data API)来实现。这些数据加载器通常具有多线程或多进程功能,可以异步读取数据,并将其加载到CPU的内存中。
  2. 将数据从可分页内存传输到主机上的固定内存。请参阅有关分页和固定的内存更多信息:可分页(pageable)内存和固定(pinned)内存都是主机内存的类型。可分页内存是普通的系统内存,操作系统可以将其页(一个内存管理单位)移动到磁盘上(即分页)。固定内存,又称为非分页内存,是指操作系统不能移动到磁盘的内存区域。固定内存的数据传输到GPU通常比从可分页内存传输更快,因为它避免了额外的复制步骤,并且可以直接通过DMA(直接内存访问)进行。在深度学习训练中,经常将数据从可分页内存复制到固定内存以准备传输到GPU。
  3. 将数据从固定内存传输到GPU:一旦数据位于固定内存中,它就可以通过高带宽的 PCIe总线(Peripheral Component Interface Express,总线和接口标准) 高效地传输到GPU内存中。深度学习框架通常提供了简化这个过程的工具。例如,在PyTorch中,你可以使用.to(device)或.cuda()方法将张量移动到GPU。此过程是由DMA引擎管理的,可以在不占用CPU资源的情况下进行。
  4. 在GPU上向前和向后传递:当数据位于GPU内存中时,可以开始训练过程,即进行模型的前向和反向传播。在前向传播中,模型的参数(也必须在GPU内存中)用于计算输出和损失函数。然后,通过反向传播,根据损失函数相对于模型参数的梯度,更新模型参数。这些计算完全在GPU上进行,利用其并行计算能力来加速训练过程。
  • 补充2:谈到主机内存,有两个主要类别——可分页(或“非固定”)[pageable (or “un-pinned”)]内存和页面锁定(或“固定”)内存[page-locked (or “pinned”)]。当您在 C 程序中使用 malloc 分配内存时,分配是在可分页内存中完成的。 GPU 无法直接从可分页主机内存访问数据,因此当调用从可分页主机内存到设备内存的数据传输时,CUDA 驱动程序首先分配一个临时固定主机数组,将主机数据复制到固定数组,然后传输数据从固定数组传输到设备内存,如下图所示(有关更多信息,请参阅本页)
  • 补充3:GPU 通常通过 PCIe 连接连接到主板,来自主(主机)内存的数据必须通过此 PCIe 链路传输到 GPU 内存。该链路的预期带宽是多少?为了回答这个问题,让我们看一下有关 PCIe 链路的一些基本信息。

2.2. PyTorch 1.0 中的数据加载器(Dataloader)

  • PyTorch中的Dataloader提供使用多个进程(通过将num_workers>0设置)从磁盘加载数据以及将多页数据从可分页内存到固定内存(pinned memory) 的能力(通过设置) pin_memory = True)。
  • 一般的,对于大批量的数据,若仅有一个线程用于加载数据,则数据加载时间占主导地位,这意味着无论我们如何加快数据处理速度,性能都会受到数据加载时间的限制。现在,设置num_workers = 3以及pin_memory = True。这样,可以使用多个进程从磁盘读取不重叠的数据,并启动生产者-消费者线程以将这些进程读取的数据从可分页的内存转移到固定的内存。
  • 多个进程能够更快地加载数据,并且当数据处理时间足够长时,流水线数据加载几乎可以完全隐藏数据加载延迟。这是因为在处理当前批次的同时,将从磁盘读取下一个批次的数据,并将其传输到固定内存。如果处理当前批次的时间足够长,则下一个批次的数据将立即可用。这个想法还建议如何为num_workers参数设置适当的值。应该设置此参数,以使从磁盘读取批处理数据的速度比GPU处理当前批处理的速度更快(但不能更高,因为这只会浪费多个进程使用的系统资源)。

二. 数据并行

  • 当一张 GPU 可以存储一个模型时,可以采用数据并行得到更准确的梯度或者加速训练,即每个 GPU 复制一份模型,将一批样本分为多份输入各个模型并行计算。因为求导以及加和都是线性的,数据并行在数学上也有效。
  • 假设我们一个 batch 有 n n n 个样本,一共有 k k k 个 GPU 每个 GPU 分到 m j m_j mj 个样本。假设样本刚好等分,则有 m j = n k m_{j}=\frac nk mj=kn 。我们考虑总的损失函数 l l l 对参数 w w w 的导数:
    ∂ L o s s ∂ w = ∂ ⌊ 1 n ∑ i = 1 n l ( x i , y i ) ⌋ ∂ w = 1 n ∑ i = 1 n ∂ l ( x i , y i ) ∂ w = m 1 n ∂ [ 1 m 1 ∑ i = 1 m 1 l ( x i , y i ) ] ∂ w + m 2 n ∂ [ 1 m 2 ∑ i = m 1 + 1 m 1 + m 2 l ( x i , y i ) ] ∂ w + ⋯ + m k n ∂ [ 1 m k ∑ i = m k − 1 + 1 m k − 1 + m k l ( x i , y i ) ] ∂ w = ∑ j = 1 k m j n ∂ [ 1 m j ∑ i = m j − 1 + 1 m j − 1 + m j l ( x i , y i ) ] ∂ w = ∑ j = 1 k m j n ∂ l o s s j ∂ w \begin{aligned} \begin{aligned}\frac{\partial Loss}{\partial w}\end{aligned}& =\frac{\partial\left\lfloor\frac1n\sum_{i=1}^nl(x_i,y_i)\right\rfloor}{\partial w} \\ &=\frac1n\sum_{i=1}^n\frac{\partial l(x_i,y_i)}{\partial w} \\ &=\frac{m_1}n\frac{\partial\left[\frac1{m_1}\sum_{i=1}^{m_1}l(x_i,y_i)\right]}{\partial w}+\frac{m_2}n\frac{\partial\left[\frac1{m_2}\sum_{i=m_1+1}^{m_1+m_2}l(x_i,y_i)\right]}{\partial w}+\cdots+\frac{m_k}n\frac{\partial\left[\frac1{m_k}\sum_{i=m_{k-1}+1}^{m_{k-1}+m_k}l(x_i,y_i)\right]}{\partial w} \\ &=\sum_{j=1}^k\frac{m_j}n\frac{\partial\left[\frac1{m_j}\sum_{i=m_{j-1}+1}^{m_{j-1}+m_j}l(x_i,y_i)\right]}{\partial w} \\ &=\sum_{j=1}^k\frac{m_j}n\frac{\partial loss_j}{\partial w} \end{aligned} wLoss=wn1i=1nl(xi,yi)=n1i=1nwl(xi,yi)=nm1w[m11i=1m1l(xi,yi)]+nm2w[m21i=m1+1m1+m2l(xi,yi)]++nmkw[mk1i=mk1+1mk1+mkl(xi,yi)]=j=1knmjw[mj1i=mj1+1mj1+mjl(xi,yi)]=j=1knmjwlossj
  • 其中 w w w 是模型参数, ∂ L o s s ∂ w \frac{\partial Loss}{\partial w} wLoss 是大小为 n n n 的big batch的真实梯度, ∂ l o s s j ∂ w \frac{\partial loss_j}{\partial w} wlossj 是 GPU/node k k k 的小批量梯度, x i x_i xi y i y_i yi 是数据点 i i i l ( x i , y i ) l(x_i,y_i) l(xi,yi) 是根据前向传播计算出的数据点 i i i 的损失, n n n 是数据集中数据点的总数, k k k 是 GPU/Node的总数, m k m_k mk 是分配给 GPU/节点的样本数量, m 1 + m 2 + ⋯ + m k = n m_1+m_2+\cdots+m_k=n m1+m2++mk=n。当 m 1 = m 2 = ⋯ = m k = n k m_1=m_2=\cdots=m_k=\frac nk m1=m2==mk=kn时,我们可以进一步有:
    ∂ L o s s ∂ w = 1 k [ ∂ l o s s 1 ∂ w + ∂ l o s s 2 ∂ w + ⋯ + ∂ l o s s k ∂ w ] \frac{\partial{Loss}}{ \partial w }=\frac{1}{k}\big[\frac{\partial loss_1}{\partial w}+\frac{\partial loss_2}{\partial w}+\cdots+\frac{\partial loss_k}{\partial w}\big] wLoss=k1[wloss1+wloss2++wlossk]
  • 这里对于每个GPU/Node,我们使用相同的模型/参数进行前向传播,我们向每个节点发送一小批不同的数据,正常计算梯度,并将梯度发送回主节点。此步骤是异步的,因为每个 GPU/节点的速度略有不同。一旦我们获得了所有梯度(我们在这里进行同步),我们就计算梯度的(加权)平均值,并使用梯度的(加权)平均值来更新模型/参数。然后我们继续下一次迭代。

2.1. DP(DataParallel)的基本原理

  • DP 的好处是,使用起来非常方便,只需要将原来单卡的 module 用 DP 改成多卡:
model = nn.DataParallel(model)

2.1.1. 从流程上理解

  • DataParallel 从流程上来看,是通过将整个小批次(minibatch)数据加载到主线程上,然后将子小批次(sub-minibatches)数据分散到整个GPU网络中来工作。
  1. 把 minibatch 数据从page-locked memory 传输到 GPU 0(master),Master GPU 也持有模型,其他GPU拥有模型的 stale copy。
  2. 在 GPUs 之间 scatter minibatch 数据。具体是将输入一个 minibatch 的数据均分成多份,分别送到对应的 GPU 进行计算。
  3. 在 GPUs 之间复制模型。与 Module 相关的所有数据也都会复制多份。
  4. 在每个GPU之上运行前向传播,计算输出。PyTorch 使用多线程来并行前向传播,每个 GPU 在单独的线程上将针对各自的输入数据独立并行地进行 forward 计算
  5. 在 master GPU 之上收集(gather)输出,计算损失。即通过将网络输出与批次中每个元素的真实数据标签进行比较来计算损失函数值。
  6. 把损失在 GPUs 之间 scatter,在各个GPU之上运行后向传播,计算参数梯度。
  7. 在 GPU 0 之上归并梯度。
  8. 更新梯度参数。①进行梯度下降,并更新主GPU上的模型参数;②由于模型参数仅在主GPU上更新,而其他从属GPU此时并不是同步更新的,所以需要将更新后的模型参数复制到剩余的从属 GPU 中,以此来实现并行。

在这里插入图片描述

2.1.2. 从模式角度理解

  • 首先我们先给出一个技术上的概括,从模式角度看:
  • DP 可以被认为是类似参数服务器的应用
  • DDP 可以被认为是集合通讯的应用
  • 参数服务器大致可以分为 master 和 worker,而DP 基于单机多卡,所以对应关系如下:
  • worker :所有GPU(包括GPU 0)都是worker,都负责计算和训练网络。
  • master :GPU 0(并非 GPU 真实标号,而是输入参数 device_ids 的首位)也负责整合梯度,更新参数。
  • 所以我们重点看看 GPU 0。
  • DataParallel会将网络模型默认放在GPU 0上,然后把模型从GPU 0 拷贝到其他的GPU,各个GPU开始并行训练,接着 GPU 0 作为master来进行梯度的汇总和模型的更新,最后将计算任务下发给其他GPU。这非常类似参数服务器的机制。
  • 从官方图也可以看到同样的信息。
  • DataParallel会开启 单进程多线程 进行数据并行化
  • 前向传播:首先,模型和mini-batch的数据会被放到GPU:0上(master GPU),之后,GPU:0会把数据分割成sub-mini-batch并scatter(分发)到其他GPU上,第二步,GPU:0会把自己的模型参数复制到其他GPU上,每个GPU拥有相同的模型参数。第三步,每个GPU在单独的线程上对其sub-mini-batch的数据前向传播,得到模型的输出结结果。第四部,GPU:0会收集所有GPU的输出结果。
  • 反向传播:GPU:0得到所有的结果之后会与真实的label计算loss并得到loss的梯度,GPU:0会把loss梯度 Scatter到所有GPU上,每个GPU会根据loss梯度反向传播计算所有参数的梯度,之后,所有GPU上计算得到的参数梯度会汇总到GPU:0上,GPU:0进而对参数进行更新。这就完成了一个batch的模型训练。

在这里插入图片描述

  • 有人说GPU:0是个自私的家伙,它把其他GPU都当成工具人来用,核心机密不传授,我只给你们数据,不给你label,你们得到结果之后给我我给你们计算loss和loss的梯度,然后分发给你们去给我计算参数的梯度,之后我得到这些参数的梯度之后我去更新参数,之后等下回需要你们的时候再去给你们其他GPU去分发我更新好的参数。
  • 这是一个悲伤的故事,首先 单进程多线程 就似乎已经注定的结局,python的全局解释锁给这些附属的GPU戴上了沉沉的牢拷,其他GPU想奋起反抗,但是DP里面只有一个优化器Optimizer,这个优化器Optimizer只在主GPU上进行参数更新,当环境不在改变的时候,其他GPU选择了躺平,当GPU:0忙前忙后去分发数据、汇总梯度,更新参数的时候,其他GPU就静静躺着。

2.1.3. 从操作系统角度看

  • 从操作系统角度看,DP 和 DDP 有如下不同(我们属于提前剧透):
  • DataParallel 是单进程,多线程的并行训练方式,并且只能在单台机器上运行
  • DistributedDataParallel 是多进程,并且适用于单机和多机训练。DistributedDataParallel 还预先复制模型,而不是在每次迭代时复制模型,并避免了全局解释器锁定。

2.1.4. 低效率

  • 这种效率不高的数据并行方法,注定要被淘汰。是的,我们迎来了DDP(DistributedDataParallel)

2.2. DDP(DistributedDataParallel)的基本原理

  • DistributedDataParallel,支持 all-reduce,broadcast,send 和 receive 等等。通过 MPI 实现 CPU 通信,通过 NCCL 实现 GPU 通信。可以用于 单机多卡也可用于多机多卡, 官方也曾经提到用 DistributedDataParallel 解决 DataParallel 速度慢,GPU负载不均衡的问题。
  • 效果比DataParallel好太多!!!torch.distributed相对于torch.nn.DataParalle 是一个底层的API,所以我们要修改我们的代码,使其能够独立的在机器(节点)中运行。
  • 与 DataParallel 的单进程控制多 GPU 不同,在 distributed 的帮助下,我们只需要编写一份代码,torch 就会自动将其分配给n个进程,分别在 n 个 GPU 上运行。不再有主GPU,每个GPU执行相同的任务。对每个GPU的训练都是在自己的过程中进行的。每个进程都从磁盘加载其自己的数据。分布式数据采样器可确保加载的数据在各个进程之间不重叠。损失函数的前向传播和计算在每个GPU上独立执行。因此,不需要收集网络输出。在反向传播期间,梯度下降在所有GPU上均被执行,从而确保每个GPU在反向传播结束时最终得到平均梯度的相同副本。
  • 区别: DDP通过多进程实现的。也就是说操作系统会为每个GPU创建一个进程,从而避免了Python解释器GIL带来的性能开销。而DataParallel()是通过单进程控制多线程来实现的

在这里插入图片描述

  • 假如我们有N张显卡:
  • (缓解GIL限制)在DDP模式下,会有N个进程被启动,每个进程在一张卡上加载一个模型,这些模型的参数在数值上是相同的。
  • (Ring-Reduce加速)在模型训练时,各个进程通过一种叫Ring-Reduce的方法与其他进程通讯,交换各自的梯度,从而获得所有进程的梯度;
  • (实际上就是Data Parallelism)各个进程用平均后的梯度更新自己的参数,因为各个进程的初始参数、更新梯度是一致的,所以更新后的参数也是完全相同的。

2.3. DP和DDP对比

  • 1. 每个进程对应一个独立的训练过程,且只对梯度等少量数据进行信息交换。

在这里插入图片描述

  • 2. 每个进程包含独立的解释器和 GIL

在这里插入图片描述

2.4. 分布式中的几个概念

  • 在 PyTorch 中,分布式并行处理是指在多个进程之间分配任务以便并行执行的一种方法,特别是在多个计算节点上进行深度学习模型的训练。以下是一些关键概念的详细解释:
  • Group(进程组):在分布式计算中,group指的是进程组。一个进程组包含了一系列可以进行集体通信操作的进程。在PyTorch中,默认情况下所有的进程都会被自动分配到一个全局默认的进程组中,我们称之为 “world”。在这个默认组中,所有的进程都可以互相通信
  • 有时,你可能需要更精细的控制,比如你只想在一部分进程之间进行通信。在这种情况下,你可以通过 torch.distributed.new_group 接口创建一个新的进程组,该组包含全局进程组的一个子集。
  • World Size(全局进程个数)world size是全局进程组中进程的总数。在多机分布式训练中,world size等于所有机器上的进程总和。例如,如果你在每台机器上启动了4个进程,且一共使用了3台机器,那么 world size 就是12。
  • Rank(进程序号):在分布式训练中,rank是分配给每个进程的唯一标识符,其范围是从0到world size - 1。每个进程在通信时都会使用它的rank作为标识。通常情况下,rank为0的进程被认为是主进程(master),它可能负责一些协调工作,比如汇总数据或打印日志。
  • Local Rank(局部GPU编号):在单个节点(机器)上可能有多个进程,每个进程可能管理一个或多个GPU。local_rank是指一个进程所管理的GPU在该节点上的编号。例如,如果一个节点上有8个GPU,那么每个进程的local_rank的范围将是0到7。在PyTorch中,这通常是由torch.distributed.launch模块在启动进程时内部设置的。这对于确保每个进程使用不同的GPU至关重要,以避免资源冲突。

三. 模型并行(ModelParallel)

  • 模型并行性对我来说听起来很可怕,但它实际上与数学无关。这是分配计算机资源的本能。有时我们无法将所有数据放入(GPU)内存中,因为我们的深度学习模型中有太多层和参数。因此,我们可以将深度学习模型分成几个部分,将几个连续的层放在一个节点上并计算其梯度。这样,单个节点的参数数量就减少了,并且可以利用数据进行训练,得到更准确的梯度。
  • 例如,我们有 10 个 GPU,我们想要训练一个简单的 ResNet50 模型。我们可以将前 5 层分配给 GPU #1,后 5 层分配给 GPU #2,依此类推,最后 5 层分配给 GPU #10。在训练期间,在每次迭代中,前向传播必须首先在 GPU #1 中完成。 GPU #2 正在等待 GPU #1 的输出,GPU #3 正在等待 GPU #2 的输出,依此类推。一旦前向传播完成。我们计算驻留在 GPU #10 中的最后一层的梯度,并更新 GPU #10 中这些层的模型参数。然后梯度反向传播到 GPU #9 中的前一层,等等。每个 GPU/节点就像工厂生产线中的一个隔间,它等待来自前一个隔间的产品,并将自己的产品发送到下一个隔间。
  • 在我看来,模型并行性的名称具有误导性,不应将其视为并行计算的示例。更好的名称可能是 “模型序列化”因为它在并行计算中使用串行方法而不是并行方法。然而,在某些场景下,某些神经网络中的某些层(例如 Siamese Network)实际上是“并行的”。这样,模型并行性可以在某种程度上表现得像真正的并行计算。然而,数据并行是100%并行计算

四. 参考文献

  • pytorch(分布式)数据并行个人实践总结——DataParallel/DistributedDataParallel
  • Data Parallelism VS Model Parallelism in Distributed Deep Learning Training
  • https://d2l.ai/chapter_computational-performance/parameterserver.html
  • 💥 Training Neural Nets on Larger Batches: Practical Tips for 1-GPU, Multi-GPU & Distributed setups
  • [原创][深度][PyTorch] DDP系列第一篇:入门教程
  • [原创][深度][PyTorch] DDP系列第二篇:实现原理与源代码解析
  • PyTorch 源码解读之 DP & DDP:模型并行和分布式训练解析
  • 【DDP详解: 1】起底DP和DDP,数据分布式训练炼丹秘籍!
  • 同时推荐一个官方设计笔记,讲得很详细,有兴趣可以看看。https://pytorch.org/docs/stable/notes/ddp.html

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

行业研究:2023年建筑涂料市场需求及发展方向分析

目前绿色化经济成为社会经济主旋律,涂料行业作为高污染行业,国家层面出台了一系列政策规划引导行业向绿色、安全、环保发展。例如,禁止使用高VOCs含量的溶剂型涂料,推广水性涂料等低VOCs含量涂料,鼓励外商投资高性能涂…

JavaScript中数据类型的转换

前端面试大全JavaScript中数据类型的转换 🌟经典真题 🌟数据类型转换介绍 🌟强制转换(显式转换) Number( ) String( ) Boolean( ) 🌟自动转换(隐式转换) 自动转换为布尔值 …

安防监控系统的工作原理是什么?具体包含哪些组成部分?

关于安防监控系统,大家熟知的就是监控系统平台,其实不然,智能视频安防监控系统涵盖的内容非常多,今天小编就和大家一起来探讨一下。 安防监控视频系统主要分为以下7大类: 1、 摄像头采集图像 安防监控系统通常使用摄…

2023-11-30 LeetCode每日一题(确定两个字符串是否接近)

2023-11-30每日一题 一、题目编号 1657. 确定两个字符串是否接近二、题目链接 点击跳转到题目位置 三、题目描述 如果可以使用以下操作从一个字符串得到另一个字符串,则认为两个字符串 接近 : 操作 1:交换任意两个 现有 字符。 例如&…

谭巍主任专业角度解读:疣体脱落前的症状是什么?

我们时常会发现身体各个部位长出一些赘生物,有些属于皮肤良性改变,而有些则是病毒引起的,称之为疣体。然而在疣体脱落之前,通常会出现一些症状,这些症状可能因人而异,但以下是一些常见的迹象: 1…

Python自动化测试面试经典题

相信大家经历过许多面试都会有这样的感受:好不容易通过了 2 -3轮技术面试,但是薪资不够理想;要么被面试的测试专家虐的不要不要的。但每一次的面试也能让自己认识到不足之处,这样才有利于后续拿到理想的offer。 牛鹭学院的学子对…

IPv6+2.0网络切片技术在电子政务网的应用实践详解

IPv6是面向5G、云网/算网融合的智能IP技术,具有包含可编程路径、快速业务发放、自动化运维、质量可视化、SLA保障和应用感知等特点。IPv6将万物互联提升到了万物智联,赋能百行百业高质量数字化转型。 图示:“IPv6”技术创新体系发展的三个阶段…

【多线程】-- 07 线程礼让与线程强制执行

多线程 5 线程状态 5.3 线程礼让 Thread.yield()礼让线程,让当前正在执行的线程暂停,但不阻塞将线程从运行状态转为就绪状态让CPU重新调度,礼让不一定成功!由CPU调度决定。 package com.duo.state;//测试礼让线程 public clas…

2023年计网408

第33题 33.在下图所示的分组交换网络中,主机H1和H2通过路由器互连,2段链路的带宽均为100Mbps、 时延带宽积(即单向传播时延带宽)均为1000bits。若 H1向 H2发送1个大小为 1MB的文件,分组长度为1000B,则从H1开始发送时刻起到H2收到…

josef JZ-7Y-33静态中间继电器 电压DC220V 板前接线

系列型号: JZ-7Y-201X静态中间继电器;JZ-7J-201X静态中间继电器; JZ-7L-201X静态中间继电器;JZ-7D-201X静态中间继电器; JZ-7Y-201静态中间继电器;JZ-7J-201静态中间继电器; JZ-7L-201静态中…

Selenium Grid

Selenium Grid 什么是Selenium Grid Selenium是Selenium套件的一部分,它专门用于并行运行多个测试用例在不同的浏览器、操作系统和机器上 Selenium Grid的两个版本 Grid1与Grid2两个版本的原理和基本工作方式完全相同,Grid2同时支持Selenium1和Selenium2&#x…

​[Oracle]编写程序,键盘输入n,计算1+前n项之和。测试案例:输入:10 输出:22.47​

编写程序,键盘输入n,计算1前n项之和。 测试案例: 输入:10 输出:22.47 代码如下: set serveroutput on declare v_sum number:0;v_n number;beginv_n:&n;for i in 1..v_n loopv_sum:v_sumsqrt(i); end loop; d…

博捷芯:半导体芯片切割,一道精细工艺的科技之门

在半导体制造的过程中,芯片切割是一道重要的环节,它不仅决定了芯片的尺寸和形状,还直接影响到芯片的性能和使用效果。随着科技的不断进步,芯片切割技术也在不断发展,成为半导体制造领域中一道精细工艺的科技之门。 芯片…

【编码艺术:掌握String类函数接口的妙用指南】

【本节目标】 1. 为什么要学习string类 2. 标准库中的string类 1. 为什么要学习string类 1.1 C语言中的字符串 C语言中,字符串是以\0结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数, 但是这些库函数与…

基于Java+SpringBoot+Vue3+Uniapp+TypeScript(有视频教程)前后端分离的求职招聘小程序

博主介绍:✌全网粉丝5W,全栈开发工程师,从事多年软件开发,在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战,博主也曾写过优秀论文,查重率极低,在这方面有丰富的经验…

LeetCode(46)汇总区间【区间】【简单】

目录 1.题目2.答案3.提交结果截图 链接: 汇总区间 1.题目 给定一个 无重复元素 的 有序 整数数组 nums 。 返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说,nums 的每个元素都恰好被某个区间范围所覆盖,并且不存在属于某…

Linux下如何运行.sh文件

运行环境为ubuntu20.04 如在/home/zoe/map运行.sh文件: 进入到/home/zoe文件夹下: cd /home/zoe/map 第一种运行方式: sh play.sh 结果: 第二种方式: 使用chmod修改文件的执行权限,然后运行 chmod x …

六、初识FreeRTOS之FreeRTOS的任务挂起和恢复函数介绍

本节需要掌握以下内容: 1,任务的挂起与恢复的API函数(熟悉) 2,任务挂起与恢复实验(掌握) 3,课堂总结(掌握) 一、任务的挂起与恢复的API函数(熟…

使用MechanicalSoup库的爬虫程序

1. 首先,我们需要导入MechanicalSoup库和requests库,这两个库都是Python中爬虫常用的库。 2. 接着,我们要设置一个代理服务器,使用proxy_host和proxy_port参数来指定。 3. 使用requests.get方法来获取网页的HTML代码。 4. 使用Bea…

CentOS 7安装Java 8

前言 这是我在这个网站整理的笔记,有错误的地方请指出,关注我,接下来还会持续更新。 作者:神的孩子都在歌唱 要在CentOS 7上安装Java 8,请按照以下步骤操作: 打开终端并以root身份登录。 更新系统软件包: …