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

TVM调度原语完全指南:从入门到微架构级优化

调度原语

在TVM的抽象体系中,调度(Schedule)是对计算过程的时空重塑。每一个原语都是改变计算次序、数据流向或并行策略的手术刀。其核心作用可归纳为:

优化目标 = max ⁡ ( 计算密度 内存延迟 × 指令开销 ) \text{优化目标} = \max \left( \frac{\text{计算密度}}{\text{内存延迟} \times \text{指令开销}} \right) 优化目标=max(内存延迟×指令开销计算密度)

下面我们将解剖20+个核心原语,揭示它们的运作机制与优化场景。


基础维度操作

1. split:维度的量子裂变

作用:将单个维度拆分为多个子维度,为后续优化创造空间

# 将长度128的维度拆分为(外轴, 内轴)=(16, 8)  
outer, inner = s[op].split(op.axis[0], factor=8)  
# 或者指定外层大小  
outer, inner = s[op].split(op.axis[0], nparts=16)  '''  
数学等价转换:  
原始迭代: for i in 0..127  
拆分后: for i_outer in 0..15  for i_inner in 0..7  i = i_outer * 8 + i_inner  
'''  

硬件视角

  • 当处理256-bit SIMD寄存器时,拆分成8个float32元素的分块可完美利用向量化
  • 在L1缓存为32KB的CPU上,拆分后的子块应满足:
    子块大小 × 数据类型大小 ≤ 32768 B \text{子块大小} \times \text{数据类型大小} \leq 32768B 子块大小×数据类型大小32768B

2. fuse:维度的熔合反应

作用:合并多个连续维度,简化循环结构

fused = s[op].fuse(op.axis[0], op.axis[1])  
'''  
数学等价:  
原始: for i in 0..15  for j in 0..31  
合并后: for fused in 0..511 (16*32=512)  
'''  

优化场景

  • 当相邻维度具有相同优化策略时,减少循环嵌套层数
  • 与parallel原语配合实现粗粒度并行
  • 案例:将H和W维度融合后做分块,更适合GPU线程块划分

3. reorder:维度的空间折叠

作用:重新排列循环轴的顺序

s[op].reorder(op.axis[2], op.axis[0], op.axis[1])  
'''  
原始顺序: axis0 -> axis1 -> axis2  
调整后: axis2 -> axis0 -> axis1  
'''  

硬件敏感优化

  • 将内存连续访问的维度置于内层循环
# 将通道维度移到最内层以利用向量化  
s[conv].reorder(n, h, w, c)  
  • 在GPU上将块索引维度提前以提升局部性
s[matmul].reorder(block_idx, thread_idx, inner)  

并行化武器库

4. parallel:多核并发的起搏器

作用:标记循环轴进行多线程并行

s[op].parallel(op.axis[0])  

实现机制

  • 在LLVM后端会生成OpenMP pragma指令
#pragma omp parallel for  
for (int i = 0; i < N; ++i)  

黄金法则

  • 并行粒度不宜过细(避免线程创建开销)
  • 每个线程的任务量应大于10μs
  • 案例:对batch维度做并行,每个线程处理不同样本

5. vectorize:SIMD的激活密钥

作用:将内层循环转换为向量化指令

s[op].vectorize(inner_axis)  

代码生成示例
原始标量计算:

for (int i = 0; i < 8; ++i)  C[i] = A[i] + B[i];  

向量化后(AVX2):

__m256 va = _mm256_load_ps(A);  
__m256 vb = _mm256_load_ps(B);  
__m256 vc = _mm256_add_ps(va, vb);  
_mm256_store_ps(C, vc);  

性能临界点

  • 向量化收益公式:
    加速比 = min ⁡ ( 元素数 向量宽度 , 内存带宽 ) \text{加速比} = \min\left(\frac{\text{元素数}}{\text{向量宽度}}, \text{内存带宽}\right) 加速比=min(向量宽度元素数,内存带宽)
  • 当循环长度不是向量宽度整数倍时,需尾部处理

