Go runtime剖析系列(一):内存管理

news2024/9/20 22:27:38

Go 的内存管理总览

程序在运行过程中会不断的调用分配器分配堆内存,同时也会通过赋值器更新内存数据,如果我们将内存看做为一个个小的对象集合,那程序的运行其实就是更改这个对象图,其中分配器在这个对象图中创建节点,赋值器更改节点之间的依赖关系,当我们从根节点(全局对象,栈对象)出发遍历对象图时,没有被遍历到的节点就会成为垃圾,等待 gc 的回收。

以上为 Go 的内存管理组件图,这篇文章主要是分析分配器的具体实现,同时也会对 gc 有所提及。

Go 的连续内存分配

在 1.10 及之前,Go 采用的连续内存管理,通过预先 reserve 一定大小的内存提升内存分配的效率,内存模型如下:

引用[2]

  • arena 这个区域就是讨论的堆区,为 512GB
  • bitmap 每 2bit 对应 arena 区域的 8Byte(一个指针大小),总大小为 512GB/64*2=16GB,存储了对应指针大小内存是否为一个指针以及 GC 扫描时是否需要继续扫描的标记
  • spans 管理内存的 mspan 结构的数组,每一个 mspan 结构管理一页(8KB),所以大小为 512GB/8KB*8BYTE=512MB

Go 的稀疏内存分配

在 Go1.11 版本之后,Go 更改了自己的内存分配策略,现在的内存模型如下:

go 的内存管理思想继承自 tcmalloc,mspan 管理一段连续内存,里面存储着相同大小的对象,每个 P(go 里面控制并发度的对象,一般为 GOMAXPROCS,每个内核线程 M 在运行前都会获取一个空闲的 P)都有自己的 mspan 缓存,其为一个 list,分别管理 8Byte,16Byte,32Byte...等大小对象,大部分内存分配可以直接从线程内维护的 mspan 链表获取空闲内存,不需要加锁,如果没有在本地找到合适的,就加锁并向 mcentral 申请,如果 mcentral 有相同 spanClass 的 mspan 链表不为空,则交割出一部分给 mcache,如果没有,mcentral 会向 mheap 申请,mheap 会检查本地并在没有时向系统申请 heaparena 并分配给 mcentral,接下来详细分析

mspan,mcache,mcentral,mheap,mheaparena 结构在内存分配中的作用和关系

mspan 结构

mspan 为内存分配的基本单元,用来管理连续内存(可能为 1 页,也可能为多页),与内存分配关系紧密的成员变量如下:

资料领取直通车:Golang云原生最新资料+视频学习路线

Go语言学习地址:Golang DevOps项目实战

1. next,prev 这两个变量将 mspan 结构构成了一个链表,在 mcache,mcentral 中通过 mSpanList{first:*mspan,last:*mspan}结构管理

2. startAddr,nPages 为这个 mspan 管理的连续内存区间[startAddr,startAddr+nPages*8k]

3. spanclass,elemsize,nelems,allocCount spanclass 为一个枚举,等于 size_class<<1 & noscan,其中 size_class 为 66+1 个,noscan 为一个 bit,表示这个 spanclass 内的对象是否有指针成员变量(因为 gc 扫描时需要对有指针成员变量的类型进行扫描以获取整个内存的对象图,此处分开可以减少扫描的内存大小,增加扫描效率),elemsize 为对象大小,nelems 为对象最大数量,nelems*elemsize=nPages*8k,allocCount 为已经分配的对象数量

4. allocBits,allocCache,freeIndex allowBits 是一个 bit 数组,大小为 nelems,bit=1 表示对应 index 已经被分配,allocCache 是 allowBits 某一段的映射,其是为了加速在 allocBits 中寻找空闲对象存在的,在 allocCache 中,bit=0 表示对应 index 已经被分配(和 allocBits 相反),freeIndex 是一个和 allocCache 挂钩的变量,其存储了 allocCache 最后一 bit 在 allocBits 的偏移量,对于 mspan 管理的 nelems 个对象,[0,freeIndex)已经全部被分配出去,[freeIndex,nelems)可能被分配,也可能是空闲的,每次 gc 后 freeIndex 置为 0,相关图如下。

引用[2]

