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

自由学习记录(108)

真正高效的 CUDA 程序,核心在于让数据尽可能留在 GPU 上计算完成再回传

PCIe 传输,过程慢且昂贵。

性能优化点

  1. 尽量减少 Host↔Device 传输次数。

  2. 使用 pinned memory(页锁定内存) 提高 PCIe 传输速度。

  3. 使用 Unified Memory(统一内存) 让CUDA自动管理数据迁移。

  4. 尽量在 GPU 内部复用数据,避免频繁回传到 CPU。

// 1. 在 CPU 上分配主机内存
float *h_data = (float*)malloc(size);

// 2. 在 GPU 上分配设备内存
float *d_data;
cudaMalloc(&d_data, size);

// 3. 从 CPU 内存传输到 GPU 内存
cudaMemcpy(d_data, h_data, size, cudaMemcpyHostToDevice);

// 4. CPU 调用 kernel(GPU 执行)
myKernel<<<grid, block>>>(d_data);

// 5. GPU 执行完后传回结果
cudaMemcpy(h_data, d_data, size, cudaMemcpyDeviceToHost);

// 6. 释放内存
cudaFree(d_data);
free(h_data);

数据在两者之间来回传输”的运行机制

https://www.youtube.com/watch?v=G-EimI4q-TQ

CPU 与 GPU 的内存体系是物理分离的

  1. CPU内存

    • 使用系统主内存(通常是DDR4/DDR5)。

    • 存放程序指令、控制逻辑、数据初始化结果。

    • 由操作系统与CPU调度器统一管理。

  2. GPU内存

    • 使用显存(GDDR6或HBM等高带宽内存)。

    • 专门为并行计算线程存放输入输出数据、纹理、矩阵、缓冲区等。

    • 不与CPU共享地址空间(除非使用统一内存机制)。

数据传输的关键路径

  1. 传输通道:PCI Express(PCIe)总线

    • 负责在CPU内存与GPU显存之间移动数据

    • 带宽远低于GPU显存内部带宽,例如:

      • GPU内部带宽:几百 GB/s~1 TB/s。

      • PCIe 4.0 ×16 带宽:约 32 GB/s

    • 因此数据传输是整个异构计算流程的主要瓶颈

  2. 传输方向与阶段

    • Host → Device:CPU把数据从主内存拷贝到GPU显存(例如训练前加载模型权重)。

    • Device → Device:GPU内部线程之间共享或复制数据(通过共享内存或全局内存)。

    • Device → Host:GPU计算结束后,把结果传回CPU(例如渲染输出帧或推理结果)。

1. 异构模型概念

  • CPU = Host(主机):运行普通程序,负责整体调度与数据管理。

  • GPU = Device(设备):运行并行计算部分,称为“kernel”。

  • 执行流程:CPU启动(launch)GPU上的kernel,GPU执行完再返回结果。
    重点:CPU是指挥者,GPU是执行者。


2. CUDA编程框架

  • NVIDIA提供的CUDA允许一个程序包含CPU端与GPU端的部分。

  • 编译器会将同一个源文件分离成两套代码:

    • CPU部分(host code)

    • GPU部分(device code)
      重点:CUDA的核心是让开发者在同一代码中写出异构执行逻辑。


3. 内存结构与数据传输

  • CPU与GPU各自拥有独立的内存(DRAM)。

  • CPU控制整个过程,负责分配GPU内存与数据搬运。

  • 数据需要通过总线(如PCIe)在CPU与GPU间来回传递。
    重点:数据传输是性能瓶颈之一。

这页PPT的重点是讲解异构编程模型(Heterogeneous Programming Model),即CPU与GPU协同工作的方式。

老师会着重讲CPU 与 GPU 的设计目标差异:延迟(Latency) vs 吞吐量(Throughput)

