当前位置: 首页 > news >正文

基于Ascend C的Matmul算子性能优化最佳实践

矩阵乘法是深度学习计算中的基础操作,对于提升模型训练和推理速度至关重要。昇腾AI处理器是一款专门面向AI领域的AI加速器,其AI Core采用达芬奇架构,以高性能Cube计算引擎为基础,针对矩阵运算进行加速,可大幅提高单位面积下的AI算力。Matmul算子实现的功能是矩阵乘法,通过Ascend C算子编程语言优化该算子的实现逻辑,可以使其在昇腾AI处理器上获得更优的执行性能。希望通过本案例的讲解,可以为开发者优化昇腾Cube类算子性能带来启发。


本案例以矩阵维度M = 4096,N = 5120,K = 4096,输入数据类型half,输出数据类型float,输出格式是ND为例,性能验证平台为Atlas A2训练系列产品/Atlas 800I A2推理产品,介绍针对Matmul算子的主要优化手段,包括优化分核逻辑、优化基本块、开启大包搬运。

  • 优化分核逻辑:开启尽量多的Cube核使能并行计算。
  • 优化基本块,选择最优的baseM、baseN、baseK参数。
  • 开启大包搬运:从Global Memory搬运数据到L1时,对于A矩阵,一次搬入depthA1个基本块,基本块大小为baseM * baseK,对于B矩阵,一次搬入depthB1个基本块,基本块大小为baseN * baseK。使能大包搬运后,一次搬入的数据量变大,提升MTE2搬运效率。

分析主要瓶颈点

借助昇腾Profiling性能数据可较方便地分析主要瓶颈点,这里我们重点分析MTE2,Cube,Scalar pipeline的流水情况,其中MTE2(Memory Transfer Engine)pipeline反映了数据的搬入情况,Cube和Scalar pipeline则反映了AI Core中的数据计算及标量的使用情况。

优化前Profiling数据

从上图Profiling数据来看,aic_mte2_ratio数值是0.973,这表明MTE2类型指令的cycle数在total cycle数中的占比过大,这意味着当前性能瓶颈点可能在于MTE2流水。此外,从图中的Block Dim数值4也可以看到,参与计算的AI处理器核并没有用满,这里假设当前案例使用的AI处理器上共有20个核。整体优化思路如下:

  • 优化分核逻辑,假设CurrentCore是未优化前分核的Cube核数,MaxCore为最大Cube核数,当开启全部核并行做当前shape数据量的计算时,预估性能收益约为MaxCore / CurrentCore的倍数。
  • 优化基本块切分将影响搬运数据的效率,算子搬运的总数据量为搬运的左矩阵和右矩阵数据量之和。根据矩阵乘法的算法,搬运左矩阵的次数为N / baseN,搬运右矩阵的次数为M / baseM,即搬运总数据量totalCnt = (N / baseN) * M * K + (M / baseM) * K * N。预估性能收益为搬运数据量的比值,优化前搬运数据量totalCnt0/优化后搬运数据量totalCnt1,化简后结果为(1 / baseM0 + 1 / baseN0) / (1 / baseM1 + 1 / baseN1),其中,baseM0, baseN0为优化前基本块参数,baseM1, baseN1为优化后基本块参数。
  • 开启大包搬运后,指令条数变化、地址对齐等因素会影响性能,按照经验预估,对于MTE2为性能瓶颈的场景,会有20%以上的MTE2性能收益。

优化分核逻辑

由Profiling数据看出分核数为4,启动更多的核同时计算,可以提高计算并行度。在当前案例使用的AI处理器上共20个核,每个核中包含1个Cube Core和2个Vector Core。程序中设置blockDim为实际使用的核数20。

// 代码片段 
uint32_t blockDim = 20; // 优化前blockDim为4 
CHECK_ACL(aclInit(nullptr)); 
aclrtContext context; 
int32_t deviceId = 0; 
CHECK_ACL(aclrtSetDevice(deviceId)); 
CHECK_ACL(aclrtCreateContext(&context, deviceId)); 
aclrtStream stream = nullptr; 
CHECK_ACL(aclrtCreateStream(&stream)); 
 
