1. 引言

SNARK (Succinct Non-interactive Arguments of Knowledge) 是实现:

  • 1)区块链扩容(如L2 rollups)
  • 2)隐私

的重要密码学原语。

SNARK中有2个重要角色:

  • 1)Prover P;
  • 2)Verifier V。

同时本文将SNARK Prover 工作分为前后端:

  • 1)前端:将计算程序转换为电路;
  • 2)后端:基于电路使用某种SNARK方案生成证明。

关于SNARK的更多知识,可参看:

  • Justin Thaler的2022年书稿《Proofs, Arguments, and Zero-Knowledge》。
  • a16z 2022年8月视频分享 An Evolution of Models for Zero-Knowledge Proofs with Sarah Meiklejohn | a16z crypto research talks
  • a16z crypto的一些列分享视频 a16z crypto

SNARKs的verification cost根据具体的实现方案,会各有不同,但是普遍认为其verification cost不错。如:

  • Plonk proof 在以太坊上的验证开销约为29万gas;
  • Groth16 proof的验证开销约为23万gas;
  • 而StarkWare的proof验证开销约为500万gas。

SNARK可用于除区块链之外的其它领域,如:

  • Untrusted servers:如论文The Efficient Server Audit Problem, Deduplicated Re-execution, and the Web
  • 硬件:如论文Verifiable ASICs

由于SNARK的验证开销较低,影响SNARK可用性的决定性因素在于SNARK prover P的开销。

2. SNARK是如何部署的?

SNARK部署通常为:

  • 开发者编写a computer programψ\psiψ,Prover P声称其知道某输入数据www(www表示witness),并检查了www是有效的。

如在rollups中,该program为检查www中的所有交易都已有效签名且account balance不会为负值等等。然后program ψ\psiψ作为某SNARK frontend的输入被编译为某种有利于SNARK技术应用的格式。这种SNARK友好的格式称为intermediate representation(IR)。

通常,IR为某种与ψ\psiψ等价的circuit-satisfiability实例。即有某circuit CCC,其输入为数据www 以及 一些称为“non-deterministic advice”的额外输入,然后基于www运行ψ\psiψ。advice输入帮助CCC运行ψ\psiψ,同时keeping CCC small。如,每次ψ\psiψ计算x/yx/yx/y,将商qqq和余数rrr作为advice提供给CCC,CCC仅需要简单的检查x=qy+rx=qy+rx=qy+r即可。相对于重新做除法运算,该检查的开销要没那么贵。

最后,对CCC应用circuit-satisfiability SNARK,这称为SNARK backend。对于一些高度结构化的问题,如:

  • 矩阵乘法——Time-Optimal Interactive Proofs for Circuit Evaluation
  • 卷积——zkCNN: Zero Knowledge Proofs for Convolutional Neural Network Predictions and Accuracy
  • 一些图形问题——The Unreasonable Power of the Sum-Check Protocol

已知的SNARK可以避免这种frontend/backend范式,从而实现更快的Prover。但这篇文章的重点是通用SNARK。

同时,将发现SNARK backend的Prover cost将随CCC size增长。保持CCC small是有调整的,因circuit是表示计算的特别有限的格式。circuit中包含了gates,这些gates通过wires连接。每个gate ggg基于 某些输入值 通过 某个很简单的函数运算 获得 某些输出值。ggg这些输出值 又通过wires 作为后续gate的输入值。

3. SNARK扩容:用时究竟多少?

关键在于,相比于基于data重新执行ψ\psiψ,SNARK prover的用时要多多少呢?
答案为:

  • “SNARK prover开销” 减去 “direct witness checking开销”。

所谓“direct witness checking开销”是指:P将www发送给V,V通过执行ψ\psiψ来检查www的有效性。

可将"SNARK prover开销"分解为:

  • SNARK frontend开销:若逐gate对circuit CCC进行evaluate 的开销 为 运行ψ\psiψ 的FFF倍,则称frontend开销为FFF。
  • SNARK backend开销:若对CCC应用backend prover的开销 为 逐gate对circuit CCC进行evaluate开销 的BBB倍,则称backend开销为BBB。

