HCCL集合通信算法开发Hello World示例(超详细)

news2024/11/26 3:39:59

本文给读者介绍了HCCL算法开发所涉及的概念和流程,并通过一个样例将前文介绍的内容串联起来。本文定位为HCCL算法开发的入门介绍,读者读完后,可结合HCCL开放代码仓中的算法样例,做深入研究。

1 什么是集合通信

集合通信定义了一系列标准信息交换接口,解决并行计算时不同进程之间的通信问题。集合通信的概念和作用可参考文章《HCCL——昇腾高性能集合通信库》。

举例来说,AllGather将group内所有节点的输入按照Rank Id重新排序,然后拼接起来,再将结果发送到所有节点的输出。

HCCL为集合通信算子提供了对应的API供开发者调用,例如AllGather算子的API如下:

HcclResult HcclAllGather(void *sendBuf, void *recvBuf, uint64_t sendCount, HcclDataType dataType, HcclComm comm, aclrtStream stream);

  • sendBuf表示输入内存缓冲区指针
  • recvBuf表示输出内存缓冲区指针
  • sendCount表示输入的数据个数
  • dataType表示处理的数据类型
  • comm表示算子执行所在的通信域
  • stream表示算子执行所在的主流

开发者可以在PyTorch等AI框架中调用HcclAllGather接口,使能昇腾AI处理器(下文简称NPU)执行AllGather通信。

2 集合通信算法

针对同一个通信算子,随着网络拓扑、通信数据量、硬件资源等变化,往往会采用不同的通信算法,从而最大化集群通信性能。以AllGather算子举例,HCCL实现了Mesh、Ring、Recursive Halving-Doubling(RHD)、NHR(Nonuniform Hierarchical Ring)、NB(Nonuniform Bruck)等多种算法用于Server内和Server间的集合通信,通信操作执行时,HCCL会根据输入条件从算法仓中自动选择性能最佳的算法。

3 概念介绍

通信域

集合通信发生在一组通信对象(比如一个NPU就是一个通信对象)。通信域是集合通信算子执行的上下文,管理对应的通信对象和通信所需的资源。

Rank

通信域中的每个通信对象称为一个Rank。

Memory

集合通信执行所需要的各种Buffer资源。

  • Input Buffer:集合通信算子输入数据缓冲区。
  • Output Buffer:集合通信算子输出数据缓冲区,存放算法计算结果。
  • CCL Buffer:一组地址固定的Buffer,可被远端访问。单算子模式下,通信对象通过CCL Buffer来实现跨Rank的数据交换。
  • CCL buffer和通信域绑定,通信域初始化的时候创建两块CCL buffer,分别称为CCL_In和CCL_Out。CCL_In和CCL_Out默认大小是200M Byte,可以通过环境变量HCCL_BUFFSIZE进行修改。同一个通信域内执行的集合通信算子都复用相同的CCL Buffer。
  • Scratch Buffer:除了CCL Buffer,有些算法计算过程中需要额外的存储空间,这部分额外的存储空间,称为Scratch Buffer。

流(Stream)是NPU上的一种硬件资源,承载了待执行的Task序列。Task可以是一个DMA操作、一个同步操作或者一个NPU算子等。同一个流上的Task序列按顺序执行,不同流上的Task序列可并发执行。

  • 主流:由Pytorch等训练框架调用集合通信API传入的stream对象称为主流。
  • 从流:为了实现集合通信算法所需要的并行性而额外申请的stream对象称为从流。

上图展示了一条主流和一条从流,主从流之间通过Post/Wait这一组Task进行同步。主从流之间没有依赖关系时,Task可并行执行。如上图主流的TaskA、TaskB和从流的TaskX、TaskY可以是并行执行的。Post/Wait的具体含义会在后文给出。

Notify

Notify是NPU上的硬件资源,用来做同步。在集合通信中主要有两种作用:1)Rank内主从流之间的同步;2)跨Rank数据收发的同步。

