nvidia常用的一些工具

news2024/11/19 1:53:38

       Nvidia作为GPU王者,无论是生产学习游戏都占据半壁江山,尤其是AI时代的爆发,让Nvidia的生意更是如日中天。下面对Nvidia开放的一些免费工具进行总结,学会怎么使用会让我们更好的发挥Nvidia系列产品的性能。

(1)apex

APEX是英伟达开源的,完美支持PyTorch框架,用于改变数据格式来减小模型显存占用的工具。其中最有价值的是amp(Automatic Mixed Precision),将模型的大部分操作都用Float16数据类型测试,一些特别操作仍然使用Float32。并且用户仅仅通过三行代码即可完美将自己的训练代码迁移到该模型。实验证明,使用Float16作为大部分操作的数据类型,并没有降低参数,在一些实验中,反而由于可以增大Batch size,带来精度上的提升,以及训练速度上的提升

》下载apex库

​ 网址 GitHub - NVIDIA/apex: A PyTorch Extension: Tools for easy mixed precision and distributed training in Pytorch,下载到本地文件夹。解压后进入到apex的目录安装依赖。在执行命令;

cd apex-master #进入apex目录 
pip install -r requirements.txt

》安装apex库

依赖安装完后,打开cmd,cd进入到刚刚下载完的apex-master路径下,运行:

python setup.py install

》使用方式

在混合精度训练上,Apex 的封装十分优雅。直接使用 amp.initialize 包装模型和优化器,apex 就会自动帮助我们管理模型参数和优化器的精度了,根据精度需求不同可以传入其他配置参数。

from apex import amp 
model, optimizer = amp.initialize(model, optimizer, opt_level='O1')

其中 opt_level 为精度的优化设置,O0(第一个字母是大写字母O):

O0:纯FP32训练,可以作为accuracy的baseline;

O1:混合精度训练(推荐使用),根据黑白名单自动决定使用FP16(GEMM, 卷积)还是FP32(Softmax)进行计算。

O2:“几乎FP16”混合精度训练,不存在黑白名单,除了Batch norm,几乎都是用FP16计算。

O3:纯FP16训练,很不稳定,但是可以作为speed的baselin

》并行训练

Apex也实现了并行训练模型的转换方式,改动并不大,主要是优化了NCCL的通信,因此代码和 torch.distributed 保持一致,换一下调用的API即可:

from apex import amp
from apex.parallel import DistributedDataParallel
model, optimizer = amp.initialize(model, optimizer, opt_level='O1')
model = DistributedDataParallel(model, delay_allreduce=True)

####反向传播时需要调用 amp.scale_loss,用于根据loss值自动对精度进行缩放
with amp.scale_loss(loss, optimizer) as scaled_loss:
    scaled_loss.backward()

》同步BN

Apex为我们实现了同步BN,用于解决单GPU的minibatch太小导致BN在训练时不收敛的问题。

from apex.parallel import convert_syncbn_model
from apex.parallel import DistributedDataParallel
### 注意顺序:三个顺序不能错
model = convert_syncbn_model(UNet3d(n_channels=1, n_classes=1)).to(device)
model, optimizer = amp.initialize(model, optimizer, opt_level='O1')
model = DistributedDataParallel(model, delay_allreduce=True)

(2)runx

Runx是NVIDIA开源的一款深度学习实验管理工具,开源于2020年三月,是个很新的开源库。其功能是帮助深度学习研究者自动执行一些常见任务,如超参数扫描、输出日志记录、运行唯一目录创建、实验总结等操作,这是考虑到深度学习一个实验就要几个小时、几天甚至几个月,如果没有及时进行保存和记录会是一件很低效的事情。

》安装

  • pip快速安装
pip install runx
  • 源码安装
git clone https://github.com/NVIDIA/runx 
cd runx 
python setup.py install --user

》模块介绍

Runx主要有三个模块,分别是runx、logx和sumx。这些模块设计之初,期待被一起使用,但如果想只使用runx也是可以的,注意的是,sumx必须配合logx进行使用。

  • runx

使用简单的YAML文件配置实验参数,runx会扫描这个文件并逐个执行。runx主要是实验运行的配置扫描,并逐次执行,例如有个train的Python脚本,需要学习率和学习器两个参数,只需要配置如下一个名为sweep.yml的YAML文件即可。