重点内容如下:

  1. 延迟 (Latency)
    • 定义:完成单个任务所需的时间。
    • 指标:秒或毫秒。
    • CPU优化方向:减少单个任务完成时间。
    • 场景:系统响应、逻辑判断、顺序执行的代码。

  1. 吞吐量 (Throughput)
    • 定义:单位时间内完成的任务数量
    • 指标:jobs/hour、pixels/s、GFLOPS。
    • GPU优化方向:最大化同时处理的任务数。
    • 场景:图形渲染、神经网络推理、大规模矩阵运算。

  1. 核心对比逻辑
    • CPU追求快地完成一个任务
    • GPU追求同时完成更多任务
    • 所以CPU设计强调复杂控制逻辑、缓存层次;GPU强调并行流水线与计算密度。

总结一句话:这页PPT的重点是说明——

“CPU专注降低单任务延迟,GPU专注提升总体吞吐量。”

CPU和GPU都由晶体管构成,但晶体管的功能分配不同:

  1. 逻辑控制型晶体管(Control Transistor)
    • 负责判断、分支、调度。
    • 在CPU中比例高,用于执行复杂指令和控制流。

  1. 算术执行型晶体管(Compute Transistor)
    • 用于加法器、乘法器、ALU、FPU。
    • GPU中比例极高,因为主要执行并行算术操作。

  1. 存储缓存型晶体管(Memory/Cache Transistor)
    • 构成寄存器、L1/L2/L3缓存、显存控制器等。
    • CPU缓存层次多,因此在此部分耗费大量面积。
    • GPU更多依赖外部显存(VRAM),片上缓存较少。

  1. 互连与控制通路型晶体管(Interconnect / Bus Control)
    • 实现数据在各单元之间的高速传输。
    • CPU内部互连复杂,用于协调多级缓存和执行单元。
    • GPU为高带宽设计,连接大量计算核心(SM或CU)。

CPU的晶体管分配约有以下特点:

  1. 控制逻辑部分占比高

    • 包含分支预测器(Branch Predictor)、乱序执行单元(Out-of-Order Execution Unit)、指令调度器(Scheduler)、寄存器重命名表(Register Renaming Table)。

    • 这些模块让CPU可以处理分支频繁、指令依赖复杂的程序。

  2. 缓存层次结构占用大量面积

    • L1/L2/L3缓存层次多,容量大,保证低延迟数据访问。

    • 每个核心都带有独立或共享的Cache,用于减少主存访问瓶颈。

  3. 算术逻辑单元(ALU)数量相对少

    • 因为大量面积和功耗都给了控制电路和缓存,CPU每个核心的执行单元数有限。

  • CPU 把晶体管大量分配给控制和缓存,以支持灵活的分支和复杂任务。

  • GPU 减少控制电路,用更多晶体管堆计算核心来提升并行吞吐。

这会引出两个重点:

  1. 为什么CPU适合通用任务而GPU擅长并行计算。

  2. 这种架构差异导致的功耗与性能对比。

重点在CPU与GPU硬件架构的对比逻辑。老师大概率会讲以下几个核心点:

  1. 控制复杂度差异

    • CPU的控制逻辑复杂(Complex control HW),能灵活调度各种任务。

    • GPU的控制逻辑简单(Simpler control HW),控制单元占比低,更多晶体管用于计算。

  2. 优劣取舍

    • CPU:灵活、通用、适合少量复杂任务,但功耗高。

    • GPU:高并行度、功耗更低,但编程受限,适合大规模数据并行。

  3. 背后机制

    • CPU适合逻辑分支多、指令不规则的任务。

    • GPU适合SIMT模型(Single Instruction, Multiple Threads)的大规模数据运算。

内存带宽的利用,?意思是说原本的是每个线程独立的去取,然后就多次访问内存,而之后是直接把读取一大块比如四分之一块进显存,此时所有gpu同时去里面拿来算,减少了反复访问的时间浪费(但是增加了显存带宽的占用,所以选择的T如果太大,在实际使用中会极大占用其他的程序需要使用的显存,,所以T的选择需要一定的随机应变?)