uint8_t *aHost; 
uint8_t *aDevice; 
CHECK_ACL(aclrtMallocHost((void **)(&aHost), aFileSize)); 
CHECK_ACL( 
  aclrtMalloc((void **)&aDevice, aFileSize, ACL_MEM_MALLOC_HUGE_FIRST)); 
ReadFile("./input/x1_gm.bin", aFileSize, aHost, aFileSize); 
// PrintData(aHost, 16, printDataType::HALF); 
CHECK_ACL(aclrtMemcpy(aDevice, aFileSize, aHost, aFileSize, 
                    ACL_MEMCPY_HOST_TO_DEVICE)); 
 
uint8_t *bHost; 
uint8_t *bDevice; 
CHECK_ACL(aclrtMallocHost((void **)(&bHost), bFileSize)); 
CHECK_ACL( 
  aclrtMalloc((void **)&bDevice, bFileSize, ACL_MEM_MALLOC_HUGE_FIRST)); 
ReadFile("./input/x2_gm.bin", bFileSize, bHost, bFileSize); 
// PrintData(bHost, 16, printDataType::HALF); 
CHECK_ACL(aclrtMemcpy(bDevice, bFileSize, bHost, bFileSize, 
                    ACL_MEMCPY_HOST_TO_DEVICE)); 
 
uint8_t *workspaceHost; 
uint8_t *workspaceDevice; 
CHECK_ACL(aclrtMallocHost((void **)(&workspaceHost), workspaceSize)); 
CHECK_ACL(aclrtMalloc((void **)&workspaceDevice, workspaceSize, 
                    ACL_MEM_MALLOC_HUGE_FIRST)); 
 
uint8_t *tilingHost; 
uint8_t *tilingDevice; 
CHECK_ACL(aclrtMallocHost((void **)(&tilingHost), tilingFileSize)); 
CHECK_ACL(aclrtMalloc((void **)&tilingDevice, tilingFileSize, 
                    ACL_MEM_MALLOC_HUGE_FIRST)); 
CHECK_ACL(aclrtMemcpy(tilingHost, tilingFileSize, GenerateTiling(), 
                    tilingFileSize, ACL_MEMCPY_HOST_TO_HOST)); 
// PrintData(tilingHost, 16, printDataType::UINT32_T); 
CHECK_ACL(aclrtMemcpy(tilingDevice, tilingFileSize, tilingHost, 
                    tilingFileSize, ACL_MEMCPY_HOST_TO_DEVICE)); 
 
uint8_t *cHost; 
uint8_t *cDevice; 
CHECK_ACL(aclrtMallocHost((void **)(&cHost), cFileSize)); 
CHECK_ACL( 
  aclrtMalloc((void **)&cDevice, cFileSize, ACL_MEM_MALLOC_HUGE_FIRST)); 
 
matmul_custom_do(blockDim, stream, aDevice, bDevice, cDevice, workspaceDevice, tilingDevice);

由于Matmul API都是从Vector侧发起的,按照Cube Core和Vector Core的配比1:2,在Matmul tiling计算中需要按照2倍的blockDim数切分,因此Tiling代码中,设置Tiling API按照40个核进行数据切分,如下代码所示。

int usedCoreNum = 40; // 优化前usedCoreNum是8 
int runMode = 1; 
int32_t baseM = 64; // 64 
int32_t baseN = 64; // 64 
optiling::TCubeTiling tilingData; 
auto ascendcPlatform = platform_ascendc::PlatformAscendCManager::GetInstance(socVersion);     
MultiCoreMatmulTiling tilingApi(*ascendcPlatform); 
tilingApi.SetDim(usedCoreNum);

修改代码后,算子执行时间(对应aicore_time)从12045us下降到2532us,约等于(20核 / 4核) = 5倍的性能提升。

优化分核逻辑后Profilling数据

优化基本块