//go:notinheap
type mspan struct {
 next *mspan     // next span in list, or nil if none
 prev *mspan     // previous span in list, or nil if none

 startAddr uintptr // address of first byte of span aka s.base()
 npages    uintptr // number of pages in span

 // freeindex is the slot index between 0 and nelems at which to begin scanning
 // for the next free object in this span.
 // Each allocation scans allocBits starting at freeindex until it encounters a 0
 // indicating a free object. freeindex is then adjusted so that subsequent scans begin
 // just past the newly discovered free object.
 //
 // If freeindex == nelem, this span has no free objects.
 //
 // allocBits is a bitmap of objects in this span.
 // If n >= freeindex and allocBits[n/8] & (1<<(n%8)) is 0
 // then object n is free;
 // otherwise, object n is allocated. Bits starting at nelem are
 // undefined and should never be referenced.
 //
 // Object n starts at address n*elemsize + (start << pageShift).
 freeindex uintptr
 // TODO: Look up nelems from sizeclass and remove this field if it
 // helps performance.
 nelems uintptr // number of object in the span.

 // Cache of the allocBits at freeindex. allocCache is shifted
 // such that the lowest bit corresponds to the bit freeindex.
 // allocCache holds the complement of allocBits, thus allowing
 // ctz (count trailing zero) to use it directly.
 // allocCache may contain bits beyond s.nelems; the caller must ignore
 // these.
 allocCache uint64

 // allocBits and gcBits hold pointers to a span's  and
 // allocation bits. The pointers are 8 byte aligned.
 // There are three arenas where this data is held.
 // free: Dirty arenas that are no longer accessed
 //       and can be reused.
 // next: Holds information to be used in the next GC cycle.
 // current: Information being used during this GC cycle.
 // previous: Information being used during the last GC cycle.
 // A new GC cycle starts with the call to finishsweep_m.
 // finishsweep_m moves the previous arena to the free arena,
 // the current arena to the previous arena, and
 // the next arena to the current arena.
 // The next arena is populated as the spans request
 // memory to hold gcBits for the next GC cycle as well
 // as allocBits for newly allocated spans.
 //
 // The pointer arithmetic is done "by hand" instead of using
 // arrays to avoid bounds checks along critical performance
 // paths.
 // The sweep will free the old allocBits and set allocBits to the
 // gcBits. The gcBits are replaced with a fresh zeroed
 // out memory.
 allocBits  *gcBits

 allocCount  uint16        // number of allocated objects
 spanclass   spanClass     // size class and noscan (uint8)
 elemsize    uintptr       // computed from sizeclass or from npages

 ...
}

mcache 结构

mcache 结构和每个 P 挂钩,通过保存 mspan 的链表作为局部缓存提供不需要加锁就能进行内存分配的能力,与内存分配关系紧密的成员变量如下: 1. alloc mspan 的链表数组,数组大小为 134=(66+1)*2,以 spanclass 作为数组索引 2. tiny,tinyoffset go 用来分配微对象使用的变量,go 会将小于 16Byte 的非指针数据当作微对象,会将其聚合为一个 16Byte 的对象进行统一存储,其中 tiny 为这个 16Byte 对象的首地址,tinyoffset 为已经被使用的字节数

// Per-thread (in Go, per-P) cache for small objects.
// No locking needed because it is per-thread (per-P).
//
// mcaches are allocated from non-GC'd memory, so any heap pointers
// must be specially handled.
//
//go:notinheap
type mcache struct {
 // tiny points to the beginning of the current tiny block, or
 // nil if there is no current tiny block.
 //
 // tiny is a heap pointer. Since mcache is in non-GC'd memory,
 // we handle it by clearing it in releaseAll during 
 // termination.
 tiny             uintptr
 tinyoffset       uintptr

 // The rest is not accessed on every malloc.

 alloc [numSpanClasses]*mspan // spans to allocate from, indexed by spanClass

 ...
}

mcentral 结构

mcentral 保存在全局的 mheap 对象中,每一个 spanClass 对应一个 mcentral,mcentral 维护了和自己 spanClass 相等的 mspan 列表,访问 mcentral 需要加锁,与内存分配关系紧密的成员变量如下: 1. lock 锁变量,访问 mcentral 结构需要锁 2. spanClass 等于 mcentral 结构维护的 mspan 链表的 spanClass 3. noempty,empty 两个 mspan 链表,empty 链表里面的 mspan 要么所有对象已经被分配,要么整个 mspan 已经移交给 mcache,noempty 里面保存着还存在空闲对象的 mspan 4. nmalloc 已分配对象数的粗略估计,其假定交割给 mcache 的 mspan 最后都会被完全分配出去