总的"SNARK prover开销"为F⋅BF\cdot BF⋅B,即使F,BF,BF,B各自为适度的,但乘积之后的开销也可能是相当大的。

事实上,F,BF,BF,B都可能约为1000或更大,这意味着总的prover开销 相对于 “direct witness checking” 将是百万级甚至千万级的。笔记本电脑上运行仅需1秒的程序,SNARK prover在笔记本电脑上使用单线程的情况下将需要数十或数百天。幸运的是,SNARK prover的工作看具体的方案,通常可不同程度的并行化。

总之,若想在当今应用中使用SNARK,需要以下三者之一成立:

  • 1)“Direct witness checking”在笔记本上用时小于1秒;
  • 2)“Direct witness checking”特别适用于电路中的表示,因此前端开销较小;
  • 3)愿意等数天待SNARK prover完成;或(和)为大型并行计算资源付费。

本文接下来将解释前端和后端开销的来源,以及不同SNARK方案相应的开销评估。然后将讨论改进的前景。

4. SNARK前后端分离

完全将前后端分离是有调整的,因不同的后端支持不同类型的circuits,因此,前端会根据其交互的后端而不同。

SNARK后端支持所谓的arithmetic circuit,即该circuit的输入为某有限域的元素,且circuit的gate会对2个域元素进行加法和乘法运算。大致对应于本质上是代数的直线计算机程序(无分支、条件语句等),也就是说,它们的原始数据类型是域元素。

大多数SNARK后端都支持名为Rank-1 Constraint Satisfaction (R1CS)的通用arithmetic circuit。除了Groth16及其前身之外,这些SNARK也可用于支持其他IRs。如StarkWare使用名为“Algebraic Intermediate Representation (AIR)”,一种类似于PlonK和其它后端所支持从名为PlonKish arithmetization。某些后端支持更通用的IR可减少生成这些IR所需的前段开销。

根据所支持的有限域,后端也会有所不同。

4.1 SNARK前端的各种方法

某些非常特别的计算机程序天然对应arithmetic circuit,如某程序对某域进行直接的矩阵乘法运算。但大多数计算机诚实既不是straight-line的,也不是algebraic的,它们通常包含了条件声明、整数除法运算、浮点数运算这些并不天然对应有限域的运算 等等。此时,前端开销将是大量的。

一种流行的前端方法是,按单一CPU逐步执行的方式来生成电路,也称为virtual machine(VM)。前端设计者会定义一组类似于真实计算机处理器的汇编指令的“基本操作”(“primitive operations”)。想要使用前端的开发人员要么直接用汇编语言编写“witness-checking programs”,要么用Solidity等高级语言编写,然后让他们的程序自动编译成汇编代码。

如:

  • 1)StarkWare的Cairo是一种功能非常有限的汇编语言,其汇编指令仅支持基于有限域的加法和乘法、函数调用、对某immutable(即write-once)memory的读写。
    Cairo VM为von Neumann架构,即意味着前端生成的circuit,以某Cairo program为public input,并基于witness “运行”该程序。Cairo语言为图灵完备的——除了其指令集有限,Cairo可模拟更标准的架构,尽管可能更昂贵。
    Cairo前端将 执行TTT个primitive指令的Cairo程序 转换为 名为“degree-2 AIR with TTT rows and about 50 columns”。其具体含义见:ethSTARK Documentation – Version 1.1。但是只要考虑SNARK prover足够快,每个Cairo CPU的TTT steps对应电路中的50到100个gates。

  • 2)RISC Zero的方案与Cairo类似,但其virtual machine为RISC-V架构。RISC-V架构为一种开源架构,具有丰富的软件生态并广受欢迎。由于其指令集非常简单,设计高效的SNARK前端 相对易处理(至少相对于复杂的架构,如x86或ARM)。截止2022年5月,RISC Zero是 图灵程序——ZK7: RISC Zero: Encoding Von-Neumann Architectures in Zero-Knowledge Proof Systems - Risc0的,可将TTT primitive RISC-V指令 执行转换为 “degree-5 AIRs with 3T rows and 160 columns”。对于RISC-V CPU的每个step,对应的circuit中至少有500 gates。未来将对其进行改进。