当前Tiling中设置的base块为 [baseM, baseN, baseK] = [64, 64, 256],这种基本块Cube计算cycle少,计算访存比(即计算量与需要数据量的比值)低;搬出一次Matmul结果到Global Memory的base块是64 * 64,由于输出格式是ND,数据类型是float,搬出下一次Matmul结果的起始地址需要偏移一个baseN的大小,即64 * 4 = 256字节,导致fixpipe搬出时Global Memory地址非512byte对齐,那么需要设置更优的基本块。


针对当前shape较大的场景,基本块的选择原则为计算访存比最大,即在Cube计算量最大的情况下,访存的数据量最小。在输入为fp16类型的情况下,Cube执行单元1 cycle能算16 * 16 * 16个数。根据经验,[baseM, baseN, baseK] = [128, 256, 64]和[128, 128, 128]两种切分方案均满足搬出时Global Memory地址512Byte对齐(每搬出一次Matmul结果时,地址分别偏移256 * 4byte和128 * 4byte),Cube计算cycle数一致,为(128 * 64 * 256) / (16 * 16 * 16) = (128 * 128 * 128) / (16 * 16 * 16) = 512cycle。


针对[baseM, baseN, baseK] = [128, 256, 64],计算访存比为512cycle / (128 * 64 * 2 + 256 * 64 * 2) = 512cycle / 48KB;针对[baseM, baseN, baseK] = [128, 128, 128],计算访存比为512cycle / (128 * 128 * 2 + 128 * 128 * 2) = 512cycle / 64KB。可见,[128, 256, 64]基本块方案的计算访存比更高,计算密度更大,同样的计算量,需要的数据量最小,可最大限度地提高Cube单元计算量。


修改Tiling代码,通过SetFixSplit()接口设置baseM和baseN,tiling函数会自动计算出最优baseK,这里得到64。

int32_t baseM = 128; // 优化前baseM是64 
int32_t baseN = 256; // 优化前baseN是64 
 
optiling::TCubeTiling tilingData; 
auto ascendcPlatform = platform_ascendc::PlatformAscendCManager::GetInstance(socVersion);     
MultiCoreMatmulTiling tilingApi(*ascendcPlatform); 
tilingApi.SetDim(usedCoreNum); 
tilingApi.SetAType(leftPos, leftFormat, leftDtype, bool(transposeA)); 
tilingApi.SetBType(rightPos, rightFormat, rightDtype, bool(transposeB)); 
tilingApi.SetCType(resPos, resFormat, resDtype); 
tilingApi.SetBiasType(biasPos, biasFormat, biasDtype); 
 
tilingApi.SetOrgShape(M, N, K); 
tilingApi.SetShape(M, N, K); 
tilingApi.SetFixSplit(baseM, baseN, -1);

从下图可以看到,使能这组基本块后,MTE2耗时(对应aic_mte2_time)从2452us降低到808us,MTE2性能提升3倍。

优化基本块后Profilling数据

使能大包搬运

当前带宽利用率为:totalSize / mte2Time = totalCnt * dtype / mte2Time,代入数据计算为 2491GB/s。未使能大包搬运的情况下,矩阵从Global Memory搬运到L1一次只搬运1个基本块。通过模板参数使能大包搬运,一次搬运多个基本块,提高MTE2带宽利用率。

 // 原始matmul对象定义: 
  Matmul<MatmulType<TPosition::GM, CubeFormat::ND, A_T>, 
         MatmulType<TPosition::GM, CubeFormat::ND, B_T>, 
         MatmulType<TPosition::GM, CubeFormat::ND, C_T>, 
         MatmulType<TPosition::GM, CubeFormat::ND, BiasT>>> 
      mm; 
 // 通过在定义matmul对象的模板参数里加上CFG_MDL参数使能大包搬运功能: 
  Matmul<MatmulType<TPosition::GM, CubeFormat::ND, A_T>, 
         MatmulType<TPosition::GM, CubeFormat::ND, B_T>, 
         MatmulType<TPosition::GM, CubeFormat::ND, C_T>, 
         MatmulType<TPosition::GM, CubeFormat::ND, BiasT>, CFG_MDL>> 
      mm;