6. bind:硬件线程的映射协议

作用:将循环轴绑定到硬件线程索引

block_x = tvm.thread_axis("blockIdx.x")  
s[op].bind(op.axis[0], block_x)  

GPU编程范式

  • blockIdx.x:GPU线程块索引
  • threadIdx.x:块内线程索引
  • 典型绑定策略:
    bx = tvm.thread_axis("blockIdx.x")  
    tx = tvm.thread_axis("threadIdx.x")  
    s[matmul].bind(s[matmul].op.axis[0], bx)  
    s[matmul].bind(s[matmul].op.axis[1], tx)  
    

CPU-GPU差异

  • CPU:通常绑定到OpenMP线程
  • GPU:需要精确管理线程层次结构

内存优化原语

7. compute_at:计算的时空折叠

作用:将一个阶段的计算插入到另一个阶段的指定位置

s[producer].compute_at(s[consumer], consumer_axis)  

优化效果

  • 提升数据局部性,减少中间结果存储
  • 案例:在卷积计算中,将输入加载插入到输出通道循环内

8. storage_align:内存对齐的标尺

作用:调整张量存储的内存对齐

s[op].storage_align(axis, factor, offset)  

底层原理

  • 确保数据地址满足:
    address % factor = = offset \text{address} \% \text{factor} == \text{offset} address%factor==offset
  • 典型用例:
    # 对齐到64字节边界(适合AVX-512)  
    s[input].storage_align(axis=2, factor=64, offset=0)  
    

性能影响

  • 对齐错误可导致性能下降10倍以上
  • 现代CPU对非对齐访问的惩罚已减小,但SIMD指令仍需对齐

9. cache_read/cache_write:数据的时空驿站

作用:创建数据的临时缓存副本

AA = s.cache_read(A, "shared", [B])  

GPU优化案例

# 将全局内存数据缓存到共享内存  
s[AA].compute_at(s[B], bx)  
s[AA].bind(s[AA].op.axis[0], tx)  

缓存层次选择

缓存类型硬件对应延迟周期
“local”寄存器1
“shared”GPU共享内存10-20
“global”设备内存200-400

循环优化原语

10. unroll:循环展开的时空折叠

作用:将循环体复制多份,消除分支预测开销

s[op].unroll(inner_axis)  

代码生成对比
原始循环:

for (int i = 0; i < 4; ++i) {  C[i] = A[i] + B[i];  
}  

展开后:

C[0] = A[0] + B[0];  
C[1] = A[1] + B[1];  
C[2] = A[2] + B[2];  
C[3] = A[3] + B[3];  

收益递减点

  • 循环体过大会导致指令缓存压力
  • 经验公式:
    最佳展开因子 = L1 ICache Size 循环体代码大小 \text{最佳展开因子} = \sqrt{\frac{\text{L1 ICache Size}}{\text{循环体代码大小}}} 最佳展开因子=循环体代码大小L1 ICache Size

11. pragma:编译器的微观调控

作用:插入特定编译指导语句

s[op].pragma(axis, "unroll_and_jam", 4)  

常见Pragma指令

# 强制向量化  
s[op].pragma(axis, "vectorize", 8)  # 流水线并行  
s[op].pragma(axis, "software_pipeline", 3)  # 内存预取  
s[op].pragma(axis, "prefetch", A)  

架构特定优化

  • Intel CPU:
    s[op].pragma(axis, "ivdep")  # 忽略向量依赖  
    
  • NVIDIA GPU:
    s[op].pragma(axis, "ldg", 1)  # 使用__ldg指令  
    

张量计算原语

12. tensorize:硬件指令的直通车

作用:将计算模式映射到特定硬件指令

# 定义矩阵内积的Tensorize内核  
def dot_product_4x4():  # 此处定义计算规则  pass  s[matmul].tensorize(ci, dot_product_4x4)  

硬件案例

  • Intel VNNI:4x4矩阵乘指令
  • NVIDIA Tensor Core:混合精度矩阵运算
  • ARM SVE:可伸缩向量扩展