和Notify有关的Task有两种:1)Post;2)Wait。

Post操作给对应的notify寄存器置1,并返回。如果对应的notify值已经是1,则不产生变化,直接返回。

Wait操作会等待对应的notify值变为1。条件满足后,将对应的notify值复位为0,并继续执行后续的Task。

Rank内主从流同步示例

主流通知从流,实质是将notify1置位为1。从流通知主流,实质是将notify2置位为1。

跨Rank数据收发同步示例

与Rank内主从流同步类似,跨Rank的数据收发也需要同步。比如向远端Rank写数据前,得知道远端是否准备好接受数据的Buffer。关于跨Rank的同步,可参考下面关于Transport链路的章节。

Transport链路

要完成Rank间的数据通信需要先建立Transport链路,Transport链路分两种:1)SDMA链路,对应到HCCS/PCIE硬件连接;2)RDMA链路,对应到RoCE硬件连接。

  1. SDMA Transport链路的两端各有两种类型的notify,分别称为AckDataSignal
  2. RDMA Transport链路的两端各有三种类型的notify,分别称为AckDataSiganlDataAck

每条Transport链路会申请各自的notify资源,不同的Transport之间不会复用notify。SDMA链路会申请四个notify,每端各两个;RDMA链路会申请六个notfy,每端各三个。

SDMA数据收发同步

一次SDMA数据收发需要两组同步,如下图所示,分别使用了Ack和DataSignal两个notify。为了避免同一条链路上多次收发数据相互影响,同步需以Ack开始,以DataSignal结束。

RDMA数据收发同步

一次RDMA数据收发需要三组同步信号,如下图所示。这是因为RDMA操作在流上是异步执行的,所以Rank 0执行完Write和Post DataSignal之后,并不知道数据什么时候写完,因此需要Rank1 Wait DataSignal满足条件后,再给Rank 0发送一个DataAck同步信号,通知Rank0数据已经写完了。

为了避免同一条链路上多次收发数据相互影响,同步需以Ack开始,以DataAck结束。

4 算法实现样例解析

4.1 算法原理

在了解完集合通信开发涉及的基本概念后,我们以单台Atlas 800T A2 训练服务器执行AllGather算子来举例,实现一个server内的mesh算法。

假定通信域中有4个NPU,每个NPU与另外三个NPU都有独立的HCCS链路,也就是说server内是mesh链接。硬件拓扑如下:

Buffer初始状态如下图,每个Rank只有UserIn(输入Buffer)中存在有效数据。Rank之间使用CCL内存交换数据,因为本算法只使用CCL_Out,所以没有把CCL_In画出来。

第一步,将数据从UserIn搬移到CCL_Out。

第二步,每个Rank同时从本Rank的CCL_Out和其他Rank的CCL_Out读取数据,并写到自己的UserOut(输出Buffer)的对应位置。这边只画了Rank0的数据搬运方向,其他Rank的搬运方式是类似的。

4.2 算法执行流程

在开发集合通信算法之前,需要先了解通信算法的执行流程,如下图所示:

通信算法的主体执行流程可划分为4步:

  1. HCCL框架根据算子类型构造出对应的算子实例。
  2. 框架调用算子实例执行算法选择,算法选择会返回将要执行的算法名字和一个标志资源的新tag字符串。
  3. 框架根据新tag查询对应的资源(Scratch Buffer、从流、主从流同步的notify、Transport链路)是否存在,若不存在,则调用算子实例的资源计算接口获取算法执行需要的资源诉求,然后框架根据资源诉求将对应的资源创建出来。
  4. 框架传入算法执行需要的资源,并执行算法编排。算法编排执行的过程中,会通过平台层的接口提交要执行的Task。

上述主体流程的步骤2至步骤4,是执行一个算法的必经步骤。那么开发一个新的算法,也需要完成相应的步骤,即算法选择、资源计算和算法编排。