不同的zkEVM项目(如zkSync 2.0,Scroll,Polygon的zkEVM)将虚拟机视为以太坊虚拟机。将每个EVM指令 转换为 等价“gadget” (即针对该指令的某优化电路)的过程,相比于简单的Cairo和RISC-V架构,这种转换的开销将更大。为此,某些zkEVM项目(见Vitalik The different types of ZK-EVMs),并不直接实现EVM指令集,而是将高层Solidity程序 变意思为其它 汇编语言,然后将汇编语言转换为电路。这些项目的性能效果尚待确定。

RISC-V和Cairo这样的“CPU仿真器”,可生成单一电路 来 处理 相关汇编语言中的所有程序。
备选方案有“RISC-like”方法:为不同的程序 生成 不同的电路。这样可为某程序生成更小的电路,特别是当程序在每个timestep的汇编指令不依赖程序输入时。如,对于naive矩阵乘法这类straight-line程序,可能可完全避免前端开销。

但是ASIC方法仍然有局限,迄今为止,其无法支持具有固定上限的循环计算。

前端开销的最后一部分来自于,所有的SNARK都是用基于有限域的circuit运算。笔记本上的CPU可通过单一机器指令来对2个整数求和或求积。若前端输出的circuit基于的域具有足够大的characteristic,则可通过相应的域运算来模仿加法或乘法运算。但是,基于real CPU来做域运算通常需要很多机器指令(尽管当今有某些处理器原生支持特定域)。

某些SNARK后端所采用的域选择很灵活。如,若后端使用密码学群GGG,则其circuit的域必须与GGG群中的元素数量相匹配,这就是限制。此外,并不是所有的域都支持实用FFT算法。

当前的情况为:

  • 1)2021年 Brakedown: Linear-time and post-quantum SNARKs for R1CS:为当前唯一原生支持对任意(足够大)域计算的SNARK方案。
  • 2)2022年 Orion: Zero Knowledge Proof with Linear Prover Time:为Brakedown的衍生方案,对于其它SNRK支持的域,其甚至有当前已知的最快prover性能,但是其proof太大,并不适于很多区块链应用。
  • 3)2022年 Scalable and Transparent Proofs over All Large Fields, via Elliptic Curves:致力于改进proof size,但prover又更慢了,且似乎离实用仍有障碍,详细见:
    • SNARKs over any field
    • https://github.com/wborgeaud/ecfft-bn254:为Ben-Sasson等人2021年论文Elliptic Curve Fast Fourier Transform (ECFFT) Part I: Fast Polynomial Algorithms over all Finite Fields 基于BN254 base field的ECFFT算法的RUST实现。

某些项目会选择某些可快速运算的域,如Plonky2等项目使用了characteristic为264−232+12^{64}-2^{32}+1264−232+1的域(即 Goldilocks域),因该域内的计算可比其他less structured域快数倍。但是,使用如此小的characteristic可能会为通过域操作高效表达整数运算带来风险。(如,32-bit无符号整数 与 任意大于2322^{32}232 的整数相乘时,结果将大于该field characteristic。因此,选择该域意味着仅支持32-bit number运算。)

但不管怎样,对于当今所有流行的SNARK来说,要实现128位的安全性(在不显著增加验证成本的情况下),它们必须在大于21282^{128}2128的域上工作。据我所知,这意味着每个域操作将需要至少10个64位机器乘法,以及相当多的加法和按位操作。因此,对于需要在有限域上工作的电路,应考虑至少一个数量级的前端开销。