性能收益

  • 在兼容硬件上可获得10-100倍加速
  • 需要精确匹配计算模式和数据布局

高级组合原语

13. rfactor:归约计算的时空分裂

作用:将归约操作分解为多阶段计算

# 原始归约  
C = tvm.compute((n,), lambda i: tvm.sum(A[i,j], axis=j))  # 创建rfactor阶段  
_, ki = s[C].split(s[C].op.reduce_axis[0], factor=4)  
Crf = s.rfactor(C, ki)  

数学等价性
原始:
C [ i ] = ∑ j = 0 15 A [ i , j ] C[i] = \sum_{j=0}^{15} A[i,j] C[i]=j=015A[i,j]
分解后:
C r f [ i , k ] = ∑ j = 0 3 A [ i , 4 k + j ] C [ i ] = ∑ k = 0 3 C r f [ i , k ] Crf[i,k] = \sum_{j=0}^{3} A[i,4k+j] \\ C[i] = \sum_{k=0}^{3} Crf[i,k] Crf[i,k]=j=03A[i,4k+j]C[i]=k=03Crf[i,k]

优化场景

  • 提升归约操作的并行度
  • 减少原子操作冲突(GPU)

14. compute_inline:计算的时空湮灭

作用:将中间计算结果直接内联到消费者

s[B].compute_inline()  

代码变换
内联前:

B = A + 1  
C = B * 2  

内联后:

C = (A + 1) * 2  

权衡分析

  • 优点:减少内存占用,提升局部性
  • 缺点:可能增加重复计算量

架构特定原语

15. stencil:数据流动的模板

作用:定义滑动窗口式计算模式

with tvm.stencil.grid([H, W]) as [i, j]:  B[i,j] = A[i-1,j] + A[i+1,j] + A[i,j-1] + A[i,j+1]  

硬件映射

  • FPGA:生成流水线化数据流
  • GPU:映射到共享内存的滑窗缓存
  • CPU:自动生成SIMD优化代码

16. sparse:稀疏数据的压缩艺术

作用:处理稀疏张量计算

# 定义CSR格式稀疏矩阵  
indptr = tvm.placeholder((n+1,), dtype="int32")  
indices = tvm.placeholder((nnz,), dtype="int32")  
data = tvm.placeholder((nnz,), dtype="float32")  # 稀疏矩阵乘调度  
s = tvm.create_schedule([indptr, indices, data, dense])  
s.sparse_indices(indptr, indices)  

优化技巧

  • 使用行分块减少随机访问
  • 利用向量化处理非零元素
  • 案例:在Transformer模型中优化稀疏注意力计算

调试与剖析原语

17. debug:计算图的显微镜

作用:输出中间计算步骤详情

s[op].debug()  

输出示例

Compute stage:  for (i, 0, 16) {  for (j, 0, 32) {  C[i, j] = (A[i, j] + B[i, j])  }  }  

调试技巧

  • 结合TVM的Lower函数查看IR变更
  • 使用LLDB/GDB附加到编译过程

18. profile:性能的时空计量仪

作用:插入性能剖析代码

s[op].profile()  

输出信息

  • 循环迭代次数
  • 缓存命中率
  • 指令吞吐量
  • 案例:发现某个循环存在90%的缓存未命中

未来原语展望

19. auto_tensorize:AI优化AI

作用:自动匹配硬件指令模式

s.auto_tensorize(target="avx512")  

实现原理

  • 使用机器学习模型识别可优化的计算模式
  • 自动生成tensorize内核

20. quantum:量子计算接口

作用:映射到量子计算指令

s[op].quantum(gate="H", qubits=[0,1])  

前沿领域

  • 量子神经网络优化
  • 混合经典-量子调度

原语组合艺术

优化案例:三维卷积调度策略