4.3 算法选择

每个算法有自己的适用范围,在适用范围内,该算法往往是性能最佳的。本样例介绍的算法在以下三个条件满足时,为优选算法。具体条件为:1)server内是mesh拓扑;2)执行模式为单算子模式;3)通信域中只有一台server。

具体代码实现如下:

// isMeshTopo为true表示server内mesh拓扑
    if (isMeshTopo) {
        // 表示当前为单算子模式
        if (workflowMode_ == HcclWorkflowMode::HCCL_WORKFLOW_MODE_OP_BASE) {
            // 表示拓扑中只有一个server
            if (isSingleMeshAggregation_) {
                // 选择条件:1)server内mesh拓扑;2)单server场景;3)单算子模式
                algName = "AllGatherMeshOpbaseExecutor";
            }
        }
    }

4.4 资源计算

接下来,我们从算法原理来分析该算法执行需要的资源,代码中以rankSize表示参与通信的实体个数,本样例中rankSize即为4。

  1. 算法执行不需要额外的Scratch Buffer,因此Scratch Buffer大小为0。
  2. 算法原理第二步中,Rank内有rankSize块数据并发搬运,一共需要rankSize条流,除去主流,还需要rankSize - 1个从流。
  3. 需要的主从流同步的notify数量为(rankSize - 1) * 2。Transport同步的notify随Transport链路自动申请,这边不用计算Transport需要的notify数量。
  4. 算法原理第二步中,每个Rank都需要和其他的Rank交互,因此需要建立mesh链路。
    资源计算的参考代码实现

// 计算从流的数量,deviceNumPerAggregation即为mesh内的Rank个数,即为RankSize
HcclResult CollAllGatherMeshOpbaseExecutor::CalcStreamNum(u32& streamNum)
{
u32 totalStreamNum = topoAttr_.deviceNumPerAggregation;
// 返回的从流数量为rankSize-1
    streamNum = totalStreamNum - 1U;
    return HCCL_SUCCESS;
}

// 计算建链诉求
HcclResult CollAllGatherMeshOpbaseExecutor::CalcCommInfo(std::vector<LevelNSubCommTransport>& opTransport)
{
    TransportMemType inputType = TransportMemType::RESERVED;
    TransportMemType outputType = TransportMemType::RESERVED;
    CHK_RET(CalcTransportMemType(inputType, outputType));
    CHK_RET(CalcLevel0CommInfo(inputType, outputType, opTransport));
    return HCCL_SUCCESS;
}

// 获取建链的内存类型
HcclResult CollAllGatherMeshOpbaseExecutor::CalcTransportMemType(TransportMemType &inputType, TransportMemType &outputType)
{
    if (GetWorkflowMode() == HcclWorkflowMode::HCCL_WORKFLOW_MODE_OP_BASE) {
        inputType = TransportMemType::CCL_INPUT;    // 单算子模式下使用CCL Buffer建链
        outputType = TransportMemType::CCL_OUTPUT;  // 单算子模式下使用CCL Buffer建链
    }
    return HCCL_SUCCESS;
}

// 调用公共函数完成server内mesh建链
HcclResult CollAllGatherMeshOpbaseExecutor::CalcLevel0CommInfo(TransportMemType inputType, TransportMemType outputType, std::vector<LevelNSubCommTransport>& opTransport)
{
    CommParaInfo commParaLevel0(COMM_LEVEL0, CommType::COMM_TAG_MESH);
    CHK_RET(CalcCommPlaneInfo(tag_, commParaLevel0, opTransport[COMM_LEVEL0], inputType, outputType));
    return HCCL_SUCCESS;
}

4.5 算法编排

以Rank0视角来举例说明算法编排。Rank0上一共有四条流,主流做Rank内的数据搬运,三条从流分别与另外三个Rank做Rank间的数据搬运。