ChatGPT said:

你理解方向完全正确,只需要我帮你精确化几个关键层次。

每个线程块把需要的一小块 A、B 从 global memory 一次性拷入 shared memory(片上高速 SRAM,延迟约是 global 的 1/100)。

cache tiling 的思路

把矩阵分成小块(tile 或 block),让每次运算只在缓存/共享内存中处理一块完整 tile

for (ii = 0; ii < N; ii += T)for (jj = 0; jj < N; jj += T)for (kk = 0; kk < N; kk += T)// 在 T×T 子块上做完整乘法for (i = ii; i < ii+T; i++)for (j = jj; j < jj+T; j++)for (k = kk; k < kk+T; k++)C[i][j] += A[i][k] * B[k][j];

每个 tile 大小 T 选得刚好能装进缓存或共享内存。
优势:

  • A、B 的一部分被载入一次,多次复用;

  • 内存带宽负担减少;

  • 计算单元利用率提升。

cache tiling 是一种优化手段,不是新算法。
它保持结果不变,但极大改善了访问模式与性能。

在 GPU 上,“cache tiling” 演化为:

  • 每个线程块(block)处理一个 tile;

  • 每个线程负责 tile 内的一个或数个元素;

  • tile 数据放在 __shared__ 内存

  • 线程块间用同步 (__syncthreads()) 协调。

所以 CUDA 的“tiled matrix multiplication” 实际上是“cache tiling”思想在 GPU 上的并行实现。

  1. ls → 列出当前目录内容:

    cacheTiling LICENSE matrixMul README.md vectorAdd

    说明当前目录名是 from_scratch,里面有这些子目录。

  2. cd cacheTiling → 进入名为 cacheTiling 的子文件夹。

  3. vim matrix_mul.cu → 在该目录下用 Vim 打开(或新建)名为 matrix_mul.cu 的 CUDA 源文件。

所以你理解正确:
这几步是在项目 from_scratch 目录中进入 cacheTiling 子文件夹,并在里面新建/编辑 CUDA 程序文件 matrix_mul.cu

它确保 GPU 的并行算法在数学意义上等价于 CPU 串行算法。

main() 里:

  1. 调用 CUDA 核函数;

  2. cudaDeviceSynchronize() 等待 GPU 执行完;

  3. 调用 verify_result() 验证。

GPU 内核不会抛异常或中断逻辑错误,只会返回“某些值”。
验证函数是唯一能证明计算逻辑正确的手段,尤其在调试:

  • tile 大小、共享内存索引、边界条件 都可能出错;

  • GPU 的并行执行次序不确定,逻辑错误可能隐蔽;

  • CPU 结果可视为“真值表”,用于对比验证。

条件是否可运行说明
N 是 tile 尺寸的倍数✅ 完全对齐
N 不是 tile 尺寸的倍数✅ 需边界判断
tile 尺寸(blockDim)固定✅ 任意 N 可运行,只影响效率

这段 CUDA tiled 矩阵乘法代码可以用于任意 N(不必是 4 或 16 的倍数),因为它在循环里专门写了:

 

for (int i = 0; i < (N + dim - 1) / dim; i++)

这里 (N + dim - 1) / dim 是一种常见的向上取整技巧,用于应对矩阵尺寸不是 tile 大小整数倍的情况。

所以:

  • 如果 N=32、64、128 → 刚好整除,所有 tile 都满。

  • 如果 N=30、45、50 → 最后一个 tile 只覆盖部分区域,线程会读到边界之外的索引,这种情况要配合边界判断:

     

    if (row < N && col < N) c[row * N + col] = sum;

    否则会访问越界。

想象 A 和 B 是两本超大的表格。

  • 你(block)带着一个 16×16 的小“放大镜”去看一部分。

  • 每次你移动放大镜(tile),抄下对应部分做乘法。

  • 一直移动,直到把整行整列看完。

  • 最后每个位置得出 C 的一个元素。