# 定义计算  
data = tvm.placeholder((N, C, D, H, W), "float32")  
kernel = tvm.placeholder((K, C, KD, KH, KW), "float32")  
conv3d = topi.nn.conv3d_ndhwc(data, kernel)  # 创建调度  
s = tvm.create_schedule(conv3d.op)  # 分块策略  
n, d, h, w, k = conv3d.op.axis  
dn, di = s[conv3d].split(d, factor=2)  
hn, hi = s[conv3d].split(h, factor=4)  
wn, wi = s[conv3d].split(w, factor=4)  
s[conv3d].reorder(n, dn, hn, wn, di, hi, wi, k)  # 并行化  
s[conv3d].parallel(n)  # 向量化  
s[conv3d].vectorize(wi)  # 缓存优化  
AA = s.cache_read(data, "local", [conv3d])  
WW = s.cache_read(kernel, "local", [conv3d])  
s[AA].compute_at(s[conv3d], wn)  
s[WW].compute_at(s[conv3d], wn)  # 指令级优化  
s[conv3d].unroll(hi)  
s[conv3d].pragma(dn, "prefetch", AA)  

结语:调度原语的哲学

在TVM的世界里,每一个调度原语都是时空的雕塑工具。优秀的性能工程师需要兼具:

  • 微观直觉:理解每个原语在硬件底层的映射
  • 宏观视野:把握多个原语之间的相互作用
  • 艺术感知:在约束条件下找到优雅的优化路径

正如计算机图形学中的渲染方程,调度优化也是一个积分过程:

最优性能 = ∫ 硬件空间 ∏ 原语 f ( x ) d x \text{最优性能} = \int_{\text{硬件空间}} \prod_{\text{原语}} f(x) \, dx 最优性能=硬件空间原语f(x)dx

愿每一位读者都能在TVM的调度世界中,找到属于自己的优化之美。

相关文章:

TVM调度原语完全指南:从入门到微架构级优化

调度原语 在TVM的抽象体系中&#xff0c;调度&#xff08;Schedule&#xff09;是对计算过程的时空重塑。每一个原语都是改变计算次序、数据流向或并行策略的手术刀。其核心作用可归纳为&#xff1a; 优化目标 max ⁡ ( 计算密度 内存延迟 指令开销 ) \text{优化目标} \max…...

c语言进阶(简单的函数 数组 指针 预处理 文件 结构体)

c语言补充 格式 void函数头 {} 中的是函数体 sum函数名 &#xff08;&#xff09; 参数表 #include <stdio.h>void sum(int begin, int end) {int i;int sum 0;for (i begin ; i < end ; i) {sum i;}printf("%d到%d的和是%d\n", begin, end, sum); …...

终极版已激活!绿话纯净,打开即用!!!

今天我想和大家聊聊一个非常实用的工具——视频转换大师最终版。 视频转换大师终极版&#xff0c;堪称一款全能型的视频制作神器&#xff0c;集视频转换与编辑功能于一体。它搭载的视频增强器技术&#xff0c;能够最大限度地保留原始视频质量&#xff0c;甚至还能实现质量的进…...

Vue.js 什么是 Composition API?

Vue.js 什么是 Composition API&#xff1f; 今天我们来聊聊 Vue 3 引入的一个重要特性&#xff1a;组合式 API&#xff08;Composition API&#xff09;。如果你曾在开发复杂的 Vue 组件时感到代码难以维护&#xff0c;那么组合式 API 可能正是你需要的工具。 什么是组合式 …...

MySQL高可用

一、mysql路由 1.利用路由器的连接路由特性&#xff0c;用户可以编写应用程序来连接到路由器&#xff0c;并令路由器使用响应的路由策略来处理连接来使其连接到正确的mysql数据库服务器 2.mysql route的部署方式 需要在所有数据库主机之外再打开一台主机mysql-router 配置mysql…...

30.Word:设计并制作新年贺卡以及标签【30】

目录 NO1.2 NO3邮件合并-信函 NO4邮件合并-标签​ NO1.2 另存为/F12&#xff1a;考生文件夹&#xff1a;Word.docx布局→页面设置对话框→页边距&#xff1a;上下左右→纸张&#xff1a;宽度/高度&#xff08;先调页边距&#x1f197;&#xff09;设计→页面颜色→填充效果→…...