算法原理中第一步对应了主流上的第一个蓝色Task即将数据从输入Buffer搬到CCL_Out Buffer在执行第二步之前需要由主流唤醒从流开始工作即第一组橙色块所示然后执行算法原来的第二步主流上做Rank内搬运从流上做Rank间搬运即中间蓝色和绿色块。最后,从流完成任务并通知主流,即图中最后一组橙色块。

图中主从流同步一共使用了6个Wait,对应到资源中的6个notify。主从流同步的写法如下:

// 主流通知从流,其中stream_表示主流,meshSignalAux_表示安排在从流上的notify
HcclResult AllgatherMeshDirect::MainRecordSub()
{
    for (u32 signalIndex = 0; signalIndex < meshSignalAux_.size(); signalIndex++) {
        CHK_RET(LocalNotify::Post(stream_, dispatcher_, meshSignalAux_[signalIndex],
            profilerInput_.stage));
    }
    return HCCL_SUCCESS;
}

// 从流等待主流,meshStream_表示从流,meshSignalAux_表示安排在从流上的notify
HcclResult AllgatherMeshDirect::SubWaitMain()
{
    for (u32 streamIndex = 0; streamIndex < meshSignalAux_.size(); streamIndex++) {
        CHK_RET(LocalNotify::Wait(meshStreams_[streamIndex], dispatcher_, meshSignalAux_[streamIndex],
            profilerInput_.stage));
    }
    return HCCL_SUCCESS;
}

// 从流通知主流,meshStream_表示从流,meshSignal_表示安排在主流上的notify
HcclResult AllgatherMeshDirect::SubRecordMain()
{
    for (u32 streamIndex = 0; streamIndex < meshSignal_.size(); streamIndex++) {
        CHK_RET(LocalNotify::Post(meshStreams_[streamIndex], dispatcher_, meshSignal_[streamIndex],
            profilerInput_.stage));
    }
    return HCCL_SUCCESS;
}

// 主流等待从流,其中stream_表示主流,meshSignal_表示安排在主流上的notify
HcclResult AllgatherMeshDirect::MainWaitSub()
{
    for (u32 signalIndex = 0; signalIndex < meshSignal_.size(); signalIndex++) {
        CHK_RET(LocalNotify::Wait(stream_, dispatcher_, meshSignal_[signalIndex], profilerInput_.stage));
    }
    return HCCL_SUCCESS;
}

算法编排实现中使用TxAck表示Post远端的Ack notify,使用RxAck表示Wait本端的Ack notify;使用TxDataSignal表示Post远端的DataSignal notify,使用RxDataSignal表示Wait本端的DataSignal notify。编排逻辑代码如下