每次循环,加载 A 和 B 的相邻 tile。

每个线程负责 tile 中一个像素的计算。

累积到最终结果 C。

矩阵太大,显存访问太慢,如果直接让每个线程从全局内存读整行整列,会反复访问相同数据 → 极低效率

术语含义作用
tile矩阵的一小块(例如16×16)优化内存访问与共享计算
blockGPU 的线程块(执行单元)通常负责一个 tile
flatten (压平)把二维矩阵变成一维数组仅是存储方式,与 tile 无关

for (int i = 0; i < (N + dim - 1) / dim; i++) {
    A[ty * dim + tx] = a[row * N + i * dim + tx];
}
这一段是:

将大矩阵 a 的不同 tile 段拷贝进共享内存 A。

i 控制“tile 在矩阵中的水平移动次数”。

因此:

“across the length of the grid” 指沿着矩阵的宽度(列方向)不断移动 tile 进行分块加载计算,
而不是把矩阵压平。

也就是说:

  • tile ≈ “本线程块正在处理的矩阵局部块”

  • “move the tile across the length of the grid” 表示:

    在循环中,线程块不断加载矩阵的不同部分(每次移动一个 tile 的宽度),重复累积计算,直到整个矩阵的乘法完成。

  • Matrix tile:矩阵被划分成若干个固定大小的小方块(如 16×16)。

  • 每个线程块(block)负责计算一个 tile 的结果。

  • 共享内存(__shared__)中存放的是当前 block 对应的 tile 的局部数据。

典型的 CUDA 矩阵乘法 (tiled matrix multiplication)
tile 在这里不是把矩阵“压平成一维数组”,而是指 矩阵被分块(tile/block)成小矩阵子块 的思想。

在 CUDA 里:

  • block(线程块) 是执行的基本调度单位,由多个线程组成。

  • grid(网格) 是由若干 block 构成的整体。

  • chunk 不是 CUDA 的正式术语,它通常出现在代码层或内核设计中,表示开发者人为划分的一段数据或任务片段

也就是说,chunk 通常指“要处理的数据的一部分”,而不是 GPU 的硬件/调度单位。

int chunkSize = 256;
int idx = threadIdx.x + blockIdx.x * blockDim.x;
int start = idx * chunkSize;

这里每个线程负责处理一个 chunk 数据,但这只是开发者定义的概念,CUDA 自身并不知道什么是 chunk。

总结:

  • block:CUDA 官方定义的线程调度单元。

  • chunk:开发者自定义的数据切片,不是 CUDA 的执行单位。

矩阵乘法是二维数据的操作,但它的计算结构天然是三维的。张量化是把这个三维关系显式化,以便从代数层面优化乘法次数。

算法优化点:张量分解

  • Strassen 等算法的核心,就是寻找把这个三维张量 TTT 分解成若干个秩为1的张量之和(每个秩1张量对应一次乘法)。

  • 减少所需的秩(即最少乘法次数) = 优化矩阵乘法。

  • 所以后续算法研究方向就是“寻找矩阵乘法张量的最小秩分解”。

张量分解(Tensor Decomposition)

  • 矩阵乘法可以视为三维张量乘法。

  • 不同算法对应于对这个张量的不同分解方式。

  • 比如 Coppersmith-Winograd 用代数几何技巧在更高维度上找到稀疏分解,从而降低指数。

Strassen 是 2×2 分块。

  • 后续算法(如 Schönhage、Coppersmith-Winograd)把更大矩阵看作高维张量,通过张量分解找到比“7乘法规则”更优的组合模式

  • 这些算法在理论上继续减少乘法数量,不改变 2×2 层内部公式,而是复用公式间的中间结果。

  • 普通 2×2 矩阵乘法需要 8 次乘法 + 4 次加法。

  • Strassen 算法把它优化为 7 次乘法 + 18 次加法。

  • 虽然加法数量多了,但乘法的代价远高于加法(在硬件上乘法≈数十倍慢)。