type mcentral struct {
 lock      mutex
 spanclass spanClass
 nonempty  mSpanList // list of spans with a free object, ie a nonempty free list
 empty     mSpanList // list of spans with no free objects (or cached in an mcache)

 // nmalloc is the cumulative count of objects allocated from
 // this mcentral, assuming all spans in mcaches are
 // fully-allocated. Written atomically, read under STW.
 nmalloc uint64
}

以上所有数据结构是 mheap 用来给用户程序分配内存涉及到的数据结构,并不会涉及到实际的与操作系统的交互(sysAlloc),下面为 mheap 实际用来管理最多 256TB 堆区内存的数据结构

heapArena 结构

由于 Go 在现版本采用了稀疏内存管理,堆区的所有内存可能为不连续的,go 将堆区内存视为一个个内存块,每一个内存块的大小为 64MB,并使用 heapArena 结构进行管理,同时在 mheap 全局结构中维护 heapArena 的指针数组,大小为 4M,这使 go 能管理的内存扩大到 256TB,在 heapArena 结构中,与内存分配关系紧密的成员变量如下: 1. bitmap 作用等于 go 在 1.10 版本之前的 bitmap 区,采用 2bit 管理一个 8Byte(x64 平台下的指针大小),所以 1Byte 可以管理 4 个指针大小数据,低 4 位为这四个指针大小数据是否为指针,高 4 位为 gc 扫描时是否需要继续往后扫描,主要用于 GC 2. spans mspan 数组,go 语言中会为每一页指定一个 mspan 结构用来管理,所以数组大小为 64MB/8KB=8192 3. pageInUse,pages gc 使用的字段,pageInUse 表示相关的页的状态为 mSpanInUse(GC 负责垃圾回收,与之相对的是 goroutine 的栈内存,由栈管理),pages 表示相关页是否存在有效对象(在 gc 扫描中被标记),用来加速内存回收 4. zeroedBase 用来加速第一次分配,在这之后的内存已经是 0,在分配给用户程序程序时不再需要额外置 0,本身单调递增

// A heapArena stores metadata for a heap arena. heapArenas are stored
// outside of the Go heap and accessed via the mheap_.arenas index.
//
//go:notinheap
type heapArena struct {
 // bitmap stores the pointer/scalar bitmap for the words in
 // this arena. See mbitmap.go for a description. Use the
 // heapBits type to access this.
 bitmap [heapArenaBitmapBytes]byte

 // spans maps from virtual address page ID within this arena to *mspan.
 // For allocated spans, their pages map to the span itself.
 // For free spans, only the lowest and highest pages map to the span itself.
 // Internal pages map to an arbitrary span.
 // For pages that have never been allocated, spans entries are nil.
 //
 // Modifications are protected by mheap.lock. Reads can be
 // performed without locking, but ONLY from indexes that are
 // known to contain in-use or stack spans. This means there
 // must not be a safe-point between establishing that an
 // address is live and looking it up in the spans array.
 spans [pagesPerArena]*mspan

 // pageInUse is a bitmap that indicates which spans are in
 // state mSpanInUse. This bitmap is indexed by page number,
 // but only the bit corresponding to the first page in each
 // span is used.
 //
 // Reads and writes are atomic.
 pageInUse [pagesPerArena / 8]uint8

 // pages is a bitmap that indicates which spans have any
 // ed objects on them. Like pageInUse, only the bit
 // corresponding to the first page in each span is used.
 //
 // Writes are done atomically during ing. Reads are
 // non-atomic and lock-free since they only occur during
 // sweeping (and hence never race with writes).
 //
 // This is used to quickly find whole spans that can be freed.
 //
 // TODO(austin): It would be nice if this was uint64 for
 // faster scanning, but we don't have 64-bit atomic bit
 // operations.
 pages [pagesPerArena / 8]uint8

 // zeroedBase s the first byte of the first page in this
 // arena which hasn't been used yet and is therefore already
 // zero. zeroedBase is relative to the arena base.
 // Increases monotonically until it hits heapArenaBytes.
 //
 // This field is sufficient to determine if an allocation
 // needs to be zeroed because the page allocator follows an
 // address-ordered first-fit policy.
 //
 // Read atomically and written with an atomic CAS.
 zeroedBase uintptr
}