总而言之,使用虚拟机抽象的现有前端在虚拟机的每一步生成100x到1000x个gates的电路,对于更复杂的虚拟机可能更多。除此之外,有限域运算至少比现代处理器上的类似指令慢10倍(如果处理器内置了对某些域的支持,则可能会有例外)。“ASIC前端方法”可能会减少一些开销,但目前其支持的程序种类有限。

5. SNARK后端的瓶颈是什么?

SNARKs for circuit-satisfiability通常设计为:

  • 名为polynomial IOP——如2019年论文Transparent SNARKs from DARK Compilers 的信息理论安全协议
  • 与 名为polynomial commitment scheme——如KZG 2010年论文Constant-Size Commitments to Polynomials and Their Applications 的密码学协议

的结合。大多数情况下,Prover的具体瓶颈在polynomial commitment scheme。特别的,这些SNARK的prover会对某个或某些degree(最多)为∣C∣|C|∣C∣的多项式进行密码学承诺,其中∣C∣|C|∣C∣表示circuit CCC中的gate数。

继而,polynomial commitment scheme中的具体拼接在于:

  • 所选用的具体多项式承诺方案;
  • 以及 circuit size。

但是polynomial commitment scheme中通常有如下3种操作之一:

  • 1)FFT计算
  • 2)密码学群内的exponentiation计算
  • 3)Merkle-hashing计算:通常仅当circuit为small时,Merkle-hashing计算才是瓶颈,因此本文后续不再讨论该情况。

5.1 基于discrete log的多项式承诺方案

基于某密码学群GGG内的discrete logarithm problem难度的多项式承诺方案有:

  • KZG:2010年论文。
  • Bulletproofs:2017年论文。
  • Dory: Efficient, Transparent arguments for Generalised Inner Products and Polynomial Commitments:微软团队2020年论文。
  • Hyrax-commit: Doubly-efficient zkSNARKs without trusted setup:2017年论文。

Prover需要对多项式的系数计算Pedersen vector commitment。这包含了size等于多项式degree的multi-exponentiation运算。在SNARK中,多项式degree通常为circuit CCC的size ∣C∣|C|∣C∣。

直接计算的话,size为∣C∣|C|∣C∣的multi-exponentiation需要约1.5⋅∣C∣⋅log⁡∣G∣≈400⋅∣C∣1.5\cdot |C|\cdot \log |G|\approx 400\cdot |C|1.5⋅∣C∣⋅log∣G∣≈400⋅∣C∣次群运算,其中∣G∣|G|∣G∣表示群GGG内的元素数。但是,Pippenger算法可将其reduce大约log⁡∣C∣\log |C|log∣C∣因子。对于大电路(也就是说,∣C∣≥225|C|\geq 2^{25}∣C∣≥225),log⁡∣C∣\log |C|log∣C∣因子具体为25或更多,即,对于搭电路,Pedersen vector commitment计算需约略多于10⋅∣C∣10\cdot |C|10⋅∣C∣次群操作。单个群操作要比单个慢约10x倍。采用这种多项式承诺机制的SNARK prover,需要约100⋅∣C∣100\cdot |C|100⋅∣C∣次域操作(昂贵的)。

除了以上100x倍因子的放大之外,现有的SNARK还有额外的开销,如:

  • 1)Spartan prover:采用Hyrax多项式承诺,需要做∣C∣1/2|C|^{1/2}∣C∣1/2次multi-exponentiations运算,每个multi-exponentiations运算size为∣C∣1/2|C|^{1/2}∣C∣1/2,从而将Pippenger算法的加速效果减半。
  • 2)Groth16 prover:需基于pairing-friendly group,该群内操作 要比 非pairing-friendly群操作 慢至少2x倍。同时,Prover还需要做3次而不是1次multi-exponentiations运算。因此,相比于上面评估的100⋅∣C∣100\cdot |C|100⋅∣C∣,实际会更慢,还需要至少附加一个666因子。
  • 3)Marlin和PlonK prover:也需要pairing,且需要对多于3个多项式进行commit。
  • 4)而对于任意使用Bulletproofs的SNARK(如Halo2使用的为PlonK+Bulletproofs,而不是PlonK+KZG) prover:在承诺机制的“opening”阶段,需计算多达logarithmically次multi-exponentiations运算,这在很大程度上消除了任何Pippenger加速效果。