cmd: 'python train.py'
hparams:
lr: [0.01, 0.02]
solver: ['sgd', 'adam']

在命令行通过python -m runx.runx sweep.yml,之后会逐次自动执行下面的命令。它的执行参数是互相组合得到的。该模块对作业系统支持较好,当使用集群时,可以方便的进行任务提交。

python train.py --lr 0.01 --solver sgd
python train.py --lr 0.01 --solver adam
python train.py --lr 0.02 --solver sgd
python train.py --lr 0.02 --solver adam
  • logx

保存metrics、messages、checkpint以及tensorboard记录文件。该模块是后续sumx模块必要的步骤,将metric传给logx才能进行用sumx进行实验分析。同时,logx还支持标准输出的本地化、方便地tensorboard记录已经快速的模型保存。

使用该模块首先需要进行导入,得到logx对象后需要先进行初始化,初始化包含下面的参数。

logx.initialize(
    logdir=None,   # 日志存储目录,不存在会自动创建
    coolname=False,  # 是否在logdir下生成一个独有的目录
    hparams=None,  # 配合runx使用,超参数都存下来
    tensorboard=False,  # 是否写入tensorboard文件,无需手动writer
    no_timestamp=False,  # 是否不启用时间戳命名
    global_rank=0,  # 分布式训练防止多输出
    eager_flush=True  # 打开后即使tensorboard写入过快也不会丢失
)

初始化之后就可以正常使用logx的几个主要功能了。

logx.msg("content")

将所有的print语句换成上面的函数即可,这样所有的标准输出都会保存在logdir目录下的一个logging.log文件中,方便后续查看。

logx.metric(phase='val', metrics=metrics, epoch=epoch)

对传入的字典metrics中的数据进行保存记录,若开启了tensorboard则自动add_scalar。phase表示不同的阶段,可选train和val,epoch表示全局轮次。

logx.save_model(
    save_dict,   # 保存的checkpoint字典, 里面可以有epoch,state_dict等
    metric,   # 评估指标,logx会维护一系列的该指标值,若变好则保存新模型
    epoch,   # 当前轮次
    higher_better=True,   # 是否更高更好,如准确率
    delete_old=True  # 是否删除旧模型
)
  • sumx

对训练结果进行总结。用于归总多次试验运行的结果,依赖于logx产生的metric文件,避免读取tensorboard文件增加分析速度。

如执行python -m runx.sumx sweep可得如下分析结果。

          lr solver acc epoch epoch_time

run4 0.02 adam 99.1 10 5:21

run3 0.02 sgd 99.0 10 5:02

run1 0.01 sgd 98.2 10 5:40

run2 0.01 adam 98.1 10 5:25

下面的代码演示训练模型拟合一元二次函数的例子。

import torch
import numpy as np
import torch.nn as nn
import torch.optim as optim
from runx.logx import logx

# dataset
x = torch.arange(16*10).view(16, 10).float()
x = x + torch.rand_like(x) / 10
y = x ** 2 + 1


# model
class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        self.fc1 = nn.Linear(1, 10)
        self.fc2 = nn.Linear(10, 1)

    def forward(self, x):
        x = self.fc1(x)
        x = nn.functional.relu(x)
        x = self.fc2(x)
        return x

# log
logx.initialize("./logs/", coolname=True, tensorboard=True)


model = Model()
model.cuda()

criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.001)

best_loss = np.inf

for epocn in range(10):
    losses = 0.0  # epochs training loss
    losses_valid = 0.0  # epoch validation loss
    for step, (x_, y_) in enumerate(zip(x, y)):
        # data
        x_, y_ = x_.cuda().view(10, 1), y_.cuda().view(10, 1)
        x_train, y_train = x_[:5], y_[:5]
        x_val, y_val = x_[5:], y_[5:]
        # forward
        out = model(x_train)
        # loss
        loss_ = criterion(out, y_train)
        losses += loss_
        # backward
        optimizer.zero_grad()
        loss_.backward()
        optimizer.step()
        
        # valid
        with torch.no_grad():
            pred = model(x_val)
            valid_loss = criterion(pred, y_val)
            losses_valid += valid_loss

    metrics = {
       'loss': losses / (step + 1),
    }
    metrics_valid = {
        'loss': losses_valid / (step+1)
    }
    logx.metric('train', metrics, epocn)
    logx.metric('val', metrics_valid, epocn)

    if losses_valid < best_loss:
        best_loss = losses_valid

    save_dict = {
        'state_dict': model.state_dict()
    }
    logx.save_model(save_dict, best_loss, epocn, higher_better=False, delete_old=True)

    logx.msg("epoch {}, train loss {:.6f}, valid loss {:.6f}".format(epocn, losses / (step+1), losses_valid / (step+1)))