Flink2支持提交StreamGraph到Flink集群

最近研究Flink源码的时候&#xff0c;发现Flink已经支持提交StreamGraph到集群了&#xff0c;替换掉了原来的提交JobGraph。 新增ExecutionPlan接口&#xff0c;将JobGraph和StreamGraph作为实现。 Flink集群Dispatcher也进行了修改&#xff0c;从JobGraph改成了接口Executio…...

大模型本地化部署(Ollama + Open-WebUI)

文章目录 环境准备下载Ollama模型下载下载Open-WebUI 本地化部署的Web图形化界面本地模型联网查询安装 Docker安装 SearXNG本地模型联网查询 环境准备 下载Ollama 下载地址&#xff1a;Ollama网址 安装完成后&#xff0c;命令行里执行命令 ollama -v查看是否安装成功。安装成…...

C++哈希(链地址法)(二)详解

文章目录 1.开放地址法1.1key不能取模的问题1.1.1将字符串转为整型1.1.2将日期类转为整型 2.哈希函数2.1乘法散列法&#xff08;了解&#xff09;2.2全域散列法&#xff08;了解&#xff09; 3.处理哈希冲突3.1线性探测&#xff08;挨着找&#xff09;3.2二次探测&#xff08;跳…...

IME关于输入法横屏全屏显示问题-Android14

IME关于输入法横屏全屏显示问题-Android14 1、输入法全屏模式updateFullscreenMode1.1 全屏模式判断1.2 全屏模式布局设置 2、应用侧关闭输入法全屏模式2.1 调用输入法的应用设置flag2.2 继承InputMethodService.java的输入法应用覆盖onEvaluateFullscreenMode方法 InputMethod…...

网络工程师 (11)软件生命周期与开发模型

一、软件生命周期 前言 软件生命周期&#xff0c;也称为软件开发周期或软件开发生命周期&#xff0c;是指从软件项目的启动到软件不再被使用为止的整个期间。这个过程可以细分为多个阶段&#xff0c;每个阶段都有其特定的目标、任务和产出物。 1. 问题定义与需求分析 问题定义…...

【人工智能】基于Python的机器翻译系统,从RNN到Transformer的演进与实现

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 机器翻译(Machine Translation, MT)作为自然语言处理领域的重要应用之一,近年来受到了广泛的关注。在本篇文章中,我们将详细探讨如何使…...

网络工程师 (12)软件开发与测试

一、软件设计 &#xff08;一&#xff09;定义与目的 软件设计是从软件需求出发&#xff0c;设计软件的整体结构、功能模块、实现算法及编写代码的过程&#xff0c;旨在确定系统如何完成预定任务。其目标是确保目标系统能够抽象、普遍地完成预定任务&#xff0c;并为后续的软件…...

3.Spring-事务

一、隔离级别&#xff1a; 脏读&#xff1a; 一个事务访问到另外一个事务未提交的数据。 不可重复读&#xff1a; 事务内多次查询相同条件返回的结果不同。 幻读&#xff1a; 一个事务在前后两次查询同一个范围的时候&#xff0c;后一次查询看到了前一次查询没有看到的行。 二…...

Python字典详解:从入门到实践

Python字典详解&#xff1a;从入门到实践 字典&#xff08;Dictionary&#xff09;是Python中最重要且最常用的数据结构之一。本文将深入讲解字典的特性、操作方法和实际应用案例。 1. 字典简介 字典是可变的、无序的键值对集合&#xff0c;使用{}创建。每个元素由key: valu…...

91,【7】 攻防世界 web fileclude

进入靶场 <?php // 包含 flag.php 文件 include("flag.php");// 以高亮语法显示当前文件&#xff08;即包含这段代码的 PHP 文件&#xff09;的内容 // 方便查看当前代码结构和逻辑&#xff0c;常用于调试或给解题者提示代码信息 highlight_file(__FILE__);// 检…...