HcclResult AllgatherMeshDirect::RunAsync(const u32 rank, const u32 rankSize, const std::vector<LINK> &links)
{
    // 获取数据类型对应的字节数
    u32 unitSize = DataUnitSize(dataType_);
    u64 sdmaSize = count_ * unitSize; // 当前一次SDMA通信的字节数
    u64 sliceSize = opInfo_->count * unitSize; // AllGather输入Buffer对应的字节数
    // 获取输入Buffer指针
char* curUerMemInPtr = static_cast<char *>(opInfo_->inputAddr);
// 获取输出Buffer指针
char* curUerMemOutPtr = static_cast<char *>(opInfo_->outputAddr);
// 获取CCL_Out Buffer指针
    char* curCommMemOutPtr = static_cast<char *>(outputMem_.ptr());

// 第一步,本地数据从输入Buffer搬移到CCL_Out
DeviceMem src;
    DeviceMem dst;
    src = DeviceMem::create(curUerMemInPtr, sdmaSize);
    u64 localOffsetByte = (sliceSize * rank) % HCCL_MIN_SLICE_ALIGN_910B;
    dst = DeviceMem::create(curCommMemOutPtr + localOffsetByte, sdmaSize);
    CHK_RET(HcclD2DMemcpyAsync(dispatcher_, dst, src, stream_));

    // 主流通知从流开始干活
    CHK_RET(MainRecordSub());
    CHK_RET(SubWaitMain());

    // 本Rank与远端Rank互发Ack信号
    for (u32 round = 1; round < rankSize; round++) {
        u32 dstRank = BackwardRank(rank, rankSize, round);
        Stream& subStream = meshStreams_[round - 1];
         // TxAck表示Post远端的Ack notify,RxAck表示Wait本端的Ack nofity
        CHK_RET(links[dstRank]->TxAck(subStream));
        CHK_RET(links[dstRank]->RxAck(subStream));
    }

    // 主流将数据从CCL_Out搬移到输出Buffer
    src = dst;
    dst = DeviceMem::create(curUerMemOutPtr + rank * sliceSize, sdmaSize);
    CHK_RET(HcclD2DMemcpyAsync(dispatcher_, dst, src, stream_));

    // 从流从其他Rank搬运数据;本Rank与远端Rank互发DataSignal信号
    for (u32 round = 1; round < rankSize; round++) {
        u32 dstRank = BackwardRank(rank, rankSize, round);
       // 获取要下发Task的从流
        Stream& subStream = meshStreams_[round - 1];
        // 获取server内远端Rank在本Rank映射的CCL_Out Buffer的地址
        void *remMemPtr = nullptr;
        CHK_RET(links[dstRank]->GetRemoteMem(UserMemType::OUTPUT_MEM, &remMemPtr));
        u64 remoteOffsetByte = (sliceSize * dstRank) % HCCL_MIN_SLICE_ALIGN_910B;
        src = DeviceMem::create(static_cast<char *>(remMemPtr) + remoteOffsetByte, sdmaSize);
        dst = DeviceMem::create(curUerMemOutPtr + dstRank * sliceSize, sdmaSize);
       // 将数据从远端Rank读取到本端输出Buffer
        CHK_RET(HcclD2DMemcpyAsync(dispatcher_, dst, src, subStream,
            links[dstRank]->GetRemoteRank(), links[dstRank]->GetLinkType()));
         // TxDataSignal表示Post远端的DataSignal notify
// RxDataSignal表示Wait本端的DataSignal nofity
        CHK_RET(links[dstRank]->TxDataSignal(subStream));
        CHK_RET(links[dstRank]->RxDataSignal(subStream));
    }
    // 从流通知主流任务完成
    CHK_RET(SubRecordMain());
    CHK_RET(MainWaitSub());

    return HCCL_SUCCESS;
}

4.6 参考代码

本样例即为HCCL开放仓中的AllGatherMeshOpbaseExecutor算法实现,对应代码链接可参考以下链接。为了减少干扰,文档中贴出的代码删除了部分逻辑,不影响整体流程。

算法选择部分代码实现

src/domain/collective_communication/algorithm/impl/operator/all_gather_operator.cc · Ascend/cann-hccl - Gitee.com

资源计算部分代码实现

src/domain/collective_communication/algorithm/impl/coll_executor/coll_all_gather/coll_all_gather_mesh_opbase_executor.cc · Ascend/cann-hccl - Gitee.com

算法编排部分代码实现

src/domain/collective_communication/algorithm/base/executor/all_gather_mesh_direct.cc · Ascend/cann-hccl - Gitee.com

5 参考资料

  1. HCCL——昇腾高性能集合通信库
  2. HCCL源码定制开发指南(docs/hccl_customized_dev/README.md · Ascend/cann-hccl - Gitee.com)
  3. HCCL开放代码仓(cann-hccl: cann-hccl,是基于昇腾硬件的高性能集合通信库(Huawei Collective Communication Library,简称HCCL)。)

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

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

相关文章

Http的get请求中的URL中的占位符参数和查询参数有什么区别