这段训练代码执行后会生成如下结果,包含模型checkpoint,tensorboard文件,标准输出日志(logging.log)以及metric记录。打开TensorBoard服务,可视化训练过程,结果如下,可以看到,logx已经自动记录了tensorboard文件。

(3)NVDashboard

NVDashboard 是一个开源包,用于在交互式 Jupyter Lab 环境中实时可视化 NVIDIA GPU 指标。 NVDashboard 是所有 GPU 用户监控系统资源的好方法。然而,它对于 NVIDIA 的 GPU 加速数据科学软件库开源套件 RAPIDS 的用户来说尤其有价值。鉴于现代数据科学算法的计算强度,在许多情况下 GPU 可以提供改变游戏规则的工作流加速。为了达到最佳性能,底层软件有效利用系统资源是绝对关键的。尽管加速库(如 cuDNN 和 RAPIDS)专门设计用于在性能优化方面进行繁重的工作,但对于开发人员和最终用户来说,验证他们的软件是否确实按预期利用了 GPU 资源可能非常有用。虽然这可以通过 nvidia-smi 等命令行工具来完成,但许多专业数据科学家更喜欢使用交互式 Jupyter 笔记本进行日常模型和工作流开发。

NVDashboard 使 Jupyter notebook用户能够在他们用于开发的同一交互式环境中可视化系统硬件指标。支持的指标包括:

  • GPU 计算利用率
  • GPU 内存消耗
  • PCIe 吞吐量
  • NVLink 吞吐量

该软件包基于基于 Python 的仪表板服务器构建,支持 Bokeh 可视化库实时显示和更新图形。一个额外的 Jupyter-Lab 扩展将这些仪表板作为可移动窗口嵌入到交互式环境中。大多数 GPU 指标都是通过 PyNVML 收集的,PyNVML 是一个开源 Python 包,由 NVIDIA 管理库 (NVML) 的包装器组成。出于这个原因,可以修改/扩展可用的仪表板以显示可通过 NVML 访问的任何可查询的 GPU 指标。

nvdashboard 包在 PyPI 上可用,它由两个基本组件组成:

  • Bokeh Server:服务器组件利用出色的 Bokeh 可视化库来实时显示和更新 GPU 诊断仪表板。使用 PyNVML 访问所需的硬件指标,PyNVML 是一个开源 python 包,由 NVIDIA 管理库 (NVML) 的包装器组成。出于这个原因,可以修改/扩展 NVDashboard 以显示任何可通过 NVML 访问的可查询 GPU 指标,并且可以从 Python 轻松访问。
  • Jupyter-Lab 扩展:Jupyter-Lab 扩展将 GPU 诊断仪表板作为可移动窗口嵌入到交互式 Jupyter-Lab 环境中。
$ pip install jupyterlab-nvdashboard

# If you are using Jupyter Lab 2 you will also need to run
$ jupyter labextension install jupyterlab-nvdashboard

(4)registry机制

简单来说registry可以提供字符串到python中类的映射,registry能让开发者输入相应的类名和参数,就能获得一个初始化好的类。常见的具体使用步骤如下:

  • 创建注册表

首先创建一个目录,随意取个名字code,在code下再创建一个目录,名为registry,在这个目录下创建一个名为builder的python文件,代码如下:

from mmcv import Registry
from mmcv.utils.registry import build_from_cfg
CLASS=Registry("class_test")

在这里我们创建了一个名为class_test的注册表,其中Registry的参数如下

name (str): 注册表的名字

build_func(func, optional):构造类接口的函数,简单来说就是用来后续处理获取初始化类的函数,

mmcv提供了一个build_from_cfg的函数,只需要建立一个字典,然后type=需要调用的类名,然后正常的参数的键值对输入就可以得到一个用自己配置参数获取的初始化的类了。当然也可以自己写。