mheap 结构

mheap 结构在全局具有唯一对象,用来协调整个内存管理,与内存分配关系紧密的成员变量如下: 1. allspans 已经分配的所有 mspan 对象 2. arenas heapArena 数组,在 linux x64 平台下为 4M 大小,总共管理 256TB 的内存 3. central mcentral 数组,大小为 134=(66+1)*2 4. spanalloc,cachealloc... 为分配 mspan,mcache...等固定元信息结构的分配器,类似于 linux 内核对 task_struct 等结构的处理,采用链表串联所有空闲对象,free 时直接加入链表,分配时从链表取出,没有就调用 sysAlloc 函数,分配的内存不受 gc 管理

内存分配流程

前面铺垫了这么多,此处我们开始进入了内存分配的流程解析,go 语言在堆上分配内存的函数入口为 runtime.newobject,这可以通过两种方式确定,一是在 src/cmd/compile/internal/gc/ssa.go 的(*state).expr 函数中,可以看到对 ONEWOBJ 的节点类型(go 在逃逸分析后确定要将对象分配在堆上会构建此节点)调用了 newobject 函数,第二种方法更简单,如下所示:

package main

func F() *int {
 var x int
 return &x
}

func main() {
 F()
}

编写一段这样的代码,然后运行 go tool compile -S xx.go,查看生成的 F 函数的汇编代码,可以明确的确是调用了 runtime.newobject 函数,newobject 函数简单的调用了 mallocgc 函数,mallocgc 函数的流程如下: 1. 检查 gcBlackenEnabled 开关,这个开关会在 gc 处于_GC 阶段时开启,主要是用来限制用户分配内存的速度不能超过 gc 的速度,采用的是一种借贷-还债的策略(称为 gcAssist),略过 2. 根据 size 和 noscan 标记决定走微对象(大小为(0,16)且不含指针)分配,小对象[8,32k)分配,大对象(>32k)分配流程 3. 如果 noscan=false(即对象包含指针数据),需要调用 heapBitsSetType 更新 heapArena 的 bitmap 数组(采用 2bit 管理 8Byte 数据),为 gc 服务,略过 4. 调用 publicationBarrier 推一个内存同步,主要解决多线程模型下的同步问题,略过 5. 如果当前不处于_GCoff 阶段,需要给分配的新对象打上标记,防止被 gc 错误回收,略过 6. 如果 assistG!=nil,更新 gcAssist 相关数据,略过 7. 检查是否需要启动新一轮 gc,略过 下面我们分别分析微对象,小对象,大对象的内存分配

小对象([8,32k])内存分配

  1. 先根据 size 算出 sizeclass,再和 noscan 标记算出 spanClass,go 中的 sizeclass 总共为 66+1 种,每个 sizeclass 对应不同的对象大小和 mspan 跨越的页数,同时因为可能需要将对象大小向上扩张,所以可能会导致内存浪费,相关数据如下图所示(sizeclass=0 为大对象对应的 sizeclass,其大小不固定):
classbytes/objbytes/spanobjectstailwastemaxwaste

2.调用 nextFreeFast 快速分配空闲对象,nextFreeFast 会检查 allocCache 中是否存在非 0 字段(通过 sys.Ctz64 函数计算二进制下的 0 数量),如果不等于 64,说明存在,更新 allocCache 和 freeIndex,否则返回失败

3.调用 mcache.nextFree 分配空闲对象,mcache.nextFree->mcache.refill->mcentral.cachespan

    1. 调用 deductSweepCredit,里面会检查是否需要清理内存(Go 的内存回收是后台 goroutine+惰性回收策略),先略过
    2. 检查 noempty 链表是否存在合适的 mspan,mspan.sweepgen 是一个和 gc 周期挂钩的变量,mheap.sweepgen 在每次 gc 中增长 2, mspan.sweepgen=mheap.sweepgen-2 表示还未被清扫,mspan.sweepgen=mheap.sweepgen-1 表示已经正处于清扫中,如果在 noempty 链表没找到合适的 mspan,便在 empty 链表寻找
    3. 如果都没找到,调用 mcentral.grow 扩张,mcentral.grow 会先调用 mheap.alloc->mheap.allocSpan 分配 mspan,然后调用 heapBitsForAddr 初始化 heapArena 的 bitmap 信息,对于指针会全部置为 1(用于 gc 扫描)
    4. 无论通过何种方式找到 mspan,都会调用 mspan.refillAllocCache 更新自身的 allocCache 和 freeIndex 变量