Http的GET请求中的URL中的占位符参数和查询参数在功能、位置和用途上存在明显的区别。 占位符参数&#xff08;Path Variables&#xff09; 定义与位置&#xff1a;占位符参数是通过URL模板中的{}定义的&#xff0c;它们位于URL的路径&#xff08;path&#xff09;部分。例如…

C#文件的输入和输出

一个文件是一个存储在磁盘中带有指定名称和目录路径的数据集合.当打开文件进行读写时,它变成一个流.从根本上说,流是通过通信路径传递的字节序列.有两个主要的流:输入流和输出流.输入流用于从文件读取数据,输出流用于向文件写入数据. C#I/O类 System.IO命名空间有各种不同的类…

带权重的随机算法

假设有10名学生&#xff0c;其中5个男生&#xff0c;5个女生。 要求点到男生的概率为70%&#xff0c;女生的概率为30%。 给男生和女生设置权重&#xff0c;其中男生权重为7&#xff0c;女生权重为3。 public class Test02_case2 {public static void main(String[] args) th…

Expected expression after operator

这个错误直译过来就是:运算符号后没有预期的表达式 这个错误通常出现在编程语言中&#xff0c;尤其是在编写C或C等类型语言的时候&#xff0c;它意味着在源代码中遇到了一个操作符&#xff08;比如 , -, *, /, , 等等&#xff09;&#xff0c;但在该操作符后面没有紧跟相应的表…

【最新华为OD机试E卷】最大利润-贪心的商人(100分)-多语言题解-(Python/C/JavaScript/Java/Cpp)

🍭 大家好这里是春秋招笔试突围 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-E/D卷的三语言AC题解 💻 ACM金牌🏅️团队| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 🍿 最新华为OD机试D卷目录,全、新、准,题目覆盖率达 95% 以上,…

Dolphinscheduler 3.2.0版本参数传递并使用switch任务进行判断

原文阅读&#xff1a;【巨人肩膀社区博客分享】3.2.0版本参数传递并使用switch任务进行判断 目标&#xff1a;根据日期判断执行哪项子任务 &#xfeff; 调度器版本&#xff1a;3.2.0 在这个版本中官方支持的参数传递任务类型有6中&#xff0c;分别为shell,sql,procedure,py…

网络压缩之网络剪枝(network pruning)

网络剪枝&#xff08;network pruning&#xff09;就是要把网络里面的一些参数剪掉。剪枝就是修剪的意思&#xff0c;把网络里面的一些参数剪掉。为什么可以把网络里面的一些参数剪 掉呢&#xff1f;这么大的网络里面有很多很多的参数&#xff0c;每一个参数不一定都有在做事。…

AcWing 897. 最长公共子序列

动态规划就是多见识应用题就完事儿了&#xff0c;也没有什么好说的。 讲解参考&#xff1a; 【E05 线性DP 最长公共子序列】 #include<iostream> #include<algorithm> #define N 1010 using namespace std; char a[N],b[N]; int n,m; int f[N][N]; int main(){…

欧拉 函数

互质&#xff1a; 互质是公约数只有1的两个整数&#xff0c;叫做互质整数。公约数只有1的两个自然数&#xff0c;叫做互质自然数&#xff0c;后者是前者特殊情况。 &#xff08;1和-1与所有整数互质&#xff0c;而且它们是唯一与0互质的整数&#xff09; 互质的判断方法&…

微信公众号文章导出工具 100%还原原文样式:wechat-article-exporter

wechat-article-exporter是一款微信公众号文章导出工具&#xff0c;能够100%还原原文样式&#xff0c;工具受 WeChat_Article 项目的启发所写&#xff0c;目前支持 搜索公众号和公众号内文章&#xff0c;导出文章为包含图片和样式文件的HTML格式&#xff08; (打包了图片和样式…

中仕公考:这样备考,你天生就是公务员!