41【文件名的编码规则】

我们在学习的过程中&#xff0c;写出数据或读取数据时需要考虑编码类型 火山采用&#xff1a;UTF-16 易语言采用&#xff1a;GBK php采用&#xff1a;UTF-8 那么我们写出的文件名应该是何种编码的&#xff1f;比如火山程序向本地写出一个“测试.txt”&#xff0c;理论上这个“测…...

蓝桥杯备赛经验帖

蓝桥杯备赛经验帖 作者&#xff1a;blue 时间&#xff1a;2025.2.1 文章目录 蓝桥杯备赛经验帖1.为什么有这篇文章2.赛制3.比赛流程4.如何准备5.其他建议6.一些感悟 1.为什么有这篇文章 ​ 笔者近期发现&#xff0c;观看我写的两道第十五届蓝桥杯题解的人数逐渐增多&#xf…...

一文大白话讲清楚webpack基本使用——17——Tree Shaking

文章目录 一文大白话讲清楚webpack基本使用——17——Tree Shaking1. 建议按文章顺序从头看&#xff0c;一看到底&#xff0c;豁然开朗2. 啥叫Tree Shaking3. 什么是死代码&#xff0c;怎么来的3. Tree Shaking的流程3.1 标记3.2 利用Terser摇起来 4. 具体使用方式4.1 适用前提…...

【C++ 区间位运算】3209. 子数组按位与值为 K 的数目|2050

本文涉及知识点 位运算、状态压缩、枚举子集汇总 LeetCode3209. 子数组按位与值为 K 的数目 给你一个整数数组 nums 和一个整数 k &#xff0c;请你返回 nums 中有多少个子数组 满足&#xff1a;子数组中所有元素按位 AND 的结果为 k 。 示例 1&#xff1a; 输入&#xff1a…...

椭圆曲线密码学(ECC)

一、ECC算法概述 椭圆曲线密码学&#xff08;Elliptic Curve Cryptography&#xff09;是基于椭圆曲线数学理论的公钥密码系统&#xff0c;由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA&#xff0c;ECC在相同安全强度下密钥更短&#xff08;256位ECC ≈ 3072位RSA…...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器

——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的​​一体化测试平台​​&#xff0c;覆盖应用全生命周期测试需求&#xff0c;主要提供五大核心能力&#xff1a; ​​测试类型​​​​检测目标​​​​关键指标​​功能体验基…...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件

在选煤厂、化工厂、钢铁厂等过程生产型企业&#xff0c;其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进&#xff0c;需提前预防假检、错检、漏检&#xff0c;推动智慧生产运维系统数据的流动和现场赋能应用。同时&#xff0c;…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile&#xff0c;新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序

一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战

“&#x1f916;手搓TuyaAI语音指令 &#x1f60d;秒变表情包大师&#xff0c;让萌系Otto机器人&#x1f525;玩出智能新花样&#xff01;开整&#xff01;” &#x1f916; Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制&#xff08;TuyaAI…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)

本期内容并不是很难&#xff0c;相信大家会学的很愉快&#xff0c;当然对于有后端基础的朋友来说&#xff0c;本期内容更加容易了解&#xff0c;当然没有基础的也别担心&#xff0c;本期内容会详细解释有关内容 本期用到的软件&#xff1a;yakit&#xff08;因为经过之前好多期…...

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题

分区配置 (ptab.json) img 属性介绍&#xff1a; img 属性指定分区存放的 image 名称&#xff0c;指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件&#xff0c;则以 proj_name:binary_name 格式指定文件名&#xff0c; proj_name 为工程 名&…...

基于Java+MySQL实现(GUI)客户管理系统

客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息&#xff0c;对客户进行统一管理&#xff0c;可以把所有客户信息录入系统&#xff0c;进行维护和统计功能。可通过文件的方式保存相关录入数据&#xff0c;对…...

Web中间件--tomcat学习

Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机&#xff0c;它可以执行Java字节码。Java虚拟机是Java平台的一部分&#xff0c;Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...