mheap.allocSpan 如果可以会先检查当前 P 的页缓存(p.pcache)是否能提供这次分配(不需要加锁),否则加锁并请求页分配器(mheap.pages)分配一定页数,如果页分配器无法满足此次分配,将调用 mheap.grow 扩展堆之后重新请求 mheap.pages 分配,分配后设置 mspan,mheap 相关字段。

mheap.grow 里面调用了 sysAlloc 函数,sys*函数是 go 用来管理虚拟内存状态的函数,go 里面把内存的状态分为 Reserved,Prepared,Ready,相关的状态转换所需要的函数为:

sysAlloc 里面调用了 sysReserve 和 sysMap 函数直接把内存状态转为 Prepared,并将分配得到的内存放入 mheap.pages 里面。

微对象((0,16)且不含指针)内存分配

go 将大小小于 16Byte 且不含指针的数据当作微对象,将其聚合在一个 16Byte 中进行存储 spanClass=5(sizeClass=2,nospan=1)。

if noscan && size < maxTinySize {
    // 取上次分配的起始offset,并进行对齐
    off := c.tinyoffset
    if size&7 == 0 {
        off = alignUp(off, 8)
    } else if size&3 == 0 {
        off = alignUp(off, 4)
    } else if size&1 == 0 {
        off = alignUp(off, 2)
    }
    // 如果上次分配的tiny block(16Byte)剩余空间足够,直接分配
    if off+size <= maxTinySize && c.tiny != 0 {
        // The object fits into existing tiny block.
        x = unsafe.Pointer(c.tiny + off)
        c.tinyoffset = off + size
        c.local_tinyallocs++
        mp.mallocing = 0
        releasem(mp)
        return x
    }
    // 重新分配一个新的tinyblock,并分配
    span := c.alloc[tinySpanClass]
    v := nextFreeFast(span)
    if v == 0 {
        v, _, shouldhelpgc = c.nextFree(tinySpanClass)
    }
    x = unsafe.Pointer(v)
    (*[2]uint64)(x)[0] = 0
    (*[2]uint64)(x)[1] = 0
    if size < c.tinyoffset || c.tiny == 0 {
        c.tiny = uintptr(x)
        c.tinyoffset = size
    }
    size = maxTinySize
}

大对象(>32k)内存分配

大对象内存分配为调用 largeAlloc 函数,其流程为: 1. 调用 deductSweepCredit 函数,检查是否需要释放内存 2. 调用 mheap.alloc 直接分配对应页数 3. 初始化 mspan 结构

内存回收流程

go 的内存回收入口函数为 sweepone->mspan.sweep 函数,其调用方为:

1. 后台 goroutine(runtime.bgsweep 函数);

2. 分配新内存时检查是否需要释放内存,入口函数为 deductSweepCredit,在 largeAlloc(分配大对象)和 mcentral.cacheSpan(微对象和小对象在 mcache 中找不到合适的内存时调用)中被调用;

3. gc 启动时会检查是否上次 gc 还有未被回收的内存,在 runtime.GC 函数(用户手动调用 GC 回收时被调用)和 gcStart(go runtime 检查需要触发新一轮 gc 时被调用)中被调用。 mspan.sweep 的流程为:

构函数 Finalizer(用户程序可以通过调用 runtime.SetFinalizer 函数设置),如果有调用; 2. 更新 mspan 状态,主要是把 allocBits 设置为 gcBits 表示相关对象已经被释放,然后调用 refillAllocCache 重新初始化 mspan.allocCache 和 mspan.freeIndex 用来加速下一次分配。

总结

go 的内存分配采用类似 TCMalloc 的策略,分别对微对象,小对象,大对象进行分别处理,并且通过 mcache,p.pcache 等线程关联缓存减少了锁的请求量,提高了效率。