在递归层面:
当矩阵被不断分块时,节省的一次乘法会在每层递归放大。
例如 O(n3) 的算法经过这种优化,复杂度降为 O(n2.807)。
即使加法更多,整体运行时间随矩阵规模增大反而更快。

减少高代价的乘法、增加低代价的加法是典型的计算权衡。Strassen 算法在大规模矩阵中能显著提速。

https://www.youtube.com/watch?v=sZxjuT1kUd0

AI 的贡献
DeepMind 的 AlphaTensor 使用强化学习,在算子空间中自动搜索矩阵乘法的最优分解方式,相当于在“如何切片、怎样重组”上找到了比人类更高效的策略。

可视化类比
你的“切成多片的面包”类比接近于 张量分解(tensor decomposition):

  • 每层(切片)是矩阵。

  • 算法寻找在这些层之间最有效的组合方式,使得最终计算结果正确但乘法更少。

Tensor ≠ 简单的立方体矩阵

  • 矩阵是二维数组(两个索引:行、列)。

  • Tensor 是更一般的多维数组,可以是 3D(像立方体)、4D(视频帧序列)、甚至更高维度。

  • 每一维代表一种“轴”,比如图像张量 (Height, Width, Channels)。

但是2022年的时候,有新的人提出新的理论,但是要求矩阵中的数字全是0和1,并且生效的范围是4x4的矩阵相乘

二x二阶的矩阵乘法,最多只能7,并且不可能6,后面被另一个人证明了

一个CPU核心可以至少运行一个线程,但现代核心通常支持同时运行多个线程,这取决于是否启用了超线程(Hyper-Threading,SMT)

结构:

  • 无超线程核心:1核 = 1硬件线程。

  • 有超线程核心:1核 = 2硬件线程(每个线程独立调度指令,共享执行单元和缓存)。

例:

  • Intel Core i5-12400:6核12线程 → 每核2线程。

  • AMD Ryzen 5 7600:6核12线程 → 同样SMT。

总结:

一个物理核心可运行1~2个线程(取决于是否启用超线程)。

精度名称位数常用场景特点
FP64双精度64 bit科学计算、数值仿真精度最高,速度最慢
FP32单精度32 bit游戏渲染、通用GPU计算平衡精度与性能
FP16半精度16 bit深度学习推理、AI训练精度低但速度快、能耗低

还有这东西,之前会用的上,但现在没什么劲去用了

https://mermaid.js.org/#/

用途:衡量 GPU、CPU、TPU 等计算硬件的浮点计算吞吐能力。

  • 1 Tflop = 10¹² 次浮点运算/秒。

  • 通常分为 FP32(单精度)、FP16(半精度)、FP64(双精度)等指标,分别表示在不同精度下的理论峰值性能。

示例:

  • NVIDIA GeForce RTX 4090 单精度 ≈ 82 Tflops。

  • A100 Tensor Core GPU 半精度 ≈ 312 Tflops。

区别:

  • 理论值 = 计算单元数 × 每周期指令数 × 频率。

  • 实际性能 依赖内存带宽、指令混合、瓶颈等,不会达到理论 Tflops。

“Teraflops” 是 trillion floating-point operations per second 的缩写,即 每秒一万亿次浮点运算

