什么是 ORAM

参考文献:

  1. [GO96] Goldreich O, Ostrovsky R. Software protection and simulation on oblivious RAMs[J]. Journal of the ACM (JACM), 1996, 43(3): 431-473.
  2. [Batcher68] Batcher K E. Sorting networks and their applications[C]//Proceedings of the April 30–May 2, 1968, spring joint computer conference. 1968: 307-314.
  3. [AKS83] Ajtai M, Komlós J, Szemerédi E. An 0 (n log n) sorting network[C]//Proceedings of the fifteenth annual ACM symposium on Theory of computing. 1983: 1-9.
  4. 从RAM到ORAM - 知乎 (zhihu.com)

文章目录

  • Oblivious RAM
    • ITM Model
    • Oblivious Simulation
  • The Square Root Solution
  • The Hierarchical Solution
    • Simple Case
    • General Case
    • Oblivious Hash

Oblivious RAM

Oblivious Random-Access Machine (ORAM) 是一种计算机模型,可以抵御(主动/被动)敌手观察到 “访存模式”。所谓 Oblivious 指的就是敌手无法区分不同的访存地址序列,只要这两个输入下程序的执行时间相同。ORAM 最初是用来做软件保护的,但之后在 MPC 等其他领域中大展拳脚(类似于 ZKP 的命运)。

应用场景:假设数据在内存中是加密的,敌手无法观察到内存中写入的数据是什么。但是,假如敌手可以观察到 CPU 的访存地址,那么就会泄露一定的信息(比如,相邻地址的数据被读/写了、某个地址的数据被访问了两次,等等)。

一个最简单的 ORAM 方案:假设在 RAM 中的程序,CPU 共执行 t t t 次访存,内存 MEM 包含 m m m 个 words,那么我们每次访存都:按顺序读取(即使 CPU 只写)并且写入(即使 CPU 只读)每一个字(即使这个 word 不读也不写),共计花费 t ⋅ m t\cdot m tm 次访存。这样,自然地掩盖了访存模式,因为所有的包含 t t t 次访存的序列,都是以完全相同的 t ⋅ m t \cdot m tm 次访存来实现的。

当然上述解决方案太愚蠢了(恐怖直立猿儿时的扳手指数数,就是个人形 ORAM)。一般地,访存次数 t t t 都远小于内存大小 m m m,因此上述方案的 m m m 倍减速是不可承受的。Goldreich 等人在 [GO96] 中给出了更高效的解决方案:

在这里插入图片描述

ITM Model

我们将 RAM 分解为两部分:CPU 以及 MEM,并将两者建模为 Interactive Turing Machine(ITM)。所谓 ITM 是一种 message-driven 的多带图灵机,按 rounds 对 “只读输入带、只写输出带、读写工作带、只读通信带、只写通信带” 进行一定的操作。简记 I T M ( c , w ) ITM(c,w) ITM(c,w) 表示工作带长度为 w w w(内存/寄存器的大小)、通信带长度为 c c c(每一轮的消息长度)的交互式图灵机。

在这里插入图片描述

这里的 ( i , a , v ) ∈ { 0 , 1 } 2 + k + O ( k ) (i,a,v) \in \{0,1\}^{2+k+O(k)} (i,a,v){0,1}2+k+O(k) 是由 CPU 发出的访存指令。

在这里插入图片描述

一般地,我们认为敌手可以观察 MEM 的各个 Cell 是否发生读写(观察通信带),但是敌手无法观察 CPU 内的 Register 是否发生读写。现在我们可以给出 (确定性)RAM 的定义,它包括一对 ITM 以及它们的交互:

在这里插入图片描述

但是对于 ORAM 的实现来说,必须要考虑随机性,比如 Random Oracle 或者 PRF。我们需要定义可以访问 Oracle 尤其是 RO 的 RAM 模型。在复杂度分析时,我们认为询问 Orcale 是 “free” 的。 下面给出 Probabilistic RAMs 的定义:

在这里插入图片描述

