【TA-霜狼_may-《百人计划》】图形2.7.2 GPU硬件架构概述

  • @[TOC](【TA-霜狼_may-《百人计划》】图形2.7.2 GPU硬件架构概述
  • GPU是什么
  • GPU物理架构
    • 发展史
    • Tesla架构
    • Fermi架构
    • Maxwell架构
    • Turing架构
  • 渲染总览
  • 一些GPU技术
    • Early-Z
    • SIMD和SIMT
    • co-issue
  • CPU与GPU
    • CPU-GPU异构架构
    • GPU资源机制
    • GPU资源管理模型(分离式架构)
    • CPU-GPU数据流
  • Shader运行机制
    • 以漫反射为例
    • GPU Context和延迟
  • Geforce RTX 2060的扩展验证
  • 总结

GPU是什么

GPU全程是Graphics Processing Unit,图形处理单元。它的功能最初与名字一致,是专门用于绘制图像和处理图元数据的特定芯片,后来渐渐加入了其他很多功能。

GPU是显卡(Video card)最核心的部分,显卡还包括散热器、通讯元件、各类插槽等等。

GPU物理架构

得益于纳米工艺的引入,GPU可以将数以亿记的晶体管和电子器件集成在一个小小的芯片内。显卡不能单独工作,需要装载在主板上,结合CPU、内存、显存、显示器等硬件设备,组成完整的PC机。

发展史


TPC:Texture / Processor Cluster,纹理处理簇
SM:Stream Multiprocessor,流多处理器
SP:Streaming Processor,流处理器
SFU:Special Function Unit,特殊函数单元
MT Issue:Multithreaded isstruction fetch

Tesla架构


Fermi架构


Maxwell架构


Turing架构




渲染总览

  1. 程序通过图形API(DX、GL、WEBGL)发出drawcall指令,指令会被推送到驱动程序,驱动会检查指令的合法性,然后会把指令放到GPU可以读取的Pushbuffer中。
  2. 经过一段时间或者显式调用flush指令后,驱动程序把Pushbuffer的内容发送给GPU,GPU通过主机接口(Host Interface)接受这些命令,并通过前端(Front End)处理这些命令。
  3. 在图元分配器(Primitive Distributor)中开始工作分配,处理indexbuffer中的顶点产生三角形分成批次(batches),然后发送给多个GPCs。这一步的理解就是提交上来n个三角形,分配给这几个GPC同时处理。
  4. 在GPC中,每个SM中的Poly Morph Engine负责通过三角形索引(triangle indices)取出三角形的数据(vertex data),即图中的Vertex Fetch模块。
  5. 在获取数据之后,在SM中以32个线程为一组的线程束(Warp)来调度,来开始处理顶点数据。
  6. SM的warp调度器会按照顺序分发指令给整个warp,单个warp中的线程会锁步(lock-step)执行各自的指令,如果线程碰到不激活执行的情况也会被遮掩(be masked out)
  7. warp中的指令可以被一次完成,也可能经过多次调度,例如通常SM中的LD/ST(加载存取)单元数量明显少于基础数学操作单元。
  8. 由于某些指令比其他指令需要更长的时间才能完成,特别是内存加载,warp调度器可能会简单地切换到另一个没有内存等待的warp,这是GPU如何克服内存读取延迟的关键,只是简单地切换活动线程组。
  9. 一旦warp完成了vertex-shader的所有指令,运算结果会被Viewport Transform模块处理,三角形会被裁剪然后准备栅格化,GPU会使用L1和L2缓存来进行vertex-shader和pixel-shader的数据通信。
  10. 接下来这些三角形将被分割,再分配给多个GPC,三角形的范围决定着它将被分配到哪个光栅引擎(raster engines),每个raster engines覆盖了多个屏幕上的tile,这等于把三角形的渲染分配到多个tile上面。也就是像素阶段就把按三角形划分变成了按显示的像素划分了。
  11. SM上的Attribute Setup保证了从vertex-shader来的数据经过插值后是pixel-shade是可读的。
  12. GPC上的光栅引擎(raster engines)在它接收到的三角形上工作,来负责这些这些三角形的像素信息的生成(同时会处理背面剔除和Early-Z剔除)。
  13. 32个像素线程将被分成一组,或者说8个2x2的像素块,这是在像素着色器上面的最小工作单元,在这个像素线程内,如果没有被三角形覆盖就会被遮掩,SM中的warp调度器会管理像素着色器的任务。
  14. 接下来的阶段就和vertex-shader中的逻辑步骤完全一样,但是变成了在像素着色器线程中执行。 由于不耗费任何性能可以获取一个像素内的值,导致锁步执行非常便利,所有的线程可以保证所有的指令可以在同一点。
  15. 最后一步,现在像素着色器已经完成了颜色的计算还有深度值的计算,在这个点上,我们必须考虑三角形的原始api顺序,然后才将数据移交给ROP(render output unit,渲染输出单元),一个ROP内部有很多ROP单元,在ROP单元中处理深度测试,和framebuffer的混合,深度和颜色的设置必须是原子操作,否则两个不同的三角形在同一个像素点就会有冲突和错误。

一些GPU技术

Early-Z

早期GPU的渲染管线的深度测试是在像素着色器之后才执行,这样会造成很多本不可见的像素执行了耗性能的像素着色器计算。后来,为了减少像素着色器的额外消耗,
将深度测试提至像素着色器之前(下图),这就是Early-Z技术的由来。Early-Z技术可以将很多无效的像素提前剔除,避免它们进入耗时严重的像素着色器。Early-Z剔除的最小单位不是1像素,而是像素块(2*2)。

下述情况会导致Early-Z失效:

  • 开启Alpha Test:由于Alpha Test需要在像素着色器后面的Alpha Test阶段比较(DX的discard,OpenGL的clip),所以无法在像素着色器之前就决定该像素是否被剔除。
  • 开启Alpha Blend:启用了Alpha混合的像素很多需要与frame buffer做混合,无法执行深度测试,也就无法利用Early-Z技术。
  • 关闭深度测试。Early-Z是建立在深度测试开启的条件下,如果关闭了深度测试,也就无法启用Early-Z技术。
  • 开启Multi-Sampling:多采样会影响周边像素,而Early-Z阶段无法得知周边像素是否被裁剪,故无法提前剔除。
  • 以及其它任何导致需要混合后面颜色的操作。

SIMD和SIMT

SIMD(Single Instruction Multiple Data)是单指令多数据,在GPU的ALU单元内,一条指令可以处理多维向量(一般是4D)的数据。比如,有以下shader指令:

float4 c = a + b

对于没有SIMD的处理单元,需要4条指令将4个float数值相加,汇编伪代码如下:

ADD c.x, a.x, b.x
ADD c.y, a.y, b.y
ADD c.z, a.z, b.z
ADD c.w, a.w, b.w

但是有了SIMD技术,只需要一条指令即可处理完成:

SIMD_ADD c, a, bfor(i=0;i<n;++i) a[i]=b[i]+c[i];


SIMT(Single Instruction Multiple Threads,单指令多线程)是SIMD的升级版,可对GPU中单个SM中的多个Core同时处理同一指令,并且每个Core存取的数据可以是不同的。

SIMT_ADD c, a, b这个指令会被同时送入在单个SM中被编组的所有Core中,同时执行运算,但a、b 、c的值可以不一样:

__global__ void add(float *a, float *b, float *c)
{int i = blockIdx.x * blockDim.x + threadIdx.x;a[i]=b[i]+c[i]; //no loop!
}

co-issue

co-issue是为了解决SIMD运算单元无法充分利用的问题。例如下图,由于float数量的不同,ALU利用率从100%依次下降为75%、50%、25%:
为了解决着色器在低维向量的利用率低的问题,可以通过合并1D与3D或2D与2D的指令。例如下图,DP3指令用了3D数据,ADD指令只有1D数据,co-issue会自动将它们合并,在同一个ALU只需一个指令周期即可执行完。


但是,对于向量运算单元(Vector ALU),如果其中一个变量既是操作数又是存储数的情况,无法启用co-issue技术:

CPU与GPU

CPU 是一个具有多种功能的优秀领导者。它的优点在于调度、管理、协调能力强,但计算能力一般。

GPU 相当于一个接受 CPU 调度的 “拥有大量计算能力” 的员工。

CPU GPU
延迟容忍度
并行目标 任务(Task) 数据(Data)
核心构架 多线程核心 SIMT核心
线程数量级别 10 10000
吞吐量
缓存需求量
线程独立性

CPU-GPU异构架构

根据CPU和GPU是否共享内存,可分为两种类型的CPU-GPU架构:

  1. 一是分离式架构(Discrete),CPU和GPU各自有独立的缓存和内存,它们通过PCI-e等总线通讯。这种结构的缺点在于 PCI-e 相对于两者具有低带宽和高延迟,数据的传输成了其中的性能瓶颈。目前使用非常广泛,如PC等。
  2. 二是耦合式架构(Couple),CPU 和 GPU 共享内存和缓存。AMD 的 APU 采用的就是这种结构,目前主要使用在游戏主机中,如 PS4、智能手机。

GPU资源机制

内存构架:GPU与CPU类似,也有多级缓存结构:寄存器、L1缓存、L2缓存、GPU显存、系统显存

它们的存取速度从寄存器到系统内存依次变慢。

由此可见,shader直接访问寄存器、L1、L2缓存还是比较快的,但访问纹理、常量缓存和全局内存非常慢,会造成很高的延迟。

Gpu内存分布在在RAM存储芯片或者GPU芯片上,他们物理上所在的位置,决定了他们的速度、大小以及访问规则:

  • 全局内存(Global memory)——位于片外存储体中。容量大、访问延迟高、传输速度较慢,使用二级缓存(L2 cache)做缓冲。
  • 本地内存(Local memory)——一般位于片内存储体中,变量、数组、结构体等都存放在此处,但是有大数组、大结构体以至于寄存器区放不下他们,编译器在编译阶段就会将他们放到片外的DDR芯片中(最好的情况也会被扔到L2 Cache中),且将他们标记为“Local”型
  • 共享内存(Shared memory)——位于每个流处理器组中(SM)中,其访问速度仅次于寄存器
  • 寄存器内存(Register memory)——位于每个流处理器组中(SM)中,访问速度最快的存储体,用于存放线程执行时所需要的变量。
  • 常量内存(Constant memory)——位于每个流处理器(SM)中和片外的RAM存储器中
  • 纹理内存(Texture memory)——位于每个流处理器(SM)中和片外的RAM存储器中

GPU资源管理模型(分离式架构)

  • MMIO(Memory Mapped IO)

    • CPU与GPU的交流就是通过MMIO进行的。CPU 通过 MMIO 访问 GPU 的寄存器状态。
    • DMA传输大量的数据就是通过MMIO进行命令控制的。
    • I/O端口可用于间接访问MMIO区域,像Nouveau等开源软件从来不访问它。
  • GPU Context
    • GPU Context代表了GPU计算的状态。
    • 在GPU中拥有自己的虚拟地址。
    • GPU 中可以并存多个活跃态下的Context。
  • GPU Channel
    • 任何命令都是由CPU发出。
    • 命令流(command stream)被提交到硬件单元,也就是GPU Channel。
    • 每个GPU Channel关联一个context,而一个GPU Context可以有多个GPU channel。
    • 每个GPU Context 包含相关channel的 GPU Channel Descriptors , 每个 Descriptor 都是 GPU 内存中的一个对象。
    • 每个 GPU Channel Descriptor 存储了 Channel 的设置,其中就包括 Page Table 。
    • 每个 GPU Channel 在GPU内存中分配了唯一的命令缓存,这通过MMIO对CPU可见。
    • GPU Context Switching 和命令执行都在GPU硬件内部调度。
  • GPU Page Table
    • GPU Context在虚拟基地空间由Page Table隔离其它的Context 。
    • GPU Page Table隔离CPU Page Table,位于GPU内存中。
    • GPU Page Table的物理地址位于 GPU Channel Descriptor中。
    • GPU Page Table不仅仅将 GPU虚拟地址转换成GPU内存的物理地址,也可以转换成CPU的物理地址。因此,GPU Page Table可以将GPU虚拟地址和CPU内存地址统一到GPU统一虚拟地址空间来。
  • PCI-e BAR
    • GPU 设备通过PCI-e总线接入到主机上。 Base Address
    • Registers(BARs) 是 MMIO的窗口,在GPU启动时候配置。
    • GPU的控制寄存器和内存都映射到了BARs中。
    • GPU设备内存通过映射的MMIO窗口去配置GPU和访问GPU内存。
  • PFIFO Engine
    • PFIFO是GPU命令提交通过的一个特殊的部件。
    • PFIFO维护了一些独立命令队列,也就是Channel。
    • 此命令队列是Ring Buffer,有PUT和GET的指针。
    • 所有访问Channel控制区域的执行指令都被PFIFO 拦截下来。
    • GPU驱动使用Channel Descriptor来存储相关的Channel设定。
    • PFIFO将读取的命令转交给PGRAPH Engine。
  • BO
    • Buffer Object (BO),内存的一块(Block),能够用于存储纹理(Texture)、渲染目标(Render Target)、着色代码(shader code)等等。
    • Nouveau和Gdev经常使用BO。

CPU-GPU数据流

下图是分离式架构的CPU-GPU的数据流程图:

  1. 将主存的处理数据复制到显存中。
  2. CPU指令驱动GPU。
  3. GPU中的每个运算单元并行处理。此步会从显存存取数据。
  4. GPU将显存结果传回主存。

Shader运行机制

  1. 在执行阶段,CPU端将shader二进制指令经由PCI-e推送到GPU端,GPU在执行代码时,会用Context将指令分成若干Channel推送到各个Core的存储空间。
  2. 下图为一个假象的Core:一个 GPU Core 包含 8 个 ALU,4 组执行环境(Execution context),每组有 8 个Ctx。这样,一个 Core 可以并发(Concurrent but interleaved)执行 4 条指令流(Instruction Streams),32 个并发程序片元(Fragment)。

以漫反射为例

sampler mySamp;
Texture2D<float3> myTex;
float3 lightDir;float4 diffuseShader(float3 norm, float2 uv)
{float3 kd;kd = myTex.Sample(mySamp, uv);kd *= clamp( dot(lightDir, norm), 0.0, 1.0);return float4(kd, 1.0);
}

经过编译后的汇编代码:

<diffuseShader>:
sample r0, v4, t0, s0
mul    r3, v0, cb0[0]
madd   r3, v1, cb0[1], r3
madd   r3, v2, cb0[2], r3
clmp   r3, r3, l(0.0), l(1.0)
mul    o0, r0, r3
mul    o1, r1, r3
mul    o2, r2, r3
mov    o3, l(1.0)

在执行阶段,汇编代码会被GPU推送到执行上下文(Execution Context),然后ALU会逐条获取(Detch)、解码(Decode)汇编指令为二进制指令,并执行它们。

而对于SIMT架构的GPU,汇编指令有所不同,变成了SIMT特定指令代码:

<VEC8_diffuseShader>:
VEC8_sample vec_r0, vec_v4, t0, vec_s0
VEC8_mul    vec_r3, vec_v0, cb0[0]
VEC8_madd   vec_r3, vec_v1, cb0[1], vec_r3
VEC8_madd   vec_r3, vec_v2, cb0[2], vec_r3
VEC8_clmp   vec_r3, vec_r3, l(0.0), l(1.0)
VEC8_mul    vec_o0, vec_r0, vec_r3
VEC8_mul    vec_o1, vec_r1, vec_r3
VEC8_mul    vec_o2, vec_r2, vec_r3
VEC8_mov    o3, l(1.0)

且Context以Core为单位组成共享的结构,同一个Core的多个ALU共享一组Context,

如果有多个Core,就会有更多的ALU同时参与shader计算,每个Core执行的数据是不一样的,可能是顶点、图元、像素等任何数据:

GPU Context和延迟

由于SIMT技术的引入,导致很多同一个SM内的很多Core并不是独立的,当它们当中有部分Core需要访问到纹理、常量缓存和全局内存时,就会导致非常大的卡顿(Stall)。

如果有4组上下文(Context),它们共用同一组运算单元ALU。

假设第一组Context需要访问缓存或内存,会导致2~3个周期的延迟,此时调度器会激活第二组Context以利用ALU。

当第二组Context访问缓存或内存又卡住,会依次激活第三、第四组Context,直到第一组Context恢复运行或所有都被激活。

延迟的后果是每组Context的总体执行时间被拉长了,越多Context可用就越可以提升运算单元的吞吐量。

Geforce RTX 2060的扩展验证

NV shader thread group提供了OpenGL的扩展,可以查询GPU线程、Core、SM、Warp等硬件相关的属性。如果要开启次此扩展,需要满足以下条件:

OpenGL 4.3+;
GLSL 4.3+;
支持OpenGL 4.3+的NV显卡;
下面是具体的字段和代表的意义:

// 开启扩展
#extension GL_NV_shader_thread_group : require     (or enable)WARP_SIZE_NV    // 单个线程束的线程数量
WARPS_PER_SM_NV // 单个SM的线程束数量
SM_COUNT_NV     // SM数量uniform uint  gl_WarpSizeNV;    // 单个线程束的线程数量
uniform uint  gl_WarpsPerSMNV;  // 单个SM的线程束数量
uniform uint  gl_SMCountNV;     // SM数量in uint  gl_WarpIDNV;       // 当前线程束id
in uint  gl_SMIDNV;         // 当前线程束所在的SM id,取值[0, gl_SMCountNV-1]
in uint  gl_ThreadInWarpNV; // 当前线程id,取值[0, gl_WarpSizeNV-1]in uint  gl_ThreadEqMaskNV; // 是否等于当前线程id的位域掩码。
in uint  gl_ThreadGeMaskNV; // 是否大于等于当前线程id的位域掩码。
in uint  gl_ThreadGtMaskNV; // 是否大于当前线程id的位域掩码。
in uint  gl_ThreadLeMaskNV; // 是否小于等于当前线程id的位域掩码。
in uint  gl_ThreadLtMaskNV; // 是否小于当前线程id的位域掩码。in bool  gl_HelperThreadNV; // 当前线程是否协助型线程。

利用以上字段,可以编写特殊shader代码转成颜色信息,可视化了顶点着色器、像素着色器的SM、Warp id,为我们查探GPU的工作机制和流程提供了途径,以便可视化窥探GPU的工作机制和流程。下面正式进入验证阶段,将以Geforce RTX 2060作为验证对象,加入扩展所需的代码,并修改颜色计算:

#version 430 core
#extension GL_NV_shader_thread_group : requireuniform uint  gl_WarpSizeNV;    // 单个线程束的线程数量
uniform uint  gl_WarpsPerSMNV;  // 单个SM的线程束数量
uniform uint  gl_SMCountNV;     // SM数量in uint  gl_WarpIDNV;       // 当前线程束id
in uint  gl_SMIDNV;         // 当前线程所在的SM id,取值[0, gl_SMCountNV-1]
in uint  gl_ThreadInWarpNV; // 当前线程id,取值[0, gl_WarpSizeNV-1]out vec4 FragColor;void main()
{// SM idfloat lightness = gl_SMIDNV / gl_SMCountNV;FragColor = vec4(lightness);
}

由上面的代码渲染的画面如下:

从上面可分析出一些信息:

  • 画面共有32个亮度色阶,也就是Geforce RTX 2060有32个SM。
  • 单个SM每次渲染16x16为单位的像素块,也就是每个SM有256个Core。
  • SM之间不是顺序分配像素块,而是无序分配。
  • 不同三角形的接缝处出现断层,说明同一个像素块如果分属不同的三角形,就会分配到不同的SM进行处理。由此推断,相同面积的区域,如果所属的三角形越多,就会导致分配给SM的次数越多,消耗的渲染性能也越多。

接着修改片元着色器的颜色计算代码以显示Warp id:

// warp id
float lightness = gl_WarpIDNV / gl_WarpsPerSMNV;
FragColor = vec4(lightness);

得到如下画面:

由此可得出一些信息或推论:

  • 画面共有32个亮度色阶,也就是每个SM有32个Warp,每个Warp有8个Core。
  • 每个色块像素是4x8,由于每个Warp有8个Core,由此推断每个Core单次要处理2x2的最小单元像素块。
  • 也是无序分配像素块。
  • 三角形接缝处出现断层,同SM的推断一致。
    再修改片元着色器的颜色计算代码以显示线程id:
// thread id
float lightness = gl_ThreadInWarpNV / gl_WarpSizeNV;
FragColor = vec4(lightness);

得到如下画面:

为了方便分析,用Photoshop对中间局部放大10倍,得到以下画面:

结合上面两幅图,也可以得出一些结论:

  • 相较SM、线程束,线程分布图比较规律。说明同一个Warp的线程分布是规律的。
  • 三角形接缝处出现紊乱,说明是不同的Warp造成了不同的线程。
  • 画面有32个色阶,说明单个Warp有32个线程。
  • 每个像素独占一个亮度色阶,与周边相邻像素都不同,说明每个线程只处理一个像素。
  • 再次说明,以上画面和结论是基于Geforce RTX 2060,不同型号的GPU可能会不一样,得到的结果和推论也会有所不同。

总结

通过其那面介绍的逻辑管线层面和硬件执行层面,可以总结出:

  • 顶点着色器和像素着色器都是在同一个单元中执行的(在原来的架构中vs和ps是分开的,后来nv把二者统一了),vs是按照三角形来并行处理的,ps是按照像素来并行处理的。
  • vs和ps中的数据是通过L1和L2缓存传递的。
  • warp和thread都是逻辑上的概念,sm和sp都是物理上的概念。线程数不等于流处理数。

【TA-霜狼_may-《百人计划》】图形2.7.2 GPU硬件架构概述相关推荐

  1. 百人计划 图形2.1 色彩空间

    色彩发送器 色彩认知:光源是出生点,光源发出光线,光线通过直射反射折射等路径最终进入人眼.在接收到光线后,人眼产生了一系列化学反应.由此把产生的信号传入大脑,大脑对颜色产生了认知感知. 光的要素: 光 ...

  2. 百人计划 图形1.4 PC手机图形API介绍

    前言 电脑工作原理:电脑是有各种不同的硬件组成,由驱动软件驱使硬件进行工作.所有的如软件工程师都会直接或间接使用到驱动. 定义:是一个针对GPU的图形库,用于渲染2D.3D矢量图形的跨语言.跨平台的应 ...

  3. 百人计划 图形 2.5 BUMP图改进

    基础感念 凹凸贴图技术是对物体表面贴图进行变化然后再进行光照计算的一种技术.例如给法线分量添加噪音,或者在一个保存扰动值的纹理图中进行查找,这是一种提升物体真实感的有效办法,但却不需要额外的提升物体的 ...

  4. 百人计划 图形2.2 模型与材质基础

    渲染流水线大致过程 1.顶点数据输入到顶点着色器中进行相关的顶点计算,然后进行图元装配,通过点与点之间的关系将点进行连接.2.再到几何着色器(可选着色器)进行图元的增加,再到光栅化通过遍历像素点将一个 ...

  5. 【TA-霜狼_may-《百人计划》】图形3.4 延迟渲染管线介绍

    [TA-霜狼_may-<百人计划>]图形3.4 延迟渲染管线介绍 @[TOC]([TA-霜狼_may-<百人计划>]图形3.4 延迟渲染管线介绍 3.4.1 渲染路径 3.4. ...

  6. 【TA-霜狼_may-《百人计划》】图形3.7.2 command buffer简

    [TA-霜狼_may-<百人计划>]图形3.72command buffer 及urp概述 @[TOC]([TA-霜狼_may-<百人计划>]图形3.72command buf ...

  7. 【TA-霜狼_may-《百人计划》】图形4.5 DoF景深基础

    [TA-霜狼_may-<百人计划>]图形4.5 Dof景深基础 @[TOC]([TA-霜狼_may-<百人计划>]图形4.5 Dof景深基础 4.5.1 景深 离散圈 4.5. ...

  8. 技美 百人计划 (图形)1.1渲染流程

    技美 百人计划 (图形) 图形 1.1 渲染流水管线2.1数学基础 很早之前就有关注百人计划这个系列的课程,也跟着看到了2.几的教学但是一直没有写笔记,最近看到百人计划已经快更新完毕了,决定从头看一遍 ...

  9. 个人学习笔记 ——【技术美术百人计划】图形 2.1 色彩空间介绍

    个人学习笔记 --[技术美术百人计划]图形 2.1 色彩空间介绍 图形 2.1 色彩空间介绍 个人学习笔记 --[技术美术百人计划]图形 2.1 色彩空间介绍 图形 2.1 色彩空间介绍 一.色彩发送 ...

最新文章

  1. linux怎么远程windows桌面,Windows系统怎么远程登陆桌面Linux?
  2. caj文件浏览器_caj文件怎么转换成pdf文件?试试这样操作,3步成功转换
  3. 【数据结构与算法】【算法思想】贪心算法
  4. APP价格标签页设计灵感!多款案例选择!
  5. Python字符串函数总结
  6. [模拟|数位] leetcode 7 整数反转
  7. 算法分析c语言版+视频教程,数据结构c语言版
  8. 语句覆盖,判定覆盖,条件覆盖,条件/判定覆盖,条件组合覆盖,路径覆盖
  9. 浅析Tone mapping
  10. win10系统word2019如何去掉文字背景颜色方法
  11. 计算机毕业设计springboot+vue基本微信小程序的社区疫情防控系统
  12. 概率论与数理统计 浙江大学 第27-34讲单元测验
  13. htmldd隐藏,如何在隐藏“dd”后隐藏“dd”
  14. 2022还不知道登陆邮箱账号怎么填写?个人邮箱登录注册流程看详解
  15. Fiddler4 手机抓包
  16. 更改app图标和名称
  17. Fiddler中篡改后端返回数据
  18. 秋招复盘 — 不忘初心,砥砺前行
  19. Framework基础之入门
  20. Ubuntu16无线网络正常连接但是无法上网的解决方法

热门文章

  1. 行人重识别数据集汇总
  2. C语言--“.”与“-”有什么区别?
  3. 虚拟服务器的好处与坏处,虚拟主机有什么坏处
  4. java 使用sourceforge.pinyin4j查询汉字拼音
  5. handlebars是什么
  6. [Android]Toolbar
  7. AutoHotKey的那些事儿:(一)、AutoHotkey常用命令
  8. 《 Python List列表全实例详解系列(五)》——修改元素(修改单个、修改一组)
  9. 后端程序员福音 -- TellMe 推送助手
  10. Java的长整型Long/long后面的数字什么情况下必须加L?