从下图可以看到,使能大包搬运后,MTE2耗时从808us下降到591us,带宽利用率代入数据计算为3406GB/s,利用率提升36%+,Cube利用率(对应aic_mac_ratio)达到80%+。

使能大包搬运后Profilling数据

验证优化方案性能收益

  • 优化分核逻辑,实际收益4.75倍,约等于(20核 / 4核) = 5倍收益,并且考虑到核的启动开销,可以认为两者基本一致。
  • 优化基本块,实际收益约3倍,理论评估带入上述分析公式,收益为(1 / 64 + 1 / 64) / (1 / 128 + 1 / 256),约等于2.7倍,考虑到cache缓存的影响,可以认为两者基本一致。
  • 大包搬运,大包搬运实际收益25%+,与经验值基本一致。

但需要注意的是,优化分核逻辑和基本块一般在输入数据shape足够大、数据量足够多时,才能分满核和使能最优的基本块。因此,大shape场景下MTE2 Bound算子可参考此案例的优化手段。

更多学习资源

了解更多Ascend C算子性能优化手段和实践案例,请访问昇腾社区Ascend C信息专区:昇腾Ascend C-入门课程-学习资源-算子文档-昇腾社区 

 

 相关推荐阅读:
《基于Ascend C的FlashAttention算子性能优化最佳实践》

相关文章:

基于Ascend C的Matmul算子性能优化最佳实践

矩阵乘法是深度学习计算中的基础操作&#xff0c;对于提升模型训练和推理速度至关重要。昇腾AI处理器是一款专门面向AI领域的AI加速器&#xff0c;其AI Core采用达芬奇架构&#xff0c;以高性能Cube计算引擎为基础&#xff0c;针对矩阵运算进行加速&#xff0c;可大幅提高单位面…...

SQL注入之EVAL长度限制突破技巧

要求&#xff1a; PHP Eval函数参数限制在16个字符的情况下 &#xff0c;如何拿到Webshell&#xff1f; widows小皮环境搭建&#xff1a; 使用phpstudy搭建一个网站。 随后在该eval文件夹下创建一个webshell.php文件&#xff0c;并在其输入代码环境 解题思路&#xff1a; 通…...

稀疏注意力:时间序列预测的局部性和Transformer的存储瓶颈

时间序列预测是许多领域的重要问题&#xff0c;包括对太阳能发电厂发电量、电力消耗和交通拥堵情况的预测。在本文中&#xff0c;提出用Transformer来解决这类预测问题。虽然在我们的初步研究中对其性能印象深刻&#xff0c;但发现了它的两个主要缺点:(1)位置不可知性:规范Tran…...

详谈系统中的环境变量

目录 前言1. 指令背后的本质2. 环境变量背后的本质3. 环境变量到底是什么4. 命令行参数5. 本地变量 与 内置命令6. 环境变量的相关命令 前言 相信在 it 行业学习或者工作的小伙伴们&#xff0c;基本都配置过环境变量&#xff08;windows环境下&#xff09;&#xff0c;如果你也…...

RAG与LLM原理及实践(11)--- Milvus hybrid search 源码分析及思想

目录 背景 hybrid search 源码分析 WeightedRanker 源码 hybrid search 核心 参数详解 基本入参 扩展入参 aysnc方式代码调用案例 说明 源码逻辑 prepare 调用过程 stub 调用结果 stub 调用过程 blocking 与 async 调用方式 深入内部core weightedRanker 的ch…...

JavaScript模拟空调效果

JavaScript模拟空调效果https://www.bootstrapmb.com/item/15074 在JavaScript中模拟空调效果主要依赖于前端界面的交互和状态变化&#xff0c;因为实际的温度调节、风扇速度调整等硬件操作无法直接通过JavaScript在浏览器中实现。不过&#xff0c;我们可以通过JavaScript来模…...

14.2 Pandas数据处理

