利用PyTorch Profiler实现大模型的性能分析和故障排查

news2024/12/27 0:43:24

本文介绍PyTorch Profiler结合TensorBoard分析模型性能,分别从数据加载、数据传输、GPU计算、模型编译等优化思路去提升模型训练的性能。最后总结了一些会导致CPU和GPU同步的常见的PyTorch API,在使用这些API时需要考虑是否会带来性能影响。

PyTorch Profiler简介

什么是PyTorch Profiler?

PyTorch作为一款应用于深度学习领域的库,其影响力日益显著。PyTorch Profiler是PyTorch生态中的一个组件,用来帮助开发者分析大规模深度学习模型的性能。该组件不仅收集GPU硬件层面的数据,还同步整合PyTorch框架内的运行信息,通过这种深层次的数据关联,自动识别模型执行过程中的瓶颈问题,并提出具体的改进建议,所有收集到的信息经过整理后,均能在TensorBoard上直观展示。真正意义上做到了从数据收集、分析到可视化,为PyTorch用户提供了一站式解决方案。此外,新版本的PyTorch Profiler API已被直接内置到PyTorch框架中,您无需额外安装其他软件包,即可直接启动模型分析流程。

PyTorch Profiler的优势与局限性

优势

局限性

  • 不仅能够收集GPU硬件级别的信息,同时也能够收集PyTorch特定操作上下文的信息。

  • PyTorch原生支持,无需执行其他安装操作,只需要在Python代码中执行import torch.profiler即可。

  • 使用简单,您只需添加几行代码便可启用Profiler。

  • 支持对长时间运行的训练任务特定的Epoch进行Profiling。

  • 支持分布式训练计算与通信操作的Profiling。

  • 仅针对PyTorch,其他训练框架不支持。

  • 提供GPU Profiling指标比较有限(例如:无法获取具体某种GPU计算单元的使用情况),目前仅包括:

    • GPU Utilization:GPU利用率。

    • Est. SM Efficiency:估计的SM的效率。

    • Est. Achieved Occupancy:估计的SM占用率。

    • Kernel Time using Tensor Cores:使用Tensor Core的时间与所有Kernel运行时间的占比。

  • 对某些操作的时间统计并不是真正的执行时间,如果cudaMemcpy操作与Kernel执行出现重叠,并且cudaMemcpy操作时间远远小于Kernel执行(即cudaMemcpy执行时间隐藏在Kernel执行中),此时PyTorch Profiler统计cudaMemcpy为0。

环境准备

本示例使用的GPU卡型为V100-SXM2-32GB,并使用PyTorch官方的Docker镜像(pytorch/pytorch:2.0.1-cuda11.7-cudnn8-devel)运行代码,需要注意一点是,启动Docker时,需为容器挂载Shm(docker run --shm-size=)和共享宿主机IPC(--ipc=host)。

  1. 创建具备GPU节点的集群。具体操作,请参见创建GPU集群。

    ACK集群支持的GPU机型,请参见ACK支持的GPU机型。

  2. 拉起PyTorch环境。

    利用Arena拉起PyTorch环境

    利用kubectl拉起PyTorch环境

    1. 安装最新版Arena。具体操作,请参见配置Arena客户端。

    2. 执行以下命令,修改values.yaml文件。

      vi ~/charts/pytorchjob/values.yaml

      打开文件后,将shmSizeprivileged的参数值修改为如下值:

      shmSize: 20Gi # 通过shmSize参数指定共享内存的大小。
      privileged: true # 因为要使用Nsight System,建议容器使用特权模式,即privileged=true。
    3. 执行以下命令,拉起PyTorch环境。

      使用Arena工具向ACK集群提交一个简单的PyTorch作业请求。作业本身并不执行任何实际的深度学习训练任务,而是通过执行sleep 10d命令来模拟一个持续运行的进程,便于测试和验证作业提交流程。

      arena submit pytorch \
          --name=workspace \
          --gpus=1 \
          --image=nvcr.io/nvidia/pytorch:24.03-py3 \
          "sleep 10d"

      预期输出:

      pytorchjob.kubeflow.org/workspace created
      INFO[0005] The Job workspace has been submitted successfully 
      INFO[0005] You can run `arena get workspace --type pytorchjob -n default` to check the job status 

      输出结果显示PyTorch作业已成功创建。

    4. 执行以下命令,查看作业是否已经处于Running状态。

      arena get workspace
    5. 当作业处于Running状态后,执行以下命令进入PyTorch容器,即可进行Profiling。

      arena attach workspace

      预期输出:

      Hello! Arena attach the container pytorch of instance workspace-master-0
    6. 可在PyTorch容器中检查Nsight System是否安装。

      nsys profile --help
  3. 创建一个NodePort类型的Service资源,并为Service开启负载均衡,来暴露TensorBoard服务。

      apiVersion: v1
      kind: Service
      metadata:
        name: tensorboard-service
      spec:
        type: NodePort
        ports:
          - port: 6006
            targetPort: 6006
            nodePort: 30006  # 可以选择一个未使用的端口号
        selector:
          app: pytorch-workspace

    然后您可以通过<节点IP>:30006访问TensorBoard。