总之,现有使用Pedersen vector commitment的SNARK后端开销至少要200x倍甚至多于1000x倍。

5.2 其它多项式承诺

使用其它多项式承诺机制(如FRI 和 Ligero-commit:2017年论文 Ligero: Lightweight Sublinear Arguments Without a Trusted Setup)的SNARK,其prover瓶颈在于需要做large FFT计算。StarkWare部署的prover每列至少需要做2次FFT计算,每列的长度在16⋅T16\cdot T16⋅T至32⋅T32\cdot T32⋅T之间。其中常量161616和323232取决于StarkWare设置的FRI内部参数,可减少该常量值,但将增加验证开销。

乐观地是,长度32⋅T32\cdot T32⋅T的FFT约需64⋅T⋅log⁡(32T)64\cdot T\cdot \log(32T)64⋅T⋅log(32T)次field multiplication。这意味着即使选择相对小的TTT(如,T≥220T\geq 2^{20}T≥220),每列所需的field操作次数最少为64⋅25⋅T=1600⋅T64\cdot 25\cdot T=1600\cdot T64⋅25⋅T=1600⋅T,因此后端开销至少为千倍级。此外,large FFT更大的瓶颈可能来自于内存带宽而不是域操作。

在某些上下文,执行large FFT的SNARK的后端开销,可借助proof aggregation技术进行缓解。如,对于rollups,这意味着P(rollup service)将a big batch of transactions 切分为10个更小的batches。对于每个small batch iii,P会为该batch的有效性生成SNARK证明πi\pi_iπi​。但P并不将这些证明提交到以太坊,因为这意味着近10倍的gas开销。相反,会再次应用SNARK,为“P knows π1,⋯,π10\pi_1,\cdots,\pi_{10}π1​,⋯,π10​”生成证明π\piπ。P声称知道的witness为该10个证明π1,⋯,π10\pi_1,\cdots,\pi_{10}π1​,⋯,π10​,且direct witness checking为 对每个证明应用SNARK验证流程。然后将证明π\piπ提交到以太坊。

在类似FRI和Ligero-commit的多项式承诺机制中,P time 和 V cost 之间存在强烈的紧张关系,内部参数充当一个旋钮,可以相互权衡。由于π1,⋯,π10\pi_1,\cdots,\pi_{10}π1​,⋯,π10​不提交到以太坊,因此可将旋钮调整,使得生成证明更快,证明更大。仅在最后一次应用SNARK aggregate π1,⋯,π10\pi_1,\cdots,\pi_{10}π1​,⋯,π10​为单一证明π\piπ,使得其为small proof。

StarkWare计划近期部署proof aggregation,参见"Recursive STARKs with Cairo" (Avihu Levy & Shahar Papini)。
而Plonky2也关注proof aggregation。

6. SNARK扩容的其他瓶颈?

本文主要关注prover time,但其它prover开销也可能是扩容瓶颈。如,很多SNARK后端的prover需为circuit CCC中的每个gate存储多个field elements,这样的space cost也很大。如,在笔记本上运行为1秒的程序ψ\psiψ,在中等处理器上可能需要执行10亿次primitive操作。正如我们所看到的,一般来说,我们应该期望CCC在每次操作中需要超过100个gates。这意味着1000亿个gate,具体取决于SNARK,可能意味着P需数十或数百TB的空间。

另一个例子,很多流行SNARKs(如PlonK,Marlin,Groth16)需要复杂的“trusted setup ceremony”来生成结构化的“proving key”——必须由Prover存储。如:

  • https://github.com/weijiekoh/perpetualpowersoftau
  • Aleo Ceremony