go 的内存分配器是 go runtime 的核心组件之一,内存分配器和垃圾回收器之间的联系非常紧密,有大量的代码穿插在两个组件中间,所以要完整的理解内存分配器的需要对 gc 的原理进行深入分析。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Flutter 的缓存策略

Flutter 的缓存策略 原文 https://medium.com/romaingreaume/implementing-a-cache-strategy-in-your-flutter-app-5db3e316e7c9 前言 在移动应用程序中&#xff0c;缓存管理是一件非常重要的事情。 在本文中&#xff0c;我将告诉您如何在我的公司 Beapp 中设置策略缓存。 正文…

ES-分词器

简介 分词器是es中的一个组件&#xff0c;通俗意义上理解&#xff0c;就是将一段文本按照一定的逻辑&#xff0c;分析成多个词语&#xff0c;同时对这些词语进行常规化的一种工具&#xff1b;ES会将text格式的字段按照分词器进行分词&#xff0c;并编排成倒排索引&#xff0c;…

Vben Admin 的介绍以及使用

Vben Admin 的介绍以及使用初识 Vben Admin一、安装vue-vben-admin二、vben admin 目录介绍1.运行项目好用的插件启动项目打开终端三 项目分析1. 路由配置新增路由四 : vben admin 的使用初识 Vben Admin Vben Admin官方文档地址 好像官网挂了 !! 嘤嘤嘤嘤嘤 因为最近业务需要…

蓝牙无线自制串口模块连接穿越机配置工具

蓝牙无线自制串口模块连接穿越机配置工具1. 目的2. 验证环境3. BLE SPP验证4. BT SPP验证5. 参考资料穿越机配置工具 SpeedyBeeBetaFlightConfigurator 目前&#xff0c;市面上连接BetaFlight给飞控固件调参比较顺手的工具大体上是官网的BetaFlight Configurator(国内都叫BF地…

OFD转PDF ~java实现

OFD转PDF ~java实现 当你在查这个问题时&#xff0c;说明大家都已经接触到OFD这个新的版式文档了。OFD格式对标的PDF&#xff0c;目前关于PDF的转换都比较成熟&#xff0c;但是当OFD出现后&#xff0c;切发现能实现转换却似乎有所困难。尝试网上搜了一下这方面的资料&#xff…

别再当大冤种了,揭开3D建模报班6个常见套路

1⃣承诺有就业保障 任何机构给你承诺✊所谓的包就业都是耍流氓&#x1f621;&#xff0c;请不要信❗️ 承诺就业保障的机构无非3种情况&#xff1a; &#x1f539;赚取学员的信任感&#xff0c;吸引学员报名和交钱&#xff1b; &#x1f538;提高机构的“专业度”和所谓的“…

JWT详解(文章内嵌jwt工具类)

JWT 基础概念详解&#xff0c;工具类和使用方法放在最后 什么是 JWT? JWT &#xff08;JSON Web Token&#xff09; 是目前最流行的跨域认证解决方案&#xff0c;是一种基于 Token 的认证授权机制。 从 JWT 的全称可以看出&#xff0c;JWT 本身也是 Token&#xff0c;一种规…

【现代密码学原理】——消息认证码(学习笔记)

&#x1f4d6; 前言&#xff1a;消息认证码 MAC&#xff08;Message Authentication Code&#xff09;是经过特定算法后产生的一小段信息&#xff0c;检查某段消息的完整性&#xff0c;以及作身份验证。它可以用来检查在消息传递过程中&#xff0c;其内容是否被更改过&#xff…

Appium 移动端自动化测试(Mac)

目录 adb调试工具&#xff08;Android Debug Bridge&#xff09; adb常用命令 Appium使用 快速体验步骤 Appium常用方法 UIAutomatorViewer 获取元素 元素等待&#xff08;在指定时间内一直等待 元素操作 Swipe 滑动和拖拽事件&#xff08;Scroll、drag_and_drop&#…

pytorch深度学习实战19

第十九课 卷积层的填充和步幅 目录 理论部分 实践部分 理论部分 首先看一下卷积层的填充。 上图所示的情况会有个问题&#xff0c;如果卷积核不变的话&#xff08;一直是5*5的卷积核&#xff09;&#xff0c;那么我的网络最多也就只能弄到第七层了&#xff0c;如果我想搭建更…