主要内容

  • 作者:Ian Buck、Tim Foley、Daniel Horn、Jeremy Sugerman、Kayvon Fatahalian、Mike Houston、Pat Hanrahan,均隶属 Stanford University 计算机科学系。 Stanford Graphics+2Stanford Graphics+2

  • 时间/出版:于 2004 年发表于 SIGGRAPH / ACM Transactions on Graphics。 ACM Digital Library+1

  • 核心目标:提供 Brook 这个编程环境(包括扩展 C 语言、编译器和运行时系统),将 GPU (图形处理单元)视作“流处理协处理器”用于通用计算。论文中分析了在何种算法和何种条件下 GPU 相比 CPU 更具优势。 Stanford Graphics+1

  • 方法/贡献包括:

    • 定义流(streams)、核(kernels)、归约(reductions)等抽象以适配 GPU 计算模型。 Stanford Graphics+1

    • 虚拟化 GPU 硬件限制(如输出数、纹理大小、用户数据结构)以提升可移植性。 Stanford Graphics

    • 提出简单成本模型(data-transfer + kernel cost)以判断 GPU 计算是否优于 CPU。 Stanford Graphics

    • 实验评估:包括 SAXPY、SGEMV、图像分割、FFT、光线追踪等代表性应用。结果显示:在某些情况下,使用 Brook 的 GPU 实现比 CPU 快至 7×。 Stanford Graphics

是否为 NVIDIA 员工撰写?
不。根据论文作者信息和其所属机构:作者来自 Stanford University。论文并未列出 NVIDIA 作为作者单位。故该论文并非由 NVIDIA 员工主导撰写

https://www.youtube.com/watch?v=pPStdjuYzSI

https://graphics.stanford.edu/papers/brookgpu/brookgpu.pdf

官方 PDF 《CUDA Programming Guide Version 1.0》标注版本 1.0 日期为 2007-06-23。NVIDIA Developer Download+1

NVIDIA 成立早(1993 年)但 CUDA 平台在 2007 年才作为通用计算平台发布。

TPU 直接取消指令概念,只让数据按固定路径流过 → 零指令解码成本

  • 电路面积(控制单元约占核心面积 30%)

  • 时钟周期(控制器延迟不可省)

  • 功耗(控制信号切换频繁)

主要的优势比较参数,

GPU 每个周期都要做:取指令 → 解码 → 分派 → 执行 → 写回

即便只是做 A*B+C,也必须经过完整的指令路径。

这些「控制逻辑」虽然在架构上看是辅助模块,但在硬件层面占用大量晶体管、功耗与时钟周期。去掉它们,等于把电路从“智能机器”变成“纯算机器”,差距是数量级的。

无需控制器、指令解码、缓存协调。

GPU 最初是为图形渲染而设计的,核心逻辑是 SIMT(Single Instruction, Multiple Thread)
也就是上万个小核心(CUDA Core)并行执行相同指令,适合像素、粒子、顶点这种可并行任务。

这种设计的优点:

  • 通用:几乎任何可并行的任务都能跑。

  • 可编程:程序员能写任意逻辑(条件分支、循环、访存控制)。

但也带来几个致命问题:

  • 调度复杂:需要线程分配、寄存器分配、Warp 同步。

  • 访存不规律:不同线程访问显存不连续时性能急降。

  • 能耗高:指令调度、寄存器重命名、缓存一致性都会耗电。

→ GPU 擅长“宽而灵活的并行”,不是“死板而极致的矩阵流计算”。

差距确实大。出现 TPU 这种“重新设计”的硬件,是因为 GPU 的可编程通用性带来了结构性浪费,而在深度学习这种固定运算模式下,这种浪费变成瓶颈。

特性GPUTPU
可编程性高(通用 GPGPU)低(固定硬件路径)
通用任务游戏、科学计算、物理仿真几乎无优势
深度学习矩阵计算强,但需调度和显存管理极强,直接硬件流水执行
延迟稍高极低
成本与生态商业化成熟仅云端可用(Google Cloud TPU)

TPU:

  • 主要由一个或多个 Systolic Array(典型如 128×128)组成。

  • 该阵列每个时钟周期完成固定矩阵乘加:

    C=A×B+C
  • 没有通用逻辑单元,不能自由编程。

  • 更像“专用计算电路”(ASIC),只为神经网络线性代数服务。

GPU:

  • 内部是数千个可编程「核心」(CUDA Core)。

  • 每个核心能执行各种浮点、逻辑、分支操作。

  • 张量计算只是 GPU 功能的一部分(NVIDIA 通过 Tensor Core 扩展支持)。