parent (Registry, optional): 父注册表,如果没有指定build_func,就调用父注册表里面的,当然父注册表里面的接口也是可以继承的

  • 注册表添加类

然后在registry目录中创建一个名为class_test的python文件,代码如下:

from .builder import CLASS
@CLASS.register_module()
class test(object):
    def init(self,n):
        self.n=n
        def printf(self):
        print(self.n)

我们在这里定义了一个类,@CLASS.register_module()的作用是将我们这个类放入CLASS注册表里面,只要在@CLASS.register_module()下写入一个类就可以将这个类放入我们创建的注册表里面了。

然后在registry中创建一个__init__.py文件,将我们的registry变成一个python包,这是必要的。代码如下:

from .builder import CLASS
from .class_test import test
all=["CLASS","test"]
  • 测试

这样我们的注册表就创建完成了,赶紧实验以下,在code目录下创建一个test python文件,代码如下:

from Registry import CLASS 
cfg = {"type":'test',"n":10} 
test=CLASS.build(cfg) 
test.printf()

在这里我们通过build方法创建好了一个初始化好了的test类,然后调用printf方法打印。

(5)Nsight

Nsight是NVIDIA面相开发者提供的开发工具套件,能提供深入的跟踪、调试、评测和分析,以优化跨 NVIDIA GPU和CPU的复杂计算应用程序。Nsight主要包含Nsight System、Nsight Compute、Nsight Graphics三部分。nsight文档

  • Nsight System

所有与NVIDIA GPU相关的程序开发都可以从Nsight System开始以确定最大的优化机会。Nsight System给开发者一个系统级别的应用程序性能的可视化分析。开发人员可以优化瓶颈,以便在任意数量或大小的CPU和GPU之间实现高效扩展。详情可访问NVIDIA官网。

  • Nsight Compute

Nsight Compute是一个CUDA应用程序的交互式kernel分析器。它通过用户接口和命令行工具的形式提供了详细的性能分析度量和API调试。Nsight Compute还提供了定制化的和数据驱动的用户接口和度量集合,可以使用分析脚本对这些界面和度量集合进行扩展,以获得后处理的结果。详情可访问NVIDIA官网。

  • Nsight Graphics

Nsight Graphics是一个用于调试、评测和分析Microsoft Windows和Linux上的图形应用程序。它允许您优化基于Direct3D 11, Direct3D 12, DirectX,Raytracing 1.1, OpenGL,Vulkan和KHR Vulkan Ray Tracing Extension的应程序的性能。详情可访问NVIDIA官网。

以手写数字数据库MNIST作为训练数据集,使用PyTorch框架进行神经网络训练。通过Nsight System对训练过程进行性能分析,进而找到性能瓶颈,指导优化训练过程。

1、下载训练所需的数据集和脚本 数据集采用MNIST,训练脚本我们采用该位置的PyTorch代码,基于单块NVIDIA Volta GPU我们将完成多batches和epochs的训练。一次完整的迭代过程包括如下几个方面:

  • 从磁盘载入数据
  • 数据加载到GPU设备端
  • 前向传播
  • 反向传播

运行以下命令,可完成一次训练,本次实践以bcc.gn3.c10m80.1v100-32g实例为例,约花费90s左右。

2、利用Nsight system进行性能分析

(可选)开发者可通过添加NVIDIA Tools Extension(NVTX);来对应用程序逻辑上的时间线进行注释,帮助分析人员理解应用算法的上下文逻辑,在main.py中的训练代码部分增加nvtx函数如下:  通过Nsight System的命令行方式,生成分析结果文件:

nsys profile –t cuda,osrt,nvtx –o baseline –w true python main.py

在这个例子中,采用的参数解释如下:

  • -t 后面跟定的参数是我们要追踪的API,即需要CUDA API,OS runtime API以及NVTX API
  • -o 给定的是输出的文件名称
  • -w 后面表明是或否要在命令行中同时输出结果
  • python main.py为程序的执行命令

将导出的baseline输出文件下载到本地,并拖拽到本地的Nsight System窗口即可获取性能结果展示,示例如下:

 可以看到,通过产生的分析文件,我们发现训练的过程中,有很大一段时间GPU都处于空闲状态。

