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

分布式深度学习训练中的数据并行(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.mzph.cn/news/186861.shtml

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

相关文章

11-22 SSM3

书城分页查询 使用mybatis分页插件: 请完成登陆注册 -> 跳转到首页 解决前端上架时间点击切换 以及侧边栏点击由背景颜色的改变 完成超链接的绑定点击时间 -> jquery $(document).ready(function() { // 初始化上架时间状态为 true(上架&…

简明指南:使用Kotlin和Fuel库构建JD.com爬虫

概述 爬虫,作为一种自动化从网络上抓取数据的程序,广泛应用于数据分析、信息提取以及竞争对手监控等领域。不同的实现方式和编程语言都能构建出高效的爬虫工具。在本文中,我们将深入介绍如何充分利用Kotlin和Fuel库,构建一个简单…

道路病害检测数据集RDD2022的标签映射关系【参考自官网给出的label_map.pbtxt文件,附查看代码】

TOC 结论 Label ID: 1, Label Name: D00 Label ID: 2, Label Name: D10 Label ID: 3, Label Name: D20 Label ID: 4, Label Name: D40链接地址 https://github.com/sekilab/RoadDamageDetector/ 查看代码 # 打开 label_map.pbtxt 文件 def read_label_map(file_path):label…

【稳定检索|投稿优惠】2024年经济管理与安全科学国际学术会议(EMSSIC 2024)

2024年经济管理与安全科学国际学术会议(EMSSIC 2024) 2024 International Conference on Economic Management and Security Sciences(EMSSIC 2024) 一、【会议简介】 2024年经济管理与安全科学国际学术会议(EMSSIC 2024),将于繁华的上海城召开。这次会议的主题是“…

C++17那些事开篇之类模版参数推导(CTAD)

C17那些事开篇之类模版参数推导(CTAD) 引入 大家好,我是光城,今天开始正式开篇C17的新特性了,期待不,欢迎留言区说出想要更新的特性呀~ C模板元编程一直是C开发者们熟知的一项功能,无论是初学者还是高级开发…

【猜数字游戏】用wxPython实现:基本的游戏框架 + 简单的图形用户界面

【猜数字游戏】 写在最前面猜数字游戏 实现【猜数字游戏】安装wxPython全部代码代码解析1. 初始化界面2. 生成随机数3. 处理猜测4. 特殊功能5. 分数计算 游戏小程序呈现结语 写在最前面 看到了一个比较有意思的问题 https://ask.csdn.net/questions/8038039 猜数字游戏 在这…

苹果TF签名全称TestFlight签名,需要怎么做才可以上架呢?

如果你正在开发一个iOS应用并准备进行内测,TestFlight是苹果提供的一个免费的解决方案,它使开发者可以邀请用户参加应用的测试。以下是一步步的指南,教你如何利用TestFlight进行内测以便于应用后续可以顺利上架App Store。 1: 准备工作 在测…

客餐书房一体布局,新中式风格禅意十足。福州中宅装饰,福州装修

你是否曾经遇到过这样的痛点:装修时不知道该选择什么样的风格,让家居空间显得既时尚又实用?如果你对此感到困惑,那么新中式风格可能正是你想要的选择! 今天,我们将一起探讨一种别样的家居布局,它…

如何用量化交易“做空”来获取收益

最近的市场环境相当不好,今年一年都没有任何主线的模式情况下去交易。更多的都是题材热点聚焦,而且板块轮动过快。市场环境不好的情况下,我们如何通过“做空”来获取收益!量化做空是指利用计算机模型和算法,通过分析市…

EasyExcel如何读取全部Sheet页数据方法

一、需求描述 Excel表格里面大约有20个sheet页,每个sheet页65535条数据,需要读取全部数据,并导入至数据库。 找了好多种方式,EasyExcel比较符合,下面看代码。 二、实现方式 采用EasyExcel框架的doReadAll()方法 1、…

【Redis】Redis高级特性和应用(慢查询、Pipeline、事务、Lua)

目录 Redis的慢查询 慢查询配置 慢查询操作命令 慢查询建议 Pipeline 事务 Redis的事务原理 Redis的watch命令 Pipeline和事务的区别 Lua Lua入门 安装Lua Lua基本语法 注释 标示符 关键词 全局变量 Lua中的数据类型 Lua 中的函数 Lua 变量 Lua中的控制语句…

Unity DOTS《群体战斗弹幕游戏》核心技术分析之3D角色动画

最近DOTS发布了正式的版本, 我们来分享现在流行基于群体战斗的弹幕类游戏,实现的核心原理。今天给大家介绍大规模战斗群体3D角色的动画如何来实现。 DOTS 对角色动画支持的局限性 截止到Unity DOTS发布的版本1.0.16,目前还是无法很好的支持3D角色动画。在DOTS 的b…

群晖NAS配置之搭建WordPress个人博客站点

群晖NAS配置之搭建WordPress个人博客站点 之前写了一些ngrok和frp给群晖nas做内网穿透,今天分享一下在群晖nas下安装wordpress的教程。 WordPress是一个开源的内容管理系统(CMS),最初是用来搭建博客的,但后来发展成为…

中职组网络安全-linux渗透测试-Server2203(环境+解析)

任务环境说明: 服务器场景:Server2203(关闭链接) 用户名:hacker 密码:123456 1.使用渗透机对服务器信息收集,并将服务器中SSH服务端口号作为flag提交; FLAG:2232 2. 使用渗透机对…

单链表相关经典算法OJ题:移除链表元素

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 前言 题目:移除链表元素 解法一: 解法一的代码实现: 解法二: 解法二代码的实现: 总结 前言 世上有两种耀眼的…

改进LiteOS中物理内存分配算法(详细实验步骤+相关源码解读)

一、实验要求 优化TLSF算法,将Best-fit策略优化为Good-fit策略,进一步降低时间复杂度至O(1)。 优化思路: 1.初始化时预先为每个索引中的内存块挂上若干空闲块,在实际分配时避免分割(split)操作&#xff…

TA-Lib学习研究笔记(三)——Volatility Indicator

TA-Lib学习研究笔记(三)——Volatility Indicator 波动率指标函数组 Volatility Indicators: [‘ATR’, ‘NATR’, ‘TRANGE’] 1.ATR Average True Range 函数名:ATR 名称:真实波动幅度均值 简介:真实波动幅度均值…

MacOS 14 系统 XCode15、 Flutter 开发 IOS

Flutter 系列文章目录 MacOS14 Sonoma 安装 Flutter 开发环境 MacOS 系统 Flutter开发Android 环境配置MacOS 系统 Flutter开发IOS 环境配置​​​​​​​ 前言 前面我们已经在MacOS14 M3芯片上安装好 Flutter环境,包括开发工具 VsCode 、Android Stuiod,那么fl…

CCF CSP认证 历年题目自练Day50

题目 试题编号: 201809-3 试题名称: 元素选择器 时间限制: 1.0s 内存限制: 256.0MB 问题描述: 题目分析(个人理解) 还是先理解题意,关于html的部分,可以按照样例画出…

【数据分析 | Numpy】Numpy模块系列指南(一),从设计架构说起

🤵‍♂️ 个人主页: AI_magician 📡主页地址: 作者简介:CSDN内容合伙人,全栈领域优质创作者。 👨‍💻景愿:旨在于能和更多的热爱计算机的伙伴一起成长!!&…