以下介绍如何利用PyTorch Profiler完成模型的性能分析与优化。

说明

具体操作前,您可以通过阅读PyTorch Profiler官方文档,了解PyTorch Profiler的基本用法;以及通过阅读Quick Usage Instructions,了解TensorBoard各指标的含义。

步骤一:创建一个深度学习模型并进行训练

  1. 创建并拷贝以下内容到demo.py文件中,用于创建一个深度学习模型并进行训练。

    为了演示模型性能分析与优化的思路和技巧,使用了TensorBoard-plugin tutorial 中的示例模型,完整代码如下。

    # 导入所有必需的库。
    import torch
    import torch.nn
    import torch.optim
    import torch.profiler
    import torch.utils.data
    import torchvision.datasets
    import torchvision.models
    import torchvision.transforms as T
    
    # 准备输入数据。本教程中,使用CIFAR10数据集,将其转换为所需格式,并使用DataLoader加载每个批次。
    transform = T.Compose(
        [T.Resize(224),
         T.ToTensor(),
         T.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
    train_set = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
    train_loader = torch.utils.data.DataLoader(train_set, batch_size=32, shuffle=True)
    
    # 创建ResNet模型、损失函数和优化器对象。为了在GPU上运行,将模型和损失转移到GPU设备上。
    device = torch.device("cuda:0")
    model = torchvision.models.resnet18(weights='IMAGENET1K_V1').cuda(device)
    criterion = torch.nn.CrossEntropyLoss().cuda(device) 
    optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)
    model.train()
    
    # 定义对每批输入数据的训练步骤。
    def train(data):
        inputs, labels = data[0].to(device=device), data[1].to(device=device)
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    
    # 使用分析器记录执行事件。
    with torch.profiler.profile(
            schedule=torch.profiler.schedule(wait=1, warmup=4, active=3, repeat=1),
            on_trace_ready=torch.profiler.tensorboard_trace_handler('./resnet18'),
            record_shapes=True,
            profile_memory=True,
            with_stack=True
    ) as prof:
        for step, batch_data in enumerate(train_loader):
            if step >= (1 + 4 + 3) * 1:
                break
            train(batch_data)
            prof.step()  # 需要在每个步骤上调用此函数以通知分析器步骤边界。

    该代码涵盖了从数据准备、模型配置、训练流程设置到性能分析的整个机器学习模型训练过程。

  2. 进入PyTorch容器内部,运行demo.py文件。

    python demo.py

    预期输出:

    Downloading: "https://download.pytorch.org/models/resnet18-f37072fd.pth" to /root/.cache/torch/hub/checkpoints/resnet18-f37072fd.pth
    100.0%
    [root@iZ2ze*******hZ ~]# ls
    data  demo.py  resnet18

    输出结果表明,当运行完代码以后,会在当前目录下产生一个目录resnet18,使用Tensorboard打开。

    tensorboard --logdir=resnet18

    效果如下:

    image

    从上图中,可以看到:

    • GPU的利用率为51.88%

    • 平均每一个Step(PyTorch Profiler将一个Mini Batch称为一个Step)时间为59.997ms。

    • 每秒处理样本数为:32 / (59.997 / 1000) = 533.36 samples/s。

步骤二:优化模型

对于一个单机的深度学习训练任务,可优化的方向有如下几个:

  • 数据加载(Data Loading):通常情况下,把数据从Disk(或者其他网络存储系统)加载到主机内存,并对数据做预处理操作(例如,去除噪声值),笼统地称为数据加载阶段。

  • 数据传输(Data Transmission):将数据从主机内存传输到GPU内存。

  • 训练(Training):GPU计算单元(CUDA Core、Tensor Core)利用这些数据做训练操作。

image

一般考虑的方向是上面前面三部分,下面将对每部分作一些案例介绍。

优化方向1:加速数据加载

在训练任务中,缩短数据加载时间对提升整体效率至关重要。理想情况下,希望数据加载耗时能够低于GPU的实际计算时间,以避免因数据准备滞后而导致的GPU空置(等待数据加载)现象,从而确保GPU资源得到充分利用。

优化前:

PyTorch的DataLoader支持多个Worker(Multi-process)同时工作,能够同时处理单个Mini Batch的数据加载任务。

未优化前,仅使用1个Worker加载数据。从TensorBoard中可以看到,整个Step的耗时为55.875ms,但是Data Loading阶段却花费了29.528ms,存在优化的空间。

image

优化后:

尝试将DataLoader的Worker数由1变成8:

train_loader = torch.utils.data.DataLoader(train_set,num_workers=8, batch_size=32, shuffle=True)

重新运行代码,将Profling结果导入Tensorboard中,可以看到GPU利用率变成了83.44%,平均每一个Step消耗时间为36.196ms。

image

在Trace视图中,查看Data Loading时间为0.101ms。

image

总结

缩短数据加载时间后,将相关指标与Baseline做一下对比:

对比项

优化前(1 Worker)

优化后(Enable 8 Workers)

GPU利用率

51.88%

83.44%

Step平均耗时

59.997ms

36.196ms

Data Loading耗时(以Step5为例)

29.528ms

0.101ms

平均每秒处理样本数(samples/s)

533.360

884.076

可以看到,仅仅将Worker数由1变成8,模型性能就有很大的提升。

优化方向2:提升数据传输效率

在处理每个Batch时,需要先将数据从Host端内存传输到GPU内存中。对于较小的Batch Size,这种数据传输可能不会显著影响整体处理时间;然而,当Batch Size较大时,减少数据传输时间能够显著提高模型的运行效率。

为了缩短数据传输时间,可以将预处理后的数据直接存储在固定内存(Pin Memory)中。在创建DataLoader时,通过设置pin_memory=True选项,能够有效加速数据从CPU到GPU的传输过程,从而提升整体性能。此外,在数据传输过程中,使用non_blocking=True参数可以使数据传输异步进行,进一步提高效率。

开启Pin Memory

在创建DataLoader时,指定pin_memory=True选项。

train_loader = torch.utils.data.DataLoader(train_set,num_workers=8,pin_memory=True, batch_size=32, shuffle=True)

同时,在传输数据时,使数据传输异步进行,设置non_blocking=True。

inputs, labels = data[0].to(device=device,non_blocking=True), data[1].to(device=device,non_blocking=True)

重新运行代码,将Profiling结果导入TensorBoard中。

开启Pin Memory后,观察到GPU利用率显著提升至91.86%,同时每个步骤的耗时减少到26.734毫秒。

image

为什么使用异步传输?
  • 如果没有指定non_blocking=True,数据传输会是一个同步过程。这意味着程序会在数据完全传输到GPU之前暂停执行后续指令。具体来说,当执行如下代码时:

        inputs, labels = data[0].to(device=device), data[1].to(device=device)

    如果未设置non_blocking=True,那么CPU会在数据(即data[0]data[1])从主机端传输至GPU内存的过程中停止运行,直到整个数据传输完成为止。在TensorBoard中可以看到,aten::_to_copy函数会调用两个CUDA API:一个是用于实际数据传输的cudaMemcpyAsync;另一个是cudaStreamSynchronize,它用来确保数据传输完成后才继续执行。

    image

  • 当设置non_blocking=True后,数据传输将变成一个异步行为。此时,aten::_to_copy不会调用cudaStreamSynchronize来等待数据传输结束。相反,一旦开始执行下面的第一行代码后,函数就会立即返回控制权给CPU,允许其继续执行下一条语句,即使此时数据可能还在从主机向GPU传输的过程中。

    inputs, labels = data[0].to(device=device,non_blocking=True), data[1].to(device=device,non_blocking=True)
    outputs = model(inputs)
    loss = criterion(outputs, labels)

    image

总结

在开启8个worker的基础上,开启Pin Memory后性能有所提升,如下所示。

对比项

优化前(Enable 8 Workers)

优化后(Enable Pin Memory)

GPU利用率

83.44%

91.86%

Step平均耗时

36.196ms

26.734ms

平均每秒处理样本数(samples/s)

884.076

1196.978

优化方向3:启用自动混合精度

在开启Pin Memory的基础上,继续寻找优化模型的机会。

GPU内核(Kernel)视图提供了Kernel的执行时间,这为提升GPU利用率提供一定参考。如下所示,从GPU内核视图中可以观察到Tensor的利用率为0,这可能是一个提升GPU利用率的方向。

image

利用Tensor Core可以进行混合精度计算,PyTorch支持在训练的时候开启混合精度计算( Automatic Mixed Precision (AMP)),在AMP模式下,GPU上部分Tensor自动转换为低精度的16位浮点数,并在GPU张量核心上运行。

说明

AMP的完整实现可能需要梯度缩放,本文的演示中没有包括这一点。在生产环境中,请正确使用AMP。

修改以下代码开启AMP模式。

# train step
def train(data):
    inputs, labels = data[0].to(device=device,non_blocking=True), data[1].to(device=device,non_blocking=True)
    # 开启amp
    with torch.autocast(device_type='cuda', dtype=torch.float16):
    	outputs = model(inputs)
    	loss = criterion(outputs, labels)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

重新运行,在TensorBoard中各指标展示如下。使用AMP后比不使用的效果还差,GPU利用率由91.86%变为48.66%,每个Step耗时由26.734ms增加到29.611ms。

image

不过在GPU内核视图中,可以看到Tensor Core的使用占比由0上升至27.8%。

image

为什么使用AMP后效果更差了?原因可能是精度转换本身是有开销,如果Batch Size比较小(或者模型比较简单),那么精度转换本身消耗的时间比较数据计算时间还长,导致无法从AMP获取更多的收益。

为了验证猜想的正确性,尝试将当前Batch Size(32)增加到128,然后比较在Batch Size为128的情况下,开启AMP与不开启AMP的效果对比。

train_loader = torch.utils.data.DataLoader(train_set,num_workers=8,pin_memory=True, batch_size=128, shuffle=True)

在没有开启AMP情况下,GPU利用率为91.68%,平均每个Step消耗时间为118.376ms,每秒处理样本数为:128 / (118.376 / 1000) = 1081.300。

image

开启AMP后,GPU利用率为84.54%,平均每个Step消耗时间为54.638ms,平均每秒处理样本数为:128 / (54.638 / 1000) = 2342.692。

image

总结

可以看到,开启AMP情况下的平均每个Step消耗时间缩短到不开启AMP的情况下的一半。开启AMP后GPU利用率这个指标值虽然有所降低,但这并影响。因为GPU利用率高并不一定说明是高效使用GPU(但是GPU利用率低一定说明GPU并未充分利用)。

优化方向4:实施模型编译优化

默认情况下,PyTorch采用的是即时编译,您可以借助PyTorch的编译API将模型编译成图模式(Graph Mode)。

模型编译需要修改如下代码:

model = torchvision.models.resnet18(weights='IMAGENET1K_V1').cuda(device)
model = torch.compile(model)

为了更好地演示模型编译效果,本小节选择Batch Size为1024(因为较小的Batch Size效果不明显),同时,需要具备如下条件:

  • Data Loader Workers数为8

  • 开启Pin Memory

  • 开启AMP

首先展示模型不编译的效果,GPU利用率为87.22%,平均每个Step耗时为422.462ms,平均每秒处理样本数为:1024 / (422.462 / 1000) = 2423.887。

image

模型编译后,GPU利用率为84.07%,平均每个Step耗时为375.715ms,平均每秒处理样本数为:1024 / (375.715 / 1000) = 2725.470。

image

总结

以上结果表明,通过编译优化,不仅提高了处理速度,还增强了整体效率。

优化方向5:精简一些数据传输与同步的操作

在PyTorch中存在一些CPU和GPU之间同步的操作。如果不注意,这些同步操作可能会极大的降低训练任务的速度。

本小节仍然基于Baseline做修改,配置如下:

  • Batch Size设置为512

  • Data Loader Worker数设置为8

完整代码如下。

import torch
import torch.nn
import torch.optim
import torch.profiler
import torch.utils.data
import torchvision.datasets
import torchvision.models
import torchvision.transforms as T

transform = T.Compose(
    [T.Resize(224),
     T.ToTensor(),
     T.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
train_set = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)


train_loader = torch.utils.data.DataLoader(
	train_set,
  batch_size=512, # batch size 设置为512
  num_workers=8, # worker数设置为8
  shuffle=True,
)

device = torch.device("cuda:0")
model = torchvision.models.resnet18(weights='IMAGENET1K_V1').cuda(device)
criterion = torch.nn.CrossEntropyLoss().cuda(device) 
optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)
model.train()


# train step
def train(data):
    inputs, labels = data[0].to(device=device), data[1].to(device=device)
    outputs = model(inputs)
    loss = criterion(outputs, labels)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

# training loop wrapped with profiler object
with torch.profiler.profile(
        schedule=torch.profiler.schedule(wait=1, warmup=4, active=3, repeat=1),
        on_trace_ready=torch.profiler.tensorboard_trace_handler('./resnet18'),
        record_shapes=True,
        profile_memory=True,
        with_stack=True
) as prof:
    for step, batch_data in enumerate(train_loader):
        if step >= (1 + 4 + 3) * 1:
            break
        train(batch_data)
        prof.step()  # Need to call this at the end of each step

运行代码,将结果导入TensorBoard中,可以看到GPU利用率84.64%,平均每个Step消耗时间为488.871ms,平均每秒处理的样本数为512 / (488.871 / 1000) = 1047.31。

image

然后修改train函数,用一个全局变量保存每次batch计算的loss值。

# 全局变量,保存每次计算的loss值
total_losses = []

# train step
def train(data):
    inputs, labels = data[0].to(device=device), data[1].to(device=device)
    outputs = model(inputs)
    loss = criterion(outputs, labels)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    # 将当前loss值添加到total_losses中
    total_losses.append(loss.item())

重新运行代码,结果如下所示。在调用loss.item()后,GPU利用率降为77.02%,平均每个Step消耗时间为538.954ms,平均每秒处理的样本数为512 / (538.954 / 1000) = 949.99。

image

仅添加了一行代码,调用了loss.item(),就导致性能出现极大的损失。进入Trace视图发现:tensor.item()操作(382.469ms)占据了整个Step(530.101ms)的大部分时间。

image

同时,放大Trace视图,可以看到tensor.item()会调用cudaStreamSynchronize(),这是一个同步CPU和GPU的操作,执行到这个函数时,进程会被阻塞,直到CUDA Stream中的当前时间点之前的操作执行完成后,cudaStreamSynchronize()才会返回,进程才可以执行其他代码。

image

总结

通过上述分析,在生产环境中应谨慎使用 tensor.item(),如果确实需要使用,则必须评估其对性能的潜在影响。此外,还有几个与 tensor.item() 行为相似的PyTorch API也会导致CPU与GPU之间的同步操作,从而可能引起性能下降。这些API包括torch.nonzerotorch.unique。在使用这些函数时,同样需要仔细考量它们是否会对系统的整体性能造成不利影响。

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

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

相关文章

关于IDE的相关知识之三【插件安装、配置及推荐的意义】

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///C爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于ide插件安装、配置及推荐意义的相关内容…

ECharts柱状图-交错正负轴标签,附视频讲解与代码下载

引言&#xff1a; 在数据可视化的世界里&#xff0c;ECharts凭借其丰富的图表类型和强大的配置能力&#xff0c;成为了众多开发者的首选。今天&#xff0c;我将带大家一起实现一个柱状图图表&#xff0c;通过该图表我们可以直观地展示和分析数据。此外&#xff0c;我还将提供…

预处理详解(完结篇)

⽬录 一. 什么是预处理 c 1 预定义符号 2. #define定义常量 3 . #define定义宏 4. 带有副作⽤的宏参数 5. 宏替换的规则 6 宏函数的对⽐ 三 #和## 四 命名约定 五. #undef 一 什么是预处理 有许多文件中都内容我们是看不懂的&#xff0c;那怎么才能令…

【k8s】监控metrics-server

metrics-server介绍 Metrics Server是一个集群范围的资源使用情况的数据聚合器。作为一个应用部署在集群中。Metric server从每个节点上KubeletAPI收集指标&#xff0c;通过Kubernetes聚合器注册在Master APIServer中。为集群提供Node、Pods资源利用率指标。 就像Linux 系统一样…

ERP 入库生产第一个版本完成

剩下的逻辑都是基于入库表达操作,资源划分,在销售,出库 windows 下直接部署 mysql 数据库,更轻量一些

ESP32-S3模组上跑通ES8388(12)

接前一篇文章&#xff1a;ESP32-S3模组上跑通ES8388&#xff08;11&#xff09; 二、利用ESP-ADF操作ES8388 2. 详细解析 上一回解析了es8388_init函数中的第5段代码&#xff0c;本回继续往下解析。为了便于理解和回顾&#xff0c;再次贴出es8388_init函数源码&#xff0c;在…

对于相对速度的重新理解 - 15

换一个视角看待能量可能一切都更为清晰&#xff0c;从&#xff0c; 可以意识到&#xff0c;最终质量 指的是 的数量。这个数量就是 我们可以去除电性振动和磁性振动的影响&#xff0c;把这两种振动的影响归结在 里面&#xff0c;这就像是&#xff0c;有一百万个某种物体&#x…

记一次腾讯云海外服务器http能正常访问https访问拒绝问题处理过程

最近双十一, 购了一台腾讯云的海外服务器&#xff0c; 开通后就是一堆的服务器软件安装数据上传和配置&#xff0c;没想到&#xff0c;等待配置完成后才发现https无法正常访问&#xff0c;于是开启了自查。 1. 检查nginx软件的ssl配置 nginx http https配置参考 server {l…

Java 单元测试模拟框架-Mockito 的介绍

Mockito 是什么 Mockito 是一个用于单元测试的模拟框架&#xff0c;基于它可以使用简洁易用的API编写出色的测试。 Mockito 允许开发人员创建和管理模拟对象&#xff08;mock objects&#xff09;&#xff0c;以便在测试过程中替换那些不容易构造或获取的对象。 Mockito的基本…

分享一款 Vue 图片编辑插件 (推荐)

&#x1f4a5;本篇文章给大家分享一款强大到没朋友的Vue图片编辑插件&#xff0c;可以对图片进行旋转、缩放、裁剪、涂鸦、标注、添加文本等&#xff0c;快来试试并收藏吧&#xff01;&#x1f495; 这是一款对图片进行旋转、缩放、裁剪、涂鸦、标注、添加文本在线处理的图片处…

在基于控制器的 API 和最小 API 之间进行选择

文章目录 ASP.NET Core 支持两种创建 API 的方法&#xff1a;基于控制器的方法和最小 API。 API 项目中的控制器是派生自 ControllerBase 的类。 最小 API 在 Lambda 或方法中使用逻辑处理程序定义终结点。 本文解释了这两种方法之间的差异。 最小 API 的设计默认隐藏了主机类…

【UVM】phase机制

Phase的种类 funcion phase 不消耗仿真时间 八种&#xff08;图中白色背景&#xff09; task phase 消耗仿真时间 一种&#xff08;图中灰色背景&#xff09;&#xff0c;run_phase又可以细分为十二种 Phase的功能 bulid_phase&#xff1a;uvm_component类的实例化&…

《装甲车内气体检测“神器”:上海松柏 K-5S 电化学传感器模组详解》

《装甲车内气体检测“神器”:上海松柏 K-5S 电化学传感器模组详解》 一、引言二、K-5S 电化学传感器模组概述&#xff08;一&#xff09;产品简介&#xff08;二&#xff09;产品特点&#xff08;三&#xff09;产品适用场景 三、电化学传感器原理及优点&#xff08;一&#xf…

【Linux课程学习】:文件第二弹---理解一切皆文件,缓存区

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;Linux课程学习 &#x1f337;追光的人&#xff0c;终会万丈光芒 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 Linux学习笔记&#xff1a; https://blog.csdn.net/d…

汽车控制软件下载移动管家手机控车一键启动app

移动管家手机控制汽车系统是一款实现车辆远程智能控制的应用程序‌。通过下载并安装特定的APP&#xff0c;用户可以轻松实现以下功能&#xff1a;‌远程启动与熄火‌&#xff1a;无论身处何地&#xff0c;只要有网络&#xff0c;即可远程启动或熄火车辆&#xff0c;提前预冷或预…

匿名发帖/匿名论坛功能设计与实现(编辑发帖部分)

前言 还是之前的音乐系统&#xff0c;首页一直是没想好写些什么&#xff0c;想写一个基于数据分析筛选的歌曲推荐功能&#xff0c;但是目前技术选型没太有考究等以后再实现吧&#xff0c;昨天突然想到可以把首页设计成前40%页面是歌曲推荐后面接下来就是一段匿名论坛功能&…

微信小程序 城市点击后跳转 并首页显示被点击城市

在微信小程序中&#xff0c;渲染出城市列表后&#xff0c;如何点击城市&#xff0c;就跳转回到首页&#xff0c;并在首页显示所点击的城市呢&#xff1f; 目录 一、定义点击城市的事件 二、首页的处理 首页&#xff1a;点击成都市会跳转到城市列表 城市列表&#xff1a;点击…

DOM,事件监听和VUE入门

四个事件、 JS模块化 VUE入门 Ajax VUE总结

Python学习笔记(5)Python的创建型设计模式

创建型设计模式&#xff08;Creational Design Patterns&#xff09;&#xff0c;主要关注对象的创建机制。这类模式可以使得系统更加独立于如何创建、组合和表示其对象。通过将这些职责分离出来&#xff0c;创建型设计模式有助于提高代码的灵活性和复用性。 本书的范例代码已经…

云备份实战项目

文章目录 前言一、整体项目简介二、服务端环境及功能简介三、 客户端环境及功能简介四、服务端文件管理类的实现1. 获取文件大小&#xff0c;最后一次修改时间&#xff0c;最后一次访问时间&#xff0c;文件名称&#xff0c;以及文件内容的读写等功能2. 判断文件是否存在&#…