16-bit floating point data type

Bfloat16 ( bf16 ) is a 16-bit floating point data type based on the IEEE 32-bit single-precision floating point data type ( f32 ). Both bf16 and f32 have an 8-bit exponent. However, while f32 has a 23-bit mantissa, bf16 has only a 7-bit one, keeping only the most significant bits.

cl.exe
代表 C Language 编译器(C + L)。

Visual Studio C++ 工具链有两个目标平台:

架构对应路径主要用途
x86Hostx86\x86\cl.exe旧 32 位项目
x64Hostx64\x64\cl.exeCUDA、现代桌面程序、游戏引擎等

你当前的 where cl 结果:

D:\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\bin\Hostx86\x86\cl.exe

说明只有 32 位工具链,没有 64 位版本。

Unity 自己有 .unity 场景文件和 .meta 索引系统,所有资源与脚本都受 UnityEditor 管理。
但当 Unity 需要与 Visual Studio 协同时(例如脚本编辑、调试),它会自动生成 .sln.csproj 文件来告诉 VS:

“这里是一个 Unity C# 项目,脚本都在这些目录,编译参数如下。”

也就是说:

  • .sln 对 Unity 来说是 IDE 交互层

  • 对 Visual Studio 来说是 项目构建层

.sln 在 Visual Studio 的意义

  • 代表一个解决方案(Solution),相当于一个项目集合。

  • 内部列出若干 .vcxproj(C++ 工程)或 .csproj(C# 工程)。

  • 控制:

    • 源码文件位置

    • 构建目标(Debug/Release)

    • 依赖库路径

    • 使用的编译器与参数

Visual Studio 打开 .sln 就能重建整个工程环境。

为什么看起来像“直接执行”

当你在 VS 里点击“运行”(或按 F5):

  • VS 先解析 .sln

  • 调用 nvcc 编译 CUDA 源码;

  • 链接生成 .exe

  • 自动启动该 .exe
    因此看起来像是 “.sln 能直接执行”。

实际上 .sln 只是工程描述文件,执行的是编译后生成的二进制。

http://www.dtcms.com/a/517798.html

相关文章:

  • 网站底部 设计乐至建设局网站
  • 外贸网站建设及推广电子商务网站建设维护有没有欺骗
  • 织梦cms传播公司网站模板域名建议网站
  • 仿win8 网站西宁网站制作 青
  • 郑州门户网站开发wordpress技术
  • 网站开发跟网页制作要基于wordpress开发
  • 湘潭房产网站建设青州网站建设优化推广
  • 优选算法(滑动窗口)
  • 枣强网站建设公司php网站建设开发
  • 网站开发文献资料销售口才900句
  • 南昌电商购物网站开发工商营业执照官网
  • 做网站需要什么技术河北住房与城乡建设厅网站
  • 韶关市建设局网站什么网站可以在线做考教师岗位的题
  • Java——static关键字
  • 盐城网站定制网站模板兼容手机
  • 企业网站改版方案中国最顶尖的广告设计公司
  • 济宁祥云网站建设长春建站优化加徽信xiala5
  • 成都 商业网站建设大连金普新区
  • 建设网站有什么原则前端开发可以做网站运营吗
  • 网站开发如何适应手机现实要求银川市建设厅网站
  • 基于php网站开发步骤用ssh做的网站
  • 网站制作包括数据库吗短剧cps分销平台官网
  • CAN总线错误类别
  • 滕州网站建设 助企网络电子商务网站计划书
  • 茶叶公司网站的建设网页升级紧急通知新域名
  • ASP 语法详解
  • 找人帮你做PPT的网站专业做网站广州
  • 新网站如何做百度收录wordpress 宕机
  • 小迪安全v2023学习笔记(一百三十六讲)—— Win系统权限提升篇计算机用户进程注入令牌窃取服务启动远程管理
  • 做阿里网站商丘购物网站开发设计