欢迎来到我的博客,很高兴能够在这里和您见面!欢迎订阅相关专栏: 工💗重💗hao💗:野老杂谈 ⭐️ 全网最全IT互联网公司面试宝典:收集整理全网各大IT互联网公司技术、项目、HR面试真题. ⭐️ AIGC时代的创新与未来:详细讲解AIGC的概念、核心技术、应用领域等内容。 ⭐…...

python学习7---多进程

一、介绍 多进程是指在同一程序中创建多个独立的进程来执行任务。每个进程都有自己独立的内存空间&#xff0c;相互之间不干扰。 因为GIL锁的存在&#xff0c;对于CPU密集型任务&#xff08;例如计算密集型操作&#xff09;&#xff0c;使用多进程可以提高程序的效率。 优点&am…...

基于Spring + Vue的旅游景区项目+源代码+文档说明

文章目录 源代码下载地址项目介绍项目功能界面预览 项目备注源代码下载地址 源代码下载地址 点击这里下载源码 项目介绍 基于Spring Vue的旅游景区项目 项目功能 民宿管理员&#xff1a;订单数量统计&#xff0c;订单交易额统计&#xff0c;客房统计饼图&#xff0c;酒店…...

Java后端面试题

Redis缓存穿透、雪崩、击穿&#xff0c;布隆过滤器 一致性hash 一致性hash sharding-jdbc实现一致性hash #一致性hash, 应用在mysql数据库的分库分表上&#xff0c; 现在已经完成了分库分表&#xff0c; 现在的问题出现了&#xff0c; 需要继续新增数据库节点&#xff0c; 请…...

【Git】远程仓库新建分支后,拉到本地开发

1. 在远程仓库上创建分支 2. git fetch origin&#xff1a;在本地同步远程仓库的分支&#xff08;获取远程仓库所有分支的所有修改&#xff09; 3. git remote -a&#xff1a;查看所有分支&#xff08;远程&#xff0b;本地&#xff09; 4. git checkout -b 本地名 远程仓库…...

React H5设置企业级v6版本路由的配置

路由配置是项目开发的必要一环&#xff0c;尤其是目前流行SPA&#xff0c;下面看看如何使用v6版本路由进行合理的H5路由配置 一、基本页面结构&#xff08;目录根据开发要求建&#xff0c;下面仅用于展示配置路由&#xff09; 二、具体文件实现 1. index.tsx import React f…...

【微信小程序】全局配置

1. 全局配置文件及常用的配置项 2.window (1).小程序窗口的组成部分 (2). 了解 window 节点常用的配置项 (3). 设置导航栏的标题 (4). 设置导航栏的背景色 (5). 设置导航栏的标题颜色 (6). 全局开启下拉刷新功能 (7). 设置下拉刷新时窗口的背景色 (8).设置下拉刷新时 loading …...

25届秋招网络安全面试资料库

吉祥知识星球http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247485367&idx1&sn837891059c360ad60db7e9ac980a3321&chksmc0e47eebf793f7fdb8fcd7eed8ce29160cf79ba303b59858ba3a6660c6dac536774afb2a6330#rd 《网安面试指南》http://mp.weixin.qq.com/s?…...

Adobe Dimension DN v4.0.2 解锁版下载安装教程 (专业的三维3D建模工具)

前言 Adobe Dimension&#xff08;简称DN&#xff09;是一款3D设计软件&#xff0c;三维合成和渲染工具&#xff0c;2D平面的二维转为3D立体的三维合成工具&#xff0c;用于3Dmax\C4D\MAYA等三维软件生成的效果图&#xff0c;在3D场景中排列对象、图形和光照。3D应用程序使用的…...

Python中*args 和 **kwargs作参数时有什么区别

*args 和 **kwargs 是 Python 函数定义中用于处理可变数量的参数的语法&#xff0c;初学者对于二者总是傻傻区分不了&#xff0c;今天我们就来详细解读一下这两个在参数传递时有什么不同。 首先要明白单个星号可以解包元组或者列表&#xff0c;两个星号可以解包字典。如&#…...