下面我们定义 memory access pattern 以及 Oblivious RAMs。访存模式是一个地址序列 ( a 1 , ⋯   , a t ) (a_1,\cdots,a_t) (a1,,at),其中 a i a_i ai 指定了 CPU 访问 MEM 时所请求的内存地址。

在这里插入图片描述

所谓的 “不经意”,就是说 RAM 执行过程中的访存模式不会泄露(前提是序列的长度本身是相同的),即敌手不可区分 CPU 访问 MEM 的指令中的内存地址序列。

在这里插入图片描述

Oblivious Simulation

现在,我们试图用 ORAM 来模拟任意一个 RAM,使得运行在 RAM 上的程序转变为运行在 ORAM 上的安全程序。基本要求是两者的函数性相同,安全性要求是后者应当是 Oblivious 执行的。另外,如果原始程序中,两个不同输入的运行时间(确定性的)相同,那么转换之后这两个输入的运行时间(随机变量的分布)也应该相同。

用 Probabilistic-RAM 不经意模拟 (确定性)RAM,定义如下:

在这里插入图片描述

对于 Orcale-RAM,可以类似的定义。注意区分两个神谕:原始 Orcale-RAM 的神谕、Probabilistic-RAM 使用到的 Random Oracle。

假设存在函数 g : N → N g:\mathbb N \to \mathbb N g:NN,对于任意的输入 y y y,若 R A M ( y ) RAM(y) RAM(y) 的运行时间为 T T T,不经意转换后 R A M ′ ( y ) RAM'(y) RAM(y) 的运行时间至多为 g ( T ) ⋅ T g(T)\cdot T g(T)T,那么我们就说 overhead of oblivious simulations 是函数 g g g(开销即减速倍率)

有时候,我们要求 CPU 知道它在时刻 j j j 之前访问地址 a a a 的 MEM Cell 的次数。这有助于抵御 tampering adversary 把 Cell 的内容替换为旧内容。我们说模拟是 Time-labeled 的,如果存在一个函数 Q ( j , a ) Q(j,a) Q(j,a) 正确计数了时刻 j j j 之前形如 ( s t o r e , a , ⋅ ) (store,a,\cdot) (store,a,) 的指令数。

软件保护的任务,可以归约到用 ORAM 模拟 RAM 的任务上。[GO96] 给出了两种 ORAM 的构造方法:平方根算法、层级算法。

The Square Root Solution

正如上面所说的,只要我每次都读写全部的内存单元,就没有人可以区分出我的访存模式!但是这导致了 m m m 倍的减速,为了降低损失,我们可以仿照 Cache 的思路,假如访问的数据就落在 Cache 内,我们只对 Cache 的内容做完全的扫描,就可以实现 Oblivious 的访存了。

对于一个 R A M m RAM_m RAMm,平方根算法使用一个 O R A M m + 2 m ORAM_{m+2\sqrt m} ORAMm+2m 来模拟它。不经意的两个问题:如何隐藏访存序列的次序(shuffle the memory)、如何隐藏访问该地址的次数(be accessed at most once),使用随机置换 π \pi π 来解决以上问题。

以示区分,我们将原始 RAM 的内存单元称为 virtual address/word,而将 ORAM 的内存单元称为 actual address/word。我们将 O R A M m + 2 m ORAM_{m+2\sqrt m} ORAMm+2m 的 MEM 的内存空间分为两部分:

  • Permuted Memory,大小为 m + m m+\sqrt m m+m ,其中的 m m m 个 Cell 存储原始 R A M m RAM_m RAMm 的 MEM 中存储的 m m m 个 word,另外的 m \sqrt m m 个 Cell 存储 dummy word,随机置换 π \pi π 作用在整个主存上
  • Shelter,大小为 m \sqrt m m ,作用类似于 Cache,随机置换 π \pi π 并不作用在 Cache 上

同时,令 ORAM 按照 epoch 将时间分片,每个时期 CPU 将执行 m \sqrt m m 次访存,然后对 MEM 重新执行一次随机置换,从而降低均摊成本。

在这里插入图片描述