3、性能优化 可以发现,GPU空闲状态主要是因为CPU在处理数据加载耗时过多。我们可以对收据加载进行优化,优化的方式如下: 原有的数据加载采用的是一个CPU worker 线程:

kwargs = {'num_workers': 1, 'pin_memory': True} if use_cuda else {}

我们可以将num_workers的数量调大,这里我们调整为8

kwargs = {'num_workers': 8, 'pin_memory': True} if use_cuda else {}

重新按照步骤2执行,可观察到如下结果: 

可以发现,经过优化之后,数据加载的时间明显缩短了。整体的训练时间也从原来的90s变成了21s,获得了显著的加速效果。

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

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

相关文章

充电桩平台的优惠券功能如何设计

在设计充电桩平台的优惠券功能时&#xff0c;应考虑以下细节&#xff1a; 优惠券的发放机制 自动发放与手动领取&#xff1a;用户可以通过完成特定任务&#xff08;如注册、推荐新用户等&#xff09;自动获得优惠券&#xff0c;或者在平台上手动领取。时间限制&#xff1a;设置…

Reflection反射

概述 Java反射机制是java语言的一个重要特性&#xff0c;首先我们要了解两个概念&#xff1a;编译期和运行期。 编译期 编译期是指把源代码交给编译器编译成计算机可以执行的文件的过程。在Java中&#xff0c;也就是把Java代码编译成class文件的过程&#xff0c;编译器只…

使用servlet将图片保存到数据库中

一、导入jar包&#xff0c;配置properties文件&#xff08;src路径下&#xff09; commons-fileupload-1.3.1.jar commons-io-2.2.jar druid-1.2.8.jar mysql-connector-java-8.0.25.jar servlet-api.jar # ????key-value??? driverClassNamecom.mysql.cj.jdbc.Driv…

优购电商小程序的设计与实现+ssm(lw+演示+源码+运行)

优购电商小程序 摘 要 随着社会的发展&#xff0c;社会的方方面面都在利用信息化时代的优势。互联网的优势和普及使得各种系统的开发成为必需。 本文以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法&#xff0c;它主要是采用java语言技术和mysql数据库来完成对…

精选推荐!分享6款论文写作ai免费带附加文献

在当今学术研究和写作领域&#xff0c;AI论文写作工具已经成为不可或缺的助手。这些工具不仅能够提高写作效率&#xff0c;还能帮助研究人员和学生产出高质量的论文。以下是六款免费且功能强大的AI论文写作工具推荐&#xff1a; 一、千笔-AIPassPaper 千笔-AIPassPaper是一款…

【试听开放中】新中地2406期GIS特训营同步,定制专属学习路线图+职业规划

很多地信相关专业的同学都知道 学习开发很重要&#xff1f;项目实战经验很重要 但是在面对学什么语言&#xff1f;掌握什么技术的时候 同学又就开始犯嘀咕&#xff0c;这么多&#xff0c;到底学什么&#xff1f; 今天听人说Java不错&#xff0c;想学 明天听人说Python前景…

掌握“问一问”策略,视频号流量轻松实现质的飞跃!

掌握“问一问”策略&#xff0c;视频号流量轻松实现质的飞跃&#xff01; 视频号新流量入口&#xff0c;微信问一问。如何玩转问一问功能&#xff0c;手把手操作教学。#视频号#微信#问一问#短视频#直播 市面上还有这么牛逼的一个流量隐藏入口&#xff0c;先看一下数据&#x…

算法工程师重生之第六天(四数相加II 赎金信 三数之和 四数之和 总结 )

参考文献 代码随想录 一、四数相加 II 给你四个整数数组 nums1、nums2、nums3 和 nums4 &#xff0c;数组长度都是 n &#xff0c;请你计算有多少个元组 (i, j, k, l) 能满足&#xff1a; 0 < i, j, k, l < nnums1[i] nums2[j] nums3[k] nums4[l] 0 示例 1&#x…

《Rocky Linux 的下载和安装详细步骤》

以下是 Rocky Linux 的下载和安装详细步骤&#xff1a; 下载&#xff1a; 1. 打开 Rocky Linux 的官方网站&#xff1a;https://rockylinux.org/ 2. 在首页找到“Download”&#xff08;下载&#xff09;选项&#xff0c;点击进入下载页面。 3. 在下载页面中&#xff0c;您…