这些大的ceremony生成的proving key支持的约228≈2502^{28}\approx 250228≈250 million gates(2.5亿个gate)的circuit。相应的proving key size为几十GB。

若proof aggregation可行,这些瓶颈可大幅缓解。

7. 展望:更多可扩展SNARK的前景

前端和后端开销都可以是三个数量级或更多。我们能预期在不久的将来这些数字会大幅下降吗?
答案是肯定的:

  • 1)首先,当今最快的后端(如Spartan何Brakedown,以及其他 sum-check protocol + 多项式承诺的SNARK),都具有large proofs——通常proof size为circuit size的平方根,使得人们实际上并不使用如此大的proof。未来,期望通过深度为1的small-proof SNARK组合,来有效降低proof size和verifier time。类似proof aggregation,这意味着Prover将首先生成具有“fast-prover, large-proof”的SNARK证明π\piπ,但并不将π\piπ直接发送给V。相反,P将使用某small-proof SNARK来生成proof π′\pi'π′,以证明其知道π\piπ,并将π′\pi'π′发送给V。这可以将当前流行的SNARK后端开销削减一个数量级。
  • 2)借助硬件加速。通常GPU速度比CPU快10倍,而ASIC又比GPU快10倍。但是需考虑以下三个前提:
    • 2.1)large FFT的瓶颈可能在内存带宽而不是field操作,因此,需执行FFT的SNARK受限于特定的硬件;
    • 2.2)本文重点关注多项式承诺瓶颈,很多SNARK prover还需做一些相对没那么昂贵的操作,解决了多项式承诺瓶颈之后(如discrete-log-based SNARKs的multi-exponentiation加速,详细见2022年论文PipeMSM: Hardware Acceleration for Multi-Scalar Multiplication),这些相对没那么昂贵的操作将成为新的不易解决的瓶颈。(如Groth16,Marlin,PlonK等SNARK方案中,Prover除需做FFT计算之外,还需做multi-exponentiation计算)。
    • 2.3)寻找SNARK的最有效域和椭圆曲线可能会持续发展一段时间,这可能会对基于ASIC的prover加速带来挑战。
  • 3)在前端方面,我们可能会越来越多地发现,Cairo、RISCZero、zkEVMs等项目的“CPU仿真器”方法实际上可以很好地适应CPU指令集的复杂性(在性能方面)。事实上,这正是各种zkEVM项目的希望所在。这可能意味着,虽然前端开销保持三个数量级或更多,但前端能够支持越来越符合实际CPU架构的VM。一个相反的担忧是,随着实现越来越复杂指令的hand-coded gadgets激增,前端可能会变得复杂和难以审计。形式化验证方法可能在解决这一问题方面发挥重要作用。
  • 4)最后,至少在区块链应用程序中,我们可能会发现,大多数智能合约主要使用简单、SNARK友好的指令。这可以在实践中实现较低的前端开销,同时保持通用性和改进开发人员体验,这与支持高级编程语言如Solidity和丰富的指令集(包括EVM的指令集)有关。

参考资料

[1] a16z团队2022年8月博客 Measuring SNARK performance: Frontends, backends, and the future
[2] a16z团队2022年9月博客 SNARK security and performance
[3] twitter SNARK security