[CSS3]2D与3D变换技术详解

文章目录 2D变换&#xff08;2D Transform&#xff09;3D变换&#xff08;3D Transform&#xff09;结语 CSS3中的2D变换与3D变换是指通过transform属性对HTML元素进行几何操作&#xff0c;使其在二维或三维空间中进行移动、旋转、缩放和倾斜等变换。这些变换为前端开发者提供了…...

大恒相机通过Line2或Line3直接给出3.3V触发,形成分时曝光

大恒相机通过Line2或Line3直接给出3.3V触发&#xff0c;形成分时曝光 一、分时曝光需求二、3.3V信号分时曝光设计 写在前面 上班了&#xff0c;没多少时间再去精度论文了&#xff0c;大多是项目上的事情。 一、分时曝光需求 一般的12V光源通过光源控制器与大恒相机Line1线连接…...

electronjs实现打开的网页密码自动保存

在 Electron 中实现自动保存网页密码的功能涉及到几个步骤&#xff0c;以下是一个基本的实现思路&#xff1a; 1. 监听登录事件 首先&#xff0c;你需要监听用户的登录事件。当用户在一个网页上登录后&#xff0c;通常会有一个 POST 请求发送到服务器验证凭据。你可以监听这个…...

观测云的自动化监控:CRD 资源与自动发现

在云原生技术快速发展的今天&#xff0c;Kubernetes已成为企业容器化应用的中心舞台。随着应用的规模化和动态化&#xff0c;传统的监控方法已经难以满足需求。自动化监控&#xff0c;以其高效性和准确性&#xff0c;成为云原生监控的新趋势。观测云平台通过与Kubernetes的深度…...

【kafka】Golang实现分布式Masscan任务调度系统

要求&#xff1a; 输出两个程序&#xff0c;一个命令行程序&#xff08;命令行参数用flag&#xff09;和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽&#xff0c;然后将消息推送到kafka里面。 服务端程序&#xff1a; 从kafka消费者接收…...

java_网络服务相关_gateway_nacos_feign区别联系

1. spring-cloud-starter-gateway 作用&#xff1a;作为微服务架构的网关&#xff0c;统一入口&#xff0c;处理所有外部请求。 核心能力&#xff1a; 路由转发&#xff08;基于路径、服务名等&#xff09;过滤器&#xff08;鉴权、限流、日志、Header 处理&#xff09;支持负…...

Leetcode 3576. Transform Array to All Equal Elements

Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接&#xff1a;3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到&#xf…...

在rocky linux 9.5上在线安装 docker

前面是指南&#xff0c;后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止

<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet&#xff1a; https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

MODBUS TCP转CANopen 技术赋能高效协同作业

在现代工业自动化领域&#xff0c;MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步&#xff0c;这两种通讯协议也正在被逐步融合&#xff0c;形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...

现代密码学 | 椭圆曲线密码学—附py代码

Elliptic Curve Cryptography 椭圆曲线密码学&#xff08;ECC&#xff09;是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础&#xff0c;例如椭圆曲线数字签…...

【决胜公务员考试】求职OMG——见面课测验1

2025最新版&#xff01;&#xff01;&#xff01;6.8截至答题&#xff0c;大家注意呀&#xff01; 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:&#xff08; B &#xff09; A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...

LLMs 系列实操科普(1)

写在前面&#xff1a; 本期内容我们继续 Andrej Karpathy 的《How I use LLMs》讲座内容&#xff0c;原视频时长 ~130 分钟&#xff0c;以实操演示主流的一些 LLMs 的使用&#xff0c;由于涉及到实操&#xff0c;实际上并不适合以文字整理&#xff0c;但还是决定尽量整理一份笔…...

实战设计模式之模板方法模式

概述 模板方法模式定义了一个操作中的算法骨架&#xff0c;并将某些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的前提下&#xff0c;重新定义算法中的某些步骤。简单来说&#xff0c;就是在一个方法中定义了要执行的步骤顺序或算法框架&#xff0c;但允许子类…...