【鸿蒙 HarmonyOS NEXT】使用屏幕属性display:获取屏幕宽高

✨本人自己开发的开源项目&#xff1a;土拨鼠充电系统 ✨踩坑不易&#xff0c;还希望各位大佬支持一下&#xff0c;在GitHub给我点个 Start ⭐⭐&#x1f44d;&#x1f44d; ✍GitHub开源项目地址&#x1f449;&#xff1a;https://github.com/cheinlu/groundhog-charging-syst…

three.js工厂案例

最终效果图 给定html页面&#xff0c;作为tag标签展示 <body style"background-color: black;"><div id"tag"><div style"position:relative;width:400px;height:322px;color: #fff;"><img src"background.png"…

Vue3+TS项目封装一个公共的el-table组件二次封装

前言 支持动态传入列&#xff0c;列内容可以指定插槽&#xff0c;指定格式化显示 样式没太写&#xff0c;主要分享基础功能封装 效果 Table组件代码BaseTable.vue <template><el-table :data"data" border><template v-for"col in columns&q…

基于SpringBoot+Vue+MySQL的画师约稿平台系统

系统展示 用户界面 画师界面 管理员界面 系统背景 基于SpringBootVueMySQL的画师约稿平台系统的背景&#xff0c;主要源于数字艺术行业的快速发展与画师、客户双方需求的日益增长。在传统的约稿方式中&#xff0c;往往存在沟通效率低下、交易过程不透明等问题&#xff0c;这限制…

Pandas读取某列、某行数据——loc、iloc区别

loc&#xff1a;通过行、列的名称或标签来索引 iloc&#xff1a;通过行、列的索引位置来寻找数据 首先&#xff0c;我们先创建一个DataFrame生成数据 import pandas as pddata {a:[1,2,3,4,5],b:[6,7,8,9,10],c:[11,12,13,14,15] } data pd.DataFrame(data) print(data) 运行…

IO模型---BIO、NIO、IO多路复用、AIO详解

本篇将想给详细解释一下什么是BIO、NIO、IO多路复用以及AIO~ 同步的阻塞(BIO)和非阻塞(NIO)的区别 BIO&#xff1a;线程发来IO请求后&#xff0c;一直阻塞着IO线程&#xff0c;需要缓冲区这边数据准备好之后&#xff0c;才会进行下一步的操作。 举个&#x1f330;&#xff1…

2024秋季云曦开学考

web ezezssrf 打开环境&#xff0c;代码审计 看起来有点多&#xff0c;要绕过五层 第一层&#xff1a;存在弱比较&#xff0c;使用数组或0e绕过 yunxi[]1&wlgf[]2 yunxis878926199a&wlgfs155964671a 第二层&#xff1a;存在强比较&#xff0c;此处使用string限制…

c++(继承、模板进阶)

一、模板进阶 1、非类型模板参数 模板参数分类类型形参与非类型形参。 类型形参即&#xff1a;出现在模板参数列表中&#xff0c;跟在class或者typename之类的参数类型名称。 非类型形参&#xff0c;就是用一个常量作为类(函数)模板的一个参数&#xff0c;在类(函数)模板中…

装饰器模式decorator

学习笔记&#xff0c;原文链接 https://refactoringguru.cn/design-patterns/decorator 将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为 调用过程 当你调用 encoded.writeData(salaryRecords); 时&#xff0c;控制流首先进入 CompressionDecorator 的 writeData …

微信小程序原生支持TS、LESS、SASS能力探究

文章目录 原生支持开始使用旧项目新建项目TS声明文件更新 功能说明less 使用全局变量sass 使用全局变量 可以参考原文 在之前开发小程序中&#xff0c;无法使用 less/sass 等 css 预编译语言&#xff0c;也无法使用 TS 进行开发&#xff0c;但在最新的编辑器版本中&#xff0c…

面向对象程序设计之模板进阶(C++)

在之前我出过一篇博客介绍了模版的初阶:面向对象程序设计(C)模版初阶&#xff0c;接下来我们将进行模版的进阶学习&#xff0c;介绍关于更多模版的知识 1.非类型模版参数 模板参数分类类型形参与非类型形参 类型形参即&#xff1a;出现在模板参数列表中&#xff0c;跟在class或…