恒太照明在北交所上市:募资规模缩水三成,第三季度收入下滑

11月17日&#xff0c;江苏恒太照明股份有限公司&#xff08;下称“恒太照明”&#xff0c;NQ:873339&#xff09;在北京证券交易所&#xff08;即“北交所”&#xff09;上市。本次上市&#xff0c;恒太照明的发行价格为6.28元/股&#xff0c;发行数量为2220万股&#xff0c;募…

Linux|centos7下部署安装alertmanager并实现邮箱和微信告警

前言&#xff1a; 一个成熟的符合一般人预想的资源监控平台应该是能够多维度的展示各种各样的数据&#xff0c;比如&#xff0c;服务器的内存使用率&#xff0c;磁盘io状态&#xff0c;磁盘使用率&#xff0c;CPU的负载状态&#xff0c;某个服务的状态&#xff08;比如&#x…

Pandas数据分析33——数据多条件筛选(点估计和区间估计评价指标)

本次是写论文代码区间估计评价指标有感..... 数据框有两列的时候&#xff0c;我们想筛选A列大于B列的样本出来时&#xff0c;只需要用布尔值索引就行&#xff1a; df[df[A]>df[B]] 可是多条件的筛选的时候怎么办&#xff0c;比如我需要A大于B列&#xff0c;还有A小于C列。…

软件测试“摆烂”已经成为常态化,我们应该怎样冲出重围?

网络日新月异发展的今天&#xff0c;每隔一段时间就会出现一些新的网络热词&#xff0c;最近“摆烂”成功突出重围&#xff0c;成为大家热议的中心。什么是“摆烂”&#xff1f;“摆烂”就是事情无法向好发展的时候直接选择妥协&#xff0c;不采取任何措施加以改变&#xff0c;…

兼容 信创鲲鹏/M1 arm64架构的kafka镜像

当前热度比较高的kafka镜像是wurstmeister/kafka&#xff0c;在dockerhub上有很多的使用次数。我起初很开心最新版支持arm64架构&#xff0c;然后拉到本地用Mac M1跑也很完美 但是&#xff01;我放到信创鲲鹏的生产环境&#xff0c;导入镜像没问题&#xff0c;但一用docker-com…

IPWorks Zip Delphi 流式压缩组件

IPWorks Zip Delphi 流式压缩组件 IPWorks Zip允许开发人员使用Zip、Tar、Gzip、7-Zip、Bzip2、ZCompress或Jar压缩标准轻松地将压缩和解压缩集成到应用程序中。IPWorks Zip组件是从头开始构建的&#xff0c;将卓越的速度与出色的数据压缩比相结合。 IPWorks Zip功能 文件压…

Allegro如何给铜皮导弧操作详解

Allegro如何给铜皮导弧操作详解 当需要给如下铜皮导弧的,是一件比较麻烦的事情,但是可以用以下两个方法来实现 方法一 具体操作如下 shape-decompose shape Find选择shapes Options选择层面,比如top层,选择delete shape after decompose 框选铜皮 得到下图效果,然后…

SNMP 协议解析(一)

♥️作者&#xff1a;小刘在C站 ♥️每天分享课堂笔记&#xff0c;一起努力&#xff0c;共赴美好人生&#xff01; ♥️夕阳下&#xff0c;是最美的&#xff0c;绽放。 一.SNMP是什么 是基于TCP/IP协议族的网络管理标准&#xff0c;它的前身是简单网关监控协议(SGMP)&#xf…

HOST前后端分离小操作

“本地测试项目配置域名小操作” ​ 相关文章【欢迎关注该公众号“一路向东”】&#xff08;CORS处理跨域问题&#xff09;&#xff1a; CORS跨域问题配置白名单CORS_ORIGIN_WHITELIST HOSTS 本地测试域名必然少不了修改&#xff1a;C:/Windows/System32/driver/etc/host文件&…

MVVM的构建(javakotlin)

概念性的描述就不写了&#xff0c;直接上代码 MVVM框架&#xff0c;主要是构建基类&#xff0c;实现ViewModel绑定&#xff0c;ViewBinding的绑定&#xff0c;在构建好基类后&#xff0c;后续开发中新建activity或fragment的viewModel和viewBinding就会在基类的方法中实现绑定…