根据上岸学员的反馈&#xff0c;小编发现了一些共通点&#xff0c;无论是在职备考还是全职备考&#xff0c;只要做到以下几点&#xff0c;不上岸那是不可能的! 1. 作息规律&#xff0c;早起不熬大夜。每天按时早起&#xff0c;挤出时间用来学习&#xff0c;晚上不熬夜学习到很…

Windows编程系列:PE文件结构

Windows编程系列&#xff1a;PE文件结构 PE文件结构 Portable Executable (PE)&#xff0c;可移植的可执行文件。在Windows平台下&#xff0c;所有的可执行文件&#xff08;包括.exe, .dll, .sys, .ocx, .com等&#xff09;均使用PE文件结构。这些使用了PE文件结构的可执行文…

HarmonyOS开发实战( Beta5版)应用性能工具CPU Profiler的使用规范

简介 本文档介绍应用性能分析工具CPU Profiler的使用方法&#xff0c;该工具为开发者提供性能采样分析手段&#xff0c;可在不插桩情况下获取调用栈上各层函数的执行时间&#xff0c;并展示在时间轴上。 开发者可通过该工具查看TS/JS代码及NAPI代码执行过程中的时序及耗时情况…

Web-gpt

AJAX AJAX&#xff08;Asynchronous JavaScript and XML&#xff0c;异步JavaScript和XML&#xff09;是一种用于创建动态网页应用的技术。它允许网页在不重新加载整个页面的情况下&#xff0c;异步地从服务器请求数据&#xff0c;并将这些数据更新到网页上。这提高了用户体验…

HarmonyOS开发实战( Beta5版)小程序场景性能优化开发指导

简介 小程序是一种轻量级的应用&#xff0c;它不需要下载、安装即可使用&#xff0c;用户可以通过扫描二维码或者搜索直接打开使用。小程序运行在特定的平台上&#xff0c;平台提供了小程序的运行环境&#xff08;运行容器&#xff09;和一些基础服务&#xff08;小程序API&am…

【C++ 第十八章】C++11 新增语法(2)

前情回顾&#xff1a; 【C11 新增语法&#xff08;1&#xff09;&#xff1a;1~6 点】 C11出现与历史、花括号统一初始化、initializer_list初始化列表、 auto、decltype、nullptr、STL的一些新变化 本文会使用到自己模拟实现的 string 和 list 类&#xff0c;为了更好的观察各…

笔记整理—内核!启动!—uboot部分(2)

上文中&#xff0c;我们说到了使用uboot去启动kernel支持的几种方式以及压缩kernel的几种形式&#xff0c;本章节将要接着内核的启动说起。 上一章我们对uImage格式进行了初步的说明&#xff0c;并说这样的格式已经被废弃&#xff0c;但是依然保留了相应的代码。boot_get_kerne…

MATLAB生成mif文件

MATLAB代码 % 参数设置 N 4096; % 数据点数量 t linspace(0, 2*pi, N); % 时间向量 width 12; % 位宽% 正弦波 sine_wave 2.5 * sin(t) 2.5; % 幅度在0到5之间% 三角波 tri_wave 5 - abs(mod(t/(2*pi)*4, 2) - 1);% 方波 square_wave 2.5 * (square(t) 1); % 将范围调…

Hive Tutorial For Beginners

Hive Tutorial For Beginners 一、Hive历史&#xff08;History of Hive&#xff09; Facebook 在面对日益增长的大数据时&#xff0c;选择了 Hadoop 作为解决方案。 但问题在于&#xff0c;许多用户并不熟悉 Java 或其他编程语言&#xff0c;这使得使用 Hadoop 的 MapReduc…

代码随想录——回文子串(Leetcode 647)

题目链接 我的题解&#xff08;双指针&#xff09; 思路&#xff1a; 当然&#xff0c;以下是对您提供的代码的解释&#xff1a; class Solution {public int countSubstrings(String s) {// 初始化回文子字符串的数量int count 0;// 遍历字符串的每个字符&#xff0c;使用…