初始化:将 ORAM 的 permuted memory 填充上 virtual words 以及 dummy words,将 shelter 的所有单元都置为 empty(注意区分 empty 和 dummy)

模拟过程:ORAM 按照 epochs 依次执行,每个 epoch 至多包含 m \sqrt m m 次 virtual address 的访存,

  1. 随机选择 [ m + m ] [m+\sqrt m] [m+m ] 上的置换 π \pi π,将 permuted memory 中的虚拟地址 i i i 上的 word,映射到实际地址 π ( i ) \pi(i) π(i),这儿的置换操作是 Oblivious 的

    1. n = m + m n=m+\sqrt m n=m+m ,通过访问 RO(或者 PRF τ : { 1 , 2 , ⋯   , n } → T n : = { 1 , 2 , ⋯   , n 2 / ϵ } \tau:\{1,2,\cdots,n\} \to T_n:=\{1,2,\cdots,n^2/\epsilon\} τ:{1,2,,n}Tn:={1,2,,n2/ϵ},其中 ϵ \epsilon ϵ 是碰撞概率,我们可以得到序列 { τ ( i ) : 1 ≤ i ≤ n } \{\tau(i):1\le i \le n\} {τ(i):1in},将它作为排序的 tag
    2. 我们使用 [Batcher68] 的排序网络(它是数据无关的,因此 Oblivious)根据这个 tag 对 permuted memory 排序,就完成了随机置换
    3. 在后续的访存时,因为 τ ( i ) \tau(i) τ(i) 是有序排列的,因此可以二叉搜索
  2. 每当 RAM 读/写虚拟地址 i i i 上的 word,假设这是第 j j j 次访存(用 CPU 寄存器 c o u n t count count 记录)

    1. ORAM 扫描 shelter 中的所有 Cell,查找是否存在 virtual word i i i(每个 Cell 同时存储了 virtual address 以及 virtual word),并使用 CPU 中的布尔寄存器 f o u n d found found 记录
    2. 如果没找到( f o u n d = 0 found=0 found=0),那么 CPU 访问 permuted memory 的位置 π ( i ) \pi(i) π(i),将对应的 word 存储到 CPU 寄存器里
    3. 如果找到了( f o u n d = 1 found=1 found=1),那么 CPU 把这个 word 存储到寄存器里,同时访问第 j j j 个 dummy word 假装访存(实际地址 π ( m + c o u n t ) \pi(m+count) π(m+count))以使得 step 2.2 和 step 2.3 的执行时间相同
    4. CPU 执行一定的计算后,将更新的数据 virtual word i i i(即使内容没发生改变)写入到 shelter 中,假如是新增数据项则找到 empty 位置写入,这个过程是 Oblivious 的(通过 scan 整个 shelter,并用 f o u n d found found 记录)
  3. 结束当前 epoch 时,将 shelter 中累积的脏数据写回到 permuted memory 中,这个过程也是 Oblivious 的

    1. 我们用 ( v , s ) (v,s) (v,s) 标记整个 MEM(包括 permuted memory 以及 shelter),其中 v v v 是 Cell 存储的 virtual address, s ∈ { 0 , 1 } s \in \{0,1\} s{0,1} 表示数据来自 permuted memory 还是 shelter(用于区分新/旧数据),对于 dummy 以及 empty 赋值 v = ∞ v=\infty v=
    2. 以字典序用 [Batcher68] 排序,使得 ( v , 0 ) , ( v , 1 ) (v,0),(v,1) (v,0),(v,1) 紧挨着(如果 word 更新过),扫描整个 MEM 清理旧数据 ( v , 0 ) (v,0) (v,0)(没更新过的不需清理)
    3. 再次 [Batcher68] 排序(现在的 tag 只有 v v v 了),将 MEM 分区为 permuted memory 以及 shelter(其实这次排序可以合并到 step 1 的随机置换中),现在的 shelter 重新是 empty 的了。

因为排序网络是数据无关的,step 1 和 step 3 是不经意的。由于 CPU 寄存器不可被敌手观察,因此 step 2.2 和 step 2.3 是不经意的。因为 CPU 扫描了整个 Shelter,因此 step 2.1 和 step 2.4 是不经意的。最终,平方根算法是一个 Oblivious Simulation,一个 epoch 计算复杂度为 O ( m log ⁡ 2 m ) + m ⋅ O ( 2 m + log ⁡ ( m + m ) ) O(m \log^2 m) + \sqrt m \cdot O(2\sqrt m + \log(m+\sqrt m)) O(mlog2m)+m O(2m +log(m+m ))均摊的减速 O ( m ⋅ log ⁡ 2 m ) O(\sqrt m \cdot \log^2 m) O(m log2m)

实际上,将 Shelter 的大小设置为 m ⋅ log ⁡ m \sqrt m \cdot \log m m logm 将达到最优化,使用渐进复杂度更低的 [AKS83] 排序网络(但是隐藏的常数很大)成本也可以更低。

The Hierarchical Solution

但是平方根算法的减速依然是 p o l y ( m ) poly(\sqrt m) poly(m ) 量级的,正如但是往往 m ≫ t m \gg t mt,导致大内存 RAM 的模拟开销巨大。假设我们用一个 powerful CPU,它拥有 m \sqrt m m 个寄存器,就可以把 shelter 移动到 CPU 里,从而不需要一次次 scan 了。不过这依然不能达到 p o l y ( log ⁡ ( t ) ) poly(\log(t)) poly(log(t)) 开销。

[G096] 给出了层次算法,使用不同大小的 Hash Table 排列成层次结构(类似于 L1 Cache、L2 Cache、Main Memory 的关系),越大的表更新频率越慢(类似于 Huffman Tree),可以将开销降低到仅仅 O ( log ⁡ 3 t ) O(\log^3 t) O(log3t)

在这里插入图片描述

Simple Case

现在我们考虑受限的情形:virtual memory 的所有 words 都至多被访问一次。简记 A = { ( V 1 , X 1 ) , ⋯   , ( V n , X n ) } A=\{(V_1,X_1),\cdots,(V_n,X_n)\} A={(V1,X1),,(Vn,Xn)} n n n 个字,其中 V i V_i Vi 是虚拟地址, X i X_i Xi 是数值。

  1. 创建一个具有 n n n 个 bucket 的 Hash table,每个桶的大小为 l = O ( log ⁡ n ) l=O(\log n) l=O(logn)(主要是为了降低数据溢出的概率,大小任意取)
  2. 使用 RO 计算哈希函数 h h h,将数据 ( V i , X i ) (V_i,X_i) (Vi,Xi) 存放到第 h ( V i ) h(V_i) h(Vi) 个桶的第一个 可用位置(假如溢出了,重新选择 h h h),这儿的 Hash 是以 Oblivious 方式执行的
  3. 当访问第 i i i 个数据时,扫描第 h ( V i ) h(V_i) h(Vi) 个桶里的全部 Cell,这必然可以找到数据 X i X_i Xi
  4. 数据更新时,直接对第 h ( V i ) h(V_i) h(Vi) 个桶里的 Cell 覆写即可

在这里插入图片描述

我们称 t t t-legal sequence 是一个虚拟地址访问模式,包括 t t t 次访存,且每个地址都仅被访问一次。对于虚拟地址 V 1 , ⋯   , V n V_1,\cdots,V_n V1,,Vn,只要做 Hash 映射后没有超过 l = O ( log ⁡ n ) l=O(\log n) l=O(logn) 个原像映射到同一个像,那么对于任意的 t ≤ n t \le n tn 的 legal sequence,上述 ORAM 的实际地址访问模式都是同分布的。即,对于受限情形,这个 ORAM 是 Oblivious 的。

General Case

现在我们考虑一般情况:访存模式中存在对于某地址的多次访问。只要我们将数据不断地使用新的 Hash Function 将数据(Oblivious)哈希到各个桶里,使得相邻两次 Hash 之间哈希表里的每个数据被至多访问一次,那么就转化为 Simple Case 了。我们使用多个大小不同的 Hash Table,并且它们的更新频率与大小成反比。

假设当前的访存模式长度为 t t t,我们申请 N = 1 + ⌈ log ⁡ t ⌉ N=1+\lceil\log t\rceil N=1+logtbuffers,第 i i i 个 buffer 包括 2 i 2^i 2i 个桶,每个桶的大小为 m = O ( log ⁡ t ) m=O(\log t) m=O(logt)(仅仅是为了防止数据溢出)。我们将各个 buffer 的时间也划分为 epochs,第 i i i 个 buffer 的时间长度为 2 i − 1 2^{i-1} 2i1(时间单位是访存次数,第一个哈希表的 epoch 长度为 1 1 1,但它并不主动重新散列;第二个哈希表每经过 2 2 2 次访存就重新散列,同时合并第一个哈希表的数据)。

我们令 h i j : { 0 , 1 } k → { 1 , 2 , ⋯   , 2 i } h_{ij}:\{0,1\}^k \to \{1,2,\cdots,2^i\} hij:{0,1}k{1,2,,2i} 是第 i i i 个 buffer 在它的第 j j j 个 epoch 使用的虚拟地址散列函数,它们相互独立且均匀随机,例如 h i j ( V ) : = f ( i , j , V ) ( m o d 2 i ) h_{ij}(V):=f(i,j,V) \pmod{2^i} hij(V):=f(i,j,V)(mod2i),这里 f f f 是个 RO。注意,随着时间 t t t 的推进,buffer 的数量 N N N 和 bucket 的大小 m m m 都在增加(在时刻 t = 2 i t=2^i t=2i),不过增加的频率指数级减慢。已经分配了内存的 buffer 不需要调整 m m m,毕竟它只是为了减少溢出概率,而上层的 buffer 中存储的数据量本来就不大。在时刻 t t t,大约消耗了 O ( t ⋅ log ⁡ 2 t ) O(t \cdot \log^2 t) O(tlog2t) 的存储空间。

在这里插入图片描述

初始化:

  1. 对于 R A M k ( y ) RAM_k(y) RAMk(y),初始设置 t = ∣ y ∣ t=|y| t=y,申请 N N N 个 buffers,它们的桶都是 empty 状态
  2. 使用 h N , 1 h_{N,1} hN,1,将输入数据 y y y 散列到第 N N N 个哈希表上(最底层的 main memory),它包含 2 N ≥ 2 ∣ y ∣ 2^N\ge 2|y| 2N2∣y 个桶(保持桶的数量至少大于所存储的虚拟数据个数)

模拟过程:时刻 t t t 原始 RAM 访问虚拟地址 V V V 的数据,

  1. ORAM 扫描第一个 buffer 全部的两个桶的 Cell,查找虚拟地址 V V V 的数据项(CPU 使用寄存器 f o u n d found found 记录),如果找到了就把 ( V , X ) (V,X) (V,X) 存储到寄存器中,并设置 f o u n d = 1 found=1 found=1
  2. 如果前 i − 1 i-1 i1 个 buffer 中都没找到( f o u n d = 0 found=0 found=0),那么继续扫描 i i i 个 buffer 的第 h i , ⌈ t / 2 i − 1 ⌉ ( V ) h_{i, \lceil t/2^{i-1}\rceil}(V) hi,t/2i1(V) 个桶(其他桶中不会存储虚拟地址 V V V 的数据),
  3. 如果前 i − 1 i-1 i1 个 buffer 中已经找到了( f o u n d = 1 found=1 found=1),那么随机扫描 i i i 个 buffer 任意一个桶(比如 h i , ⌈ t / 2 i − 1 ⌉ ( 0 , t ) h_{i, \lceil t/2^{i-1}\rceil}(0,t) hi,t/2i1(0,t)),使得运行时间相同。注意,每个 buffer 都要扫描,不允许提前终止
  4. 无论数据是否更新,CPU 都要将 ( V , X ) (V,X) (V,X) 写回 MEM,ORAM 把它 obliviously 写到第一个 buffer 里,如果数据项已经存在那么就覆写,否则写入一个 empty 位置
  5. 在时刻 t t t,令 i = 1 , 2 , ⋯   , max ⁡ { j : 2 j ∣ t } i=1,2,\cdots,\max\{j:2^{j} \mid t\} i=1,2,,max{j:2jt} 是那些当前 epoch 恰好结束的 buffers。我们将第 i i i 个 buffer(时期长度 2 i − 1 2^{i-1} 2i1)和第 i + 1 i+1 i+1 个 buffer(时期长度 2 i 2^{i} 2i)所存储的 virtual words 全都使用 h i + 1 , 1 + t / 2 i h_{i+1, 1+t/2^i} hi+1,1+t/2i 重新散列到第 i + 1 i+1 i+1 个 buffer 内,同时将第 i i i 个 buffer 的所有 actual words 都设为 empty(清理掉上层 cache 中累积的脏数据)。如果第 i + 1 i+1 i+1 个 buffer 不存在,那么就申请一个新的 buffer

注意,当 t t t 是奇数时 max ⁡ { j : 2 j ∣ t } = 0 \max\{j:2^{j} \mid t\}=0 max{j:2jt}=0,只有第 i = 1 i=1 i=1 个 buffer 的时期结束了,第二个 buffer 的时期还没结束,因此第 i + 1 i+1 i+1 个 buffer 的数据不需要重新散列(也不合并前 i i i 个 buffer 的数据)。此时 step 5 没有做任何事,虽然 h 1 , t h_{1,t} h1,t 被变更了(不影响 step 1 的 oblivious read,它同时扫描了两个桶)。

每经过 2 i + 1 2^{i+1} 2i+1 时间,第 i + 2 i+2 i+2 个 buffer 重新散列时将会清空前 i + 1 i+1 i+1 个 buffer。在第 ( 2 q + 1 ) ⋅ 2 i (2q+1) \cdot 2^i (2q+1)2i 时刻,前 i i i 个 buffer 中至多被写入了 2 i 2^i 2i 个脏数据,此时第 i + 1 i+1 i+1 个 buffer 是空的,重新散列将这 2 i 2^i 2i 个数据合并到第 i + 1 i+1 i+1 个 buffer(含有 2 i + 1 2^{i+1} 2i+1 个桶)中。然后在第 ( 2 q + 2 ) ⋅ 2 i (2q+2) \cdot 2^i (2q+2)2i 时刻,前 i i i 个 buffer 中的至多 2 i 2^i 2i 个数据以及第 i + 1 i+1 i+1 个 buffer 中的至多 2 i 2^i 2i 个数据合并到第 i + 1 i+1 i+1 个 buffer 中,紧接着就被合并到了第 i + 2 i+2 i+2 个 buffer 内。因此,第 i + 1 i+1 i+1 个 buffer 中所存储的巅峰数据量大小为 2 i + 1 2^{i+1} 2i+1,发生数据溢出的可能极小。同时由于每次 epoch 结束都更换 Hash 函数,因此碰撞也不会累积。

Oblivious Hash

现在我们实现模拟过程中 step 5 里的 Oblivious re-hash 功能。抽象地,有两个数组 A , B A,B A,B:数组 A A A 含有 n n n 个桶,存储了至多 n n n 个(新)数据;数组 B B B 含有 2 n 2n 2n 个桶,存储了至多 n n n 个(旧)数据。我们将这至多 2 n 2n 2n 个数据,散列到数组 B B B 内。

[GO96] 将数组 A , B A,B A,B 合并为长度 n m + 2 n m nm+2nm nm+2nm 的数组,通过 [AKS83] 排序网络,类似于平方根算法,清理掉旧数据。然后插入一些占位符,再次排序使得前 2 n m 2nm 2nm 前缀出现桶结构,截断为 merge 之后的数组 B B B

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

把上述的 Oblivious Hash 应用到层次算法的 re-hash 过程中,便得到了一个 ORAM。使用 AKS 排序网络,时间复杂度为 O ( t log ⁡ 3 t ) O(t \log^3 t) O(tlog3t),减速因子 O ( log ⁡ 3 t ) O(\log^3 t) O(log3t);使用 Batcher 排序网络,时间复杂度为 O ( t log ⁡ 4 t ) O(t \log^4 t) O(tlog4t),减速因子 O ( log ⁡ 4 t ) O(\log^4 t) O(log4t)。在整个 Oblivious Simulation 过程中,CPU 仅仅需要 3 3 3 个寄存器:两个用于存储 MEM 发送的数据(比较运算),一个用于 t t t 的计时(其实还有个布尔寄存器 f o u n d found found)。

[GO96] 给出了 ORAM 的高效构造:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/98364.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

206.Flink(一):flink概述,flink集群搭建,flink中执行任务,单节点、yarn运行模式,三种部署模式的具体实现

一、Flink概述 1.基本描述 Flink官网地址:Apache Flink — Stateful Computations over Data Streams | Apache Flink Flink是一个框架和分布式处理引擎,用于对无界和有界数据流进行有状态计算。 2.有界流和无界流 无界流(流): 有定义流的开始,没有定义结束。会无休止…

【webpack】HMR热更新原理

本文:参考文章 一、HMR是什么,为什么出现 1、出现的原因 之前,应用的加载、更新都是一个页面级别的操作,即使单个代码文件更新,整个页面都要刷新,才能拿到最新的代码同步到浏览器,导致会丢失…

【C语言】循环语句详解

✨个人主页: Anmia.🎉所属专栏: C Language 🎃操作环境: Visual Studio 2019 版本 目录 1.什么是循环结构? 2.while循环 while流程图 while语句中的break和continue break continue 3.for循环 for流…

滑动窗口系列4-Leetcode322题零钱兑换-限制张数-暴力递归到动态规划再到滑动窗口

这个题目是Leecode322的变种,322原题如下: 我们这里的变化是把硬币变成可以重复的,并且只有coins数组中给出的这么多的金币,也就是说有数量限制: package dataStructure.leecode.practice;import java.util.Arrays; i…

金融风控数据分析-信用评分卡建模(附数据集下载地址)

本文引用自: 金融风控:信用评分卡建模流程 - 知乎 (zhihu.com) 在原文的基础上加上了一部分自己的理解,转载在CSDN上作为保留记录。 本文涉及到的数据集可直接从天池上面下载: Give Me Some Credit给我一些荣誉_数据集-阿里云…

Docker搭建私有仓库并迁移

目录 方案 A、B机器安装docker 设置阿里云镜像源 安装 Docker-CE并设置为开机自动启动 A机器准备数据 拷贝数据 B机器运行redis、mysql镜像 重启docker服务 方案 准备两台机器:A机器(可以连接外网),B机器(内网机器…

python web GUI框架-NiceGUI 教程(一)

python web GUI框架-NiceGUI 教程(一) streamlit可以在一些简单的场景下仍然推荐使用,但是streamlit实在不灵活,受限于它的核心机制,NiceGUI是一个灵活的web框架,可以做web网站也可以打包成独立的exe。 基…

Docker 将容器打包成镜像推送镜像到仓库

Docker 将容器打包成镜像&推送镜像到仓库 一、将容器打包成镜像 $ docker commit <容器ID> <镜像名称:标签>示例&#xff1a; $ sudo docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS …

字节跳动推出AI对话工具“豆包”:免费用

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 听说松松客服的小马爆料了一个消息&#xff1a;字节跳动推出了一个新的AI大模型对话工具&#xff0c;叫做“豆包”。竟然一查发现&#xff0c;早在8月18号就已经上线了呢。原来这个“豆包”其实是之…

Kind创建本地环境安装Ingress

目录 1.K8s什么要使用Ingress 2.在本地K8s集群安装Nginx Ingress controller 2.1.使用Kind创建本地集群 2.1.1.创建kind配置文件 2.1.2.执行创建命令 2.2.找到和当前k8s版本匹配的Ingress版本 2.2.1.查看当前的K8s版本 2.2.2.在官网中找到对应的合适版本 2.3.按照版本安…

day30 日期转换

一&#xff1a;Date Date类&#xff1a; 这个类是java.util.Date getTime() : 获取内部维护的long值 Date date new Date(); long time date.getTime(); setTime()&#xff1a;按照指定的long值&#xff08;表示的时间&#xff09;设置Date表示的时间 time 60*60*24*1000;…

Android学习之路(11) ActionBar与ToolBar的使用

自android5.0开始&#xff0c;AppCompatActivity代替ActionBarActivity&#xff0c;而且ToolBar也代替了ActionBar&#xff0c;下面就是ActionBar和ToolBar的使用 ActionBar 1、截图 2、使用 2.1、AppCompatActivity和其对应的Theme AppCompatActivity使用的是v7的ActionBa…

快乐开源活动全面升级!提PR,赢PS5、Switch等缤纷好礼

快乐开源 活动升级 礼品升级 PS5、Switch、Apple、雷蛇、富士…… 开发者们想要的 我们安排&#xff01; 飞桨快乐开源活动旨在鼓励更多的开发者参与到飞桨社区的开源建设中&#xff0c;帮助修复 bug 或贡献 feature。活动起初只是一个「提 PR 领取新年礼物 &#x1f381;」…

python简介

Python 是一门优雅而健壮的编程语言&#xff0c;它继承了传统编译语言的强大性和通用性&#xff0c;同时也借鉴了脚本语言和解释语言的易用性。 Python 的历史 Python是由创始人贵铎范罗萨姆&#xff08;Guido van Rossum&#xff09;在阿姆斯特丹于1989年圣诞节期间&#xf…

多张图片转为pdf怎么弄?

多张图片转为pdf怎么弄&#xff1f;在网络传输过程中&#xff0c;为了避免图片格式文件出现差错&#xff0c;并确保图片的清晰度和色彩不因不同设备而有所改变&#xff0c;常见的做法是将图片转换为PDF格式。然而&#xff0c;当涉及到多张图片时&#xff0c;逐一转换将会变得相…

PyCharm切换虚拟环境

PyCharm切换虚拟环境 为了满足不同任务需要不同版本的包&#xff0c;可以在Anaconda或者Miniconda创建多个虚拟环境文件夹&#xff0c;并在PyCharm下切换虚拟环境。 解决方案 1、打开Ananconda Prompt 2、创建自己的虚拟环境 格式&#xff1a;conda create -n 虚拟环境名字…

虚拟机的使用

首先需要安装VMware软件&#xff0c;这是虚拟机&#xff0c;在里面可以实现在windows的笔记本上运行包括&#xff0c;windows11和linux系统的开发和研究。 VMware是一种虚拟化技术&#xff0c;可以让你在一台物理计算机上运行多个操作系统和应用程序&#xff0c;而不需要重启或…

如何在,Linux中安装Luajit2.*

1.文件下载The LuaJIT Project 2.将下载文件上传到对应的服务器&#xff1a;例如/opt 3.进入对应的文件夹 4.make PREFIX/usr/local&#xff0c;设置安装路径 5.make install&#xff0c;编译安装 6.进入安装目录&#xff0c;cd /usr/local/include/luajit-2.0 7.luajit -v…

目标检测笔记(十二):如何通过界面化操作YOLOv5完成数据集的自动标注

文章目录 一、意义二、修改源码获取三、自动标注前期准备四、开始自动标注五、可视化标注效果六、XML转换TXT 一、意义 通过界面化操作YOLOv5完成数据集的自动标注的意义在于简化数据标注的流程&#xff0c;提高标注的效率和准确性。 传统的数据集标注通常需要手动绘制边界框…

第三届计算机、物联网与控制工程国际学术会议(CITCE 2023)

第三届计算机、物联网与控制工程国际学术会议&#xff08;CITCE 2023) The 3rd International Conference on Computer, Internet of Things and Control Engineering&#xff08;CITCE 2023) 第三届计算机、物联网与控制工程国际学术会议&#xff08;CITCE 2023&#xff09;…