SNARK性能及安全——Prover篇相关推荐

  1. 【转】一文掌握 Linux 性能分析之网络篇(续)

    [转]一文掌握 Linux 性能分析之网络篇(续) 在上篇网络篇中,我们已经介绍了几个 Linux 网络方向的性能分析工具,本文再补充几个.总结下来,余下的工具包括但不限于以下几个: sar:统计信息 ...

  2. Android性能优化系列总篇

    目前性能优化专题已完成以下部分: 性能优化总纲--性能问题及性能调优方式 性能优化第四篇--移动网络优化 性能优化第三篇--Java(Android)代码优化 性能优化第二篇--布局优化 性能优化第一 ...

  3. Spark性能优化指南——基础篇

    在大数据计算领域,Spark已经成为了越来越流行.越来越受欢迎的计算平台之一.Spark的功能涵盖了大数据领域的离线批处理.SQL类处理.流式/实时计算.机器学习.图计算等各种不同类型的计算操作,应用 ...

  4. 学习动态性能表 第五篇--V$SESSION

    学习动态性能表 第五篇--V$SESSION  在本视图中,每一个连接到数据库实例中的session都拥有一条记录.包括用户session及后台进程如DBWR,LGWR,arcchiver等等. V$ ...

  5. Unity性能优化 :合批篇

    前言 本系列为一些性能优化的小知识,是日常游戏开发中与性能表现的一些点,本篇为该系列文章的第二篇,前篇链接: 第一篇: Unity性能优化:资源篇 在早期Unity中,对于合批的处理手段主要是下面三种 ...

  6. 【转】【技术博客】Spark性能优化指南——高级篇

    http://mp.weixin.qq.com/s?__biz=MjM5NjQ5MTI5OA==&mid=2651745207&idx=1&sn=3d70d59cede236e ...

  7. Android App 性能优化系列结语篇

    Android App 性能优化系列结语篇 原文出处:http://gold.xitu.io/post/581f4ad667f3560058a33057 关于Android App的优化, 从第一篇的 ...

  8. Sql Server查询性能优化之索引篇【推荐】

    Sql Server查询性能优化之索引篇[推荐] 这篇是索引系列中比较完整的,经过整理而来的 一 索引基础知识 索引概述 1.概念 可以把索引理解为一种特殊的目录.就好比<新华字典>为了加 ...

  9. 性能优化系列第一篇——数据库性能优化

    本文章转载的Trinea大神的文章,文章原地址 http://www.trinea.cn/android/database-performance/ 性能优化之数据库优化 本文为性能优化的第一篇--数 ...

最新文章

  1. mysql 慢查询_mysql如何捕捉慢日志查询
  2. celeron处理器_显卡和处理器哪个更重要?
  3. mysql中int时间和datetime时间的互相转换
  4. MSM--Memcached_Session_Manager介绍及使用
  5. java Math 方法
  6. EditText焦点问题
  7. css中字间距调整(转)
  8. python多线程运用
  9. 事务的隔离级别与锁的申请和释放
  10. springboot vue组件写的个人博客系统
  11. 桃养人,杏害人,樱桃树下埋死人
  12. vb.net教程 3-1 窗体编程基础 1
  13. 如何制作微信小程序(三个步骤开发小程序)
  14. 2020电信宽带费用_现在电信宽带多少钱一年,2020年电信宽带套餐价格表
  15. Springboot 邮件发送(html内嵌图片、附件)
  16. Adobe Acrobat的安装时出现:服务print spooler启动失败,请确认您有足够权限启动系,打印机print spooler系统服务不能正常启用解决办法
  17. python图片镜像翻转_python中镜像实现方法
  18. 接口开发及技术负责人的职责随笔
  19. rabbitMq用guest登录失败解决办法
  20. 基于腾讯COS对象存储SDK使用Python编写的文件上传工具第二版

热门文章

  1. oracle dba 培训教程
  2. linux共享实体机硬盘,实现目录共享
  3. RAKsmart美国云服务器配置升级教程
  4. 2020年总结:携梦而行,无怨无悔
  5. HCIP第十六天(VLAN IF接口,STP生成树协议,BPDU的配置)
  6. 计算机在运行 显示器出现黑屏,显示器黑屏但电脑一直在运行是什么原因
  7. 短信验证码接口的应用场景和优势
  8. Debezium报错处理系列之三十六:Task threw an uncaught and unrecoverable exception. Task is being killed and will
  9. 为了直播焊接,我准备了这些装备
  10. PowerShell: 作为一个PowerShell菜鸟,如何快速入门?掌握这些就够了