go内存返还系统相关代码

在go中内存返还系统相关代码主要由sysUnusedOS实现

在Linux中默认是通过madvice方法的_MADV_FREE进行释放,在这种释放中内存其实是被延迟回收的。

func sysUnusedOS(v unsafe.Pointer, n uintptr) {if uintptr(v)&(physPageSize-1) != 0 || n&(physPageSize-1) != 0 {// 如果地址或大小不是物理页大小的整数倍,则抛出异常throw("unaligned sysUnused")}advise := atomic.Load(&adviseUnused)if debug.madvdontneed != 0 && advise != madviseUnsupported {advise = _MADV_DONTNEED}switch advise {case _MADV_FREE:// 尝试使用 MADV_FREE 提示操作系统这些内存页可以被释放,但释放可能会延迟,直到出现内存压力if madvise(v, n, _MADV_FREE) == 0 {break}// 如果 MADV_FREE 不被支持,则设置 adviseUnused 为 _MADV_DONTNEEDatomic.Store(&adviseUnused, _MADV_DONTNEED)fallthroughcase _MADV_DONTNEED:// 使用 MADV_DONTNEED 提示操作系统这些内存页可以被释放。 内核可以自由地延迟释放页面,直到适当的时刻。 然而,调用进程的驻留集大小(RSS)将立即减少。if madvise(v, n, _MADV_DONTNEED) == 0 {break}// 如果 MADV_DONTNEED 不被支持,则设置 adviseUnused 为 madviseUnsupportedatomic.Store(&adviseUnused, madviseUnsupported)fallthroughcase madviseUnsupported:// 如果 madvise 不被支持,则使用 mmap 重新映射内存区域以释放内存mmap(v, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_FIXED|_MAP_PRIVATE, -1, 0)}// 如果硬件提交被启用,则设置内存区域的权限为无if debug.harddecommit > 0 {p, err := mmap(v, n, _PROT_NONE, _MAP_ANON|_MAP_FIXED|_MAP_PRIVATE, -1, 0)if p != v || err != 0 {throw("runtime: cannot disable permissions in address space")}}
}

sysUnusedOS被scavengeOne调用,scavengeOne在给定的内存块(chunk)中搜索可回收的连续页面,并尝试回收指定数量的内存

值得注意的是,在回收之后内存还是可以再次分配的

//go:systemstack
// ci:要操作的块索引。
// searchIdx:在块中开始搜索的页面索引。
// max:最多要回收的字节数。
func (p *pageAlloc) scavengeOne(ci chunkIdx, searchIdx uint, max uintptr) uintptr {// 回收的最大页面数计算maxPages := max / pageSizeif max%pageSize != 0 {maxPages++}// 最少要回收的页面数计算minPages := physPageSize / pageSizeif minPages < 1 {minPages = 1}lock(p.mheapLock)// 如果在块中有至少minPages个空闲页面,继续操作if p.summary[len(p.summary)-1][ci].max() >= uint(minPages) {// 查找可回收的页面范围base, npages := p.chunkOf(ci).findScavengeCandidate(searchIdx, minPages, maxPages)if npages != 0 {// 计算起始位置addr := chunkBase(ci) + uintptr(base)*pageSize// 标记页面范围为已分配,以防止在回收过程中被其他协程分配p.chunkOf(ci).allocRange(base, npages)// 标记为连续内存已分配p.update(addr, uintptr(npages), true, true)// With that done, it's safe to unlock.unlock(p.mheapLock)// 如果不是测试模式,执行系统回收操作,包括调用sysUnused释放系统内存,并更新全局统计信息if !p.test {pageTraceScav(getg().m.p.ptr(), 0, addr, uintptr(npages))sysUnused(unsafe.Pointer(addr), uintptr(npages)*pageSize)// Update global accounting only when not in test, otherwise// the runtime's accounting will be wrong.nbytes := int64(npages * pageSize)gcController.heapReleased.add(nbytes)gcController.heapFree.add(-nbytes)stats := memstats.heapStats.acquire()atomic.Xaddint64(&stats.committed, -nbytes)atomic.Xaddint64(&stats.released, nbytes)memstats.heapStats.release()}// Relock the heap, because now we need to make these pages// available allocation. Free them back to the page allocator.// 重新加锁,将回收的页面重新标记为可分配,并更新页面分配器的状态,标记页面范围为已回收lock(p.mheapLock)// 更新 searchAddr 以优化下一次内存分配的起始搜索地址if b := (offAddr{addr}); b.lessThan(p.searchAddr) {p.searchAddr = b}// 释放从 base 地址开始的 npages 页内存p.chunkOf(ci).free(base, npages)// 更新内存管理器的内部数据结构,表明这些连续页已经释放p.update(addr, uintptr(npages), true, false)// 将这些页标记为已回收p.chunkOf(ci).scavenged.setRange(base, npages)unlock(p.mheapLock)return uintptr(npages) * pageSize}}// Mark this chunk as having no free pages.p.scav.index.setEmpty(ci)unlock(p.mheapLock)return 0
}

scavengeOne继而被scavenge调用

该过程分块进行,从最高地址开始,一直持续到清除指定字节数(nbytes)或耗尽堆。如果需要的话,它还可以通过忽略大页面启发式方法来强制清理。

func (p *pageAlloc) scavenge(nbytes uintptr, shouldStop func() bool, force bool) uintptr {released := uintptr(0)// 持续直到请求的nbytes被清除或堆耗尽for released < nbytes {// 找到清除候选者ci, pageIdx := p.scav.index.find(force)if ci == 0 {break}// 执行清除systemstack(func() {released += p.scavengeOne(ci, pageIdx, nbytes-released)})if shouldStop != nil && shouldStop() {break}}return released
}

以下逐个说明哪些地方调用了系统内存返还

allocSpan

allocSpan用于分配mspan。

该 mspan 拥有 npages 个页面的内存。它根据给定的分配类型(typ)和跨度类(spanclass)来分配和初始化跨度,并进行内存清理和统计更新。这个函数需要在系统堆栈上调用,以确保堆锁和垃圾收集的安全性

//go:systemstack
func (h *mheap) allocSpan(npages uintptr, typ spanAllocType, spanclass spanClass) (s *mspan) {// Function-global state.gp := getg()base, scav := uintptr(0), uintptr(0)growth := uintptr(0)// 检查是否需要物理页面对齐(针对某些平台的堆栈分配)needPhysPageAlign := physPageAlignedStacks && typ == spanAllocStack && pageSize < physPageSize// If the allocation is small enough, try the page cache!// The page cache does not support aligned allocations, so we cannot use// it if we need to provide a physical page aligned stack allocation.// 尝试使用页面缓存分配pp := gp.m.p.ptr()// 如果页缓存未对齐或页数较小,尝试从页缓存分配内存if !needPhysPageAlign && pp != nil && npages < pageCachePages/4 {c := &pp.pcache// If the cache is empty, refill it.// 如果页缓存为空,则从全局页池填充if c.empty() {lock(&h.lock)*c = h.pages.allocToCache()unlock(&h.lock)}// Try to allocate from the cache.// 如果成功从页面缓存分配,则尝试获取 mspanbase, scav = c.alloc(npages)if base != 0 {s = h.tryAllocMSpan()if s != nil {goto HaveSpan}// We have a base but no mspan, so we need// to lock the heap.}}// 锁定堆进行分配// For one reason or another, we couldn't get the// whole job done without the heap lock.lock(&h.lock)// 如果需要物理页面对齐,则进行额外页面的分配以确保对齐if needPhysPageAlign {// Overallocate by a physical page to allow for later alignment.extraPages := physPageSize / pageSize// 找到一个足够大的区域,但只分配对齐的部分// 不能简单地分配然后释放边缘,因为需要考虑回收的内存,这在分配过程中很难处理// 这里跳过了对 searchAddr 的更新。即使 searchAddr 过时并高于正常值,操作依然正确,只是性能会受到影响// 查找足够大的区域进行分配,如果失败则增长堆base, _ = h.pages.find(npages + extraPages)if base == 0 {var ok boolgrowth, ok = h.grow(npages + extraPages)if !ok {unlock(&h.lock)return nil}base, _ = h.pages.find(npages + extraPages)if base == 0 {throw("grew heap, but no adequate free space found")}}// 对齐起始地址并分配范围base = alignUp(base, physPageSize)scav = h.pages.allocRange(base, npages)}// 如果基本地址未分配成功,再次尝试分配基本地址if base == 0 {// Try to acquire a base address.base, scav = h.pages.alloc(npages)if base == 0 {var ok boolgrowth, ok = h.grow(npages)if !ok {unlock(&h.lock)return nil}base, scav = h.pages.alloc(npages)if base == 0 {throw("grew heap, but no adequate free space found")}}}// 如果前面没有分配到 mspan,则在持有堆锁的情况下分配 mspanif s == nil {// We failed to get an mspan earlier, so grab// one now that we have the heap lock.s = h.allocMSpanLocked()}unlock(&h.lock)HaveSpan:bytesToScavenge := uintptr(0)forceScavenge := false// 如果当前没有达到垃圾收集(GC)预设的CPU限制,并且内存使用超过了设定的内存限制(memoryLimit),则计算需要清理的字节数if limit := gcController.memoryLimit.Load(); !gcCPULimiter.limiting() {// Assist with scavenging to maintain the memory limit by the amount// that we expect to page in.inuse := gcController.mappedReady.Load()// Be careful about overflow, especially with uintptrs. Even on 32-bit platforms// someone can set a really big memory limit that isn't maxInt64.// 如果当前使用的内存(inuse)加上新分配的内存(scav)超过了内存限制,则计算需要清理的字节数,并设置 forceScavenge 为 true,表示需要强制清理if uint64(scav)+inuse > uint64(limit) {bytesToScavenge = uintptr(uint64(scav) + inuse - uint64(limit))forceScavenge = true}}// 读取垃圾回收器的目标内存使用量,更新实际超出部分、或增长量、或原bytesToScavenge的最大值if goal := scavenge.gcPercentGoal.Load(); goal != ^uint64(0) && growth > 0 {// 通过内联回收处理内存碎片分配失败的问题,优先回收最不可能再次使用的内存碎片。由于没有使用内存限制,因此只需在不超过内存限制的情况下关注堆的增长,而之前的内存回收检查已经处理了这一点// 检查当前保留的内存量与内存增长量是否超过了目标使用量if retained := heapRetained(); retained+uint64(growth) > goal {// 内存回收算法需要释放堆锁以减少其频繁获取的次数,这是一个可能很耗时的操作。这样做可以让其他 goroutine 在此期间继续分配内存,并且可以利用刚刚增加的内存// 如果增长量超过实际超出量,则需要调整清理量(todo)为实际超出部分(overage)。最终更新 bytesToScavenge 为需要清理的最大值todo := growthif overage := uintptr(retained + uint64(growth) - goal); todo > overage {todo = overage}if todo > bytesToScavenge {bytesToScavenge = todo}}}// 系统内存回收var now int64if pp != nil && bytesToScavenge > 0 {start := nanotime()track := pp.limiterEvent.start(limiterEventScavengeAssist, start)// Scavenge, but back out if the limiter turns on.released := h.pages.scavenge(bytesToScavenge, func() bool {return gcCPULimiter.limiting()}, forceScavenge)mheap_.pages.scav.releasedEager.Add(released)// Finish up accounting.now = nanotime()if track {pp.limiterEvent.stop(limiterEventScavengeAssist, now)}scavenge.assistTime.Add(now - start)}// Initialize the span.h.initSpan(s, typ, spanclass, base, npages)// Commit and account for any scavenged memory that the span now owns.nbytes := npages * pageSize// 如果存在被回收的内存 scav,调用 sysUsed 函数提交实际可用的页面,并更新垃圾回收控制器的统计信息,将回收的内存字节数从 heapReleased 中减去if scav != 0 {// sysUsed all the pages that are actually available// in the span since some of them might be scavenged.sysUsed(unsafe.Pointer(base), nbytes, scav)gcController.heapReleased.add(-int64(scav))}// Update stats.gcController.heapFree.add(-int64(nbytes - scav))if typ == spanAllocHeap {gcController.heapInUse.add(int64(nbytes))}// Update consistent stats.stats := memstats.heapStats.acquire()atomic.Xaddint64(&stats.committed, int64(scav))atomic.Xaddint64(&stats.released, -int64(scav))switch typ {case spanAllocHeap:atomic.Xaddint64(&stats.inHeap, int64(nbytes))case spanAllocStack:atomic.Xaddint64(&stats.inStacks, int64(nbytes))case spanAllocPtrScalarBits:atomic.Xaddint64(&stats.inPtrScalarBits, int64(nbytes))case spanAllocWorkBuf:atomic.Xaddint64(&stats.inWorkBufs, int64(nbytes))}memstats.heapStats.release()pageTraceAlloc(pp, now, base, npages)return s
}

从上面代码中可以看出只有在

  • 没有达到垃圾收集(GC)预设的CPU限制,并且内存使用超过了设定的内存限制(memoryLimit)
  • 当前保留的内存量与内存增长量超过了目标使用量

就会清除内存返还给系统

对于内存限制来说

默认设定的内存限制是maxInt64,可以认为是没有限制的。

也可以通过GOMEMLIMIT环境变量或者debug.setMemoryLimit()进行设置

而目标使用量则通过以下代码进行的计算

// Compute our scavenging goal.goalRatio := float64(heapGoal) / float64(lastHeapGoal)gcPercentGoal := uint64(float64(memstats.lastHeapInUse) * goalRatio)// Add retainExtraPercent overhead to retainedGoal. This calculation// looks strange but the purpose is to arrive at an integer division// (e.g. if retainExtraPercent = 12.5, then we get a divisor of 8)// that also avoids the overflow from a multiplication.gcPercentGoal += gcPercentGoal / (1.0 / (retainExtraPercent / 100.0))// Align it to a physical page boundary to make the following calculations// a bit more exact.gcPercentGoal = (gcPercentGoal + uint64(physPageSize) - 1) &^ (uint64(physPageSize) - 1)// Represents where we are now in the heap's contribution to RSS in bytes.//// Guaranteed to always be a multiple of physPageSize on systems where// physPageSize <= pageSize since we map new heap memory at a size larger than// any physPageSize and released memory in multiples of the physPageSize.//// However, certain functions recategorize heap memory as other stats (e.g.// stacks) and this happens in multiples of pageSize, so on systems// where physPageSize > pageSize the calculations below will not be exact.// Generally this is OK since we'll be off by at most one regular// physical page.heapRetainedNow := heapRetained()// If we're already below our goal, or within one page of our goal, then indicate// that we don't need the background scavenger for maintaining a memory overhead// proportional to the heap goal.if heapRetainedNow <= gcPercentGoal || heapRetainedNow-gcPercentGoal < uint64(physPageSize) {scavenge.gcPercentGoal.Store(^uint64(0))} else {scavenge.gcPercentGoal.Store(gcPercentGoal)}

总结以上计算过程为一个公式(去掉物理页对其部分)
gcPercentGoal = ( lastHeapInUse × heapGoal lastHeapGoal ) × ( 1 + 1 ( retainExtraPercent 100 ) ) \text{gcPercentGoal} = \left(\text{lastHeapInUse} \times \frac{\text{heapGoal}}{\text{lastHeapGoal}}\right) \times(1+ \frac{1}{\left(\frac{\text{retainExtraPercent}}{100}\right)} ) gcPercentGoal=(lastHeapInUse×lastHeapGoalheapGoal)×(1+(100retainExtraPercent)1)
从以上公式中可以看出

  • 当前堆大小期望越大,目标越大,也越可能无效
  • 上一个堆大小期望越小,目标越大,也越可能无效
  • 上一次使用堆越大,目标越大,也越可能无效

retainExtraPercent是常量10

综合来说,在分配span时什么情况下会触发返还系统内存呢?

  • 超出设定内存
  • 内存保有量足够大(保有量包括已分配但未使用的内存、已经写入数据但尚未释放的内存)
  • 当前堆期望相对更小,或者上次堆使用量更小,导致的目标堆值小

debug.FreeOSMemory

主动调用debug.FreeOSMemory会清理所有,并尝试返还系统内存

// scavengeAll acquires the heap lock (blocking any additional
// manipulation of the page allocator) and iterates over the whole
// heap, scavenging every free page available.
//
// Must run on the system stack because it acquires the heap lock.
//
//go:systemstack
func (h *mheap) scavengeAll() {// Disallow malloc or panic while holding the heap lock. We do// this here because this is a non-mallocgc entry-point to// the mheap API.gp := getg()gp.m.mallocing++// Force scavenge everything.released := h.pages.scavenge(^uintptr(0), nil, true)gp.m.mallocing--if debug.scavtrace > 0 {printScavTrace(0, released, true)}
}

bgscavenge

bgscavenge是Go运行时用于后台gc的函数。主要用于维护应用程序的 RSS(驻留集大小),确保其保持在合理的范围内

func bgscavenge(c chan int) {// 初始化scavenger。赋予实际清理方法scavenger.init()// 通知其他 goroutine,后台 scavenger 已经启动c <- 1// 等待需要进行垃圾收集的信号scavenger.park()// 不断执行实际的垃圾收集工作,返回释放的内存量 released 和需要休眠的时间 workTimefor {released, workTime := scavenger.run()// 如果没有释放内存,则再次等待下一次需要进行垃圾收集的信号if released == 0 {scavenger.park()continue}mheap_.pages.scav.releasedBg.Add(released)scavenger.sleep(workTime)}
}

run方法是实际执行内存清理的。

func (s *scavengerState) run() (released uintptr, worked float64) {// 锁定和检查 goroutine是否scavenger所属goroutinelock(&s.lock)if getg() != s.g {throw("tried to run scavenger from another goroutine")}unlock(&s.lock)// 持续执行,直到至少minScavWorkTime时间for worked < minScavWorkTime {// If something from outside tells us to stop early, stop.if s.shouldStop() {break}// 较小的值使得回收器对调度器更加响应,适合抢占。// 较大的值则能更好地摊薄回收开销,提高回收效率。// 当前值基于假设每个物理页面的回收成本约为 10 微秒,4 KiB 物理页面的最坏情况延迟约为 160 微秒,偏向于响应时间而非吞吐量。// 每次尝试释放64KB内存const scavengeQuantum = 64 << 10// Accumulate the amount of time spent scavenging.r, duration := s.scavenge(scavengeQuantum)// 在某些平台上,当回收内存时间小于时钟最小粒度或由于时钟错误导致 end >= start 时// 假设每个物理页面的回收时间为 10 微秒,并忽略大页面对时间的影响。// 如果 duration 为零,则按每个物理页面 10 微秒计算 worked 时间。// 否则,使用实际的 duration 更新 worked 时间,并累加释放的内存量 released。const approxWorkedNSPerPhysicalPage = 10e3if duration == 0 {worked += approxWorkedNSPerPhysicalPage * float64(r/physPageSize)} else {// TODO(mknyszek): If duration is small compared to worked, it could be// rounded down to zero. Probably not a problem in practice because the// values are all within a few orders of magnitude of each other but maybe// worth worrying about.worked += float64(duration)}released += r// 如果释放的内存量 r 小于 scavengeQuantum,表示堆内存耗尽,退出循环if r < scavengeQuantum {break}// When using fake time just do one loop.if faketime != 0 {break}}// 确保释放的内存至少有一个物理页面大小,否则抛出错误以避免内存损坏if released > 0 && released < physPageSize {throw("released less than one physical page of memory")}return
}

重点看看什么时候会被唤醒呢?

唤醒方法是scavenger.wake(),被以下方法调用

  • scavengerState.timer.f

    scavengerState.sleep()方法会重置timer,使得被唤醒

  • mgcsweep.go finishsweep_m()

    是在 GC 进行到标记阶段之前,确保所有的内存区域都已经完成了扫描操作,以便在标记阶段开始时能够顺利进行内存标记和回收工作

    主要是这个地方唤醒的

  • proc.go sysmon()

实际debug来看bgscanvenge的调用频率非常之高,主要都是来自于finishsweep_m方法唤醒的,那么完全可以认为每次gc都会进行返还系统内存

综合来看

allocSpan与debug.FreeOSMemory都不是常态化的清理,只有bgscavenge会每次gc都会执行返还64KB内存的

Ref

  1. go1.22.3 source code

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

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

相关文章

Spring AOP实现操作日志记录示例

1. 准备工作 项目环境&#xff1a;jdk8springboot2.6.13mysql8 1.1 MySQL表 /*Navicat Premium Data TransferSource Server : localhostSource Server Type : MySQLSource Server Version : 50730Source Host : 127.0.0.1:3306Source Schema …

双扩散金属氧化物半导体(DMOS)应用广泛 超结VDMOS市场需求空间大

双扩散金属氧化物半导体&#xff08;DMOS&#xff09;应用广泛 超结VDMOS市场需求空间大 双扩散金属氧化物半导体简称DMOS&#xff0c;是MOS管的一种。MOS管全称为金属氧化物半导体场效应管&#xff0c;又称为MOSFET&#xff0c;是一种利用改变电压来控制电流的半导体器件。  …

《梦醒蝶飞:释放Excel函数与公式的力量》8.8 STDEVP函数

8.8 STDEVP函数 STDEVP函数是Excel中用于计算总体数据的标准偏差的函数。标准偏差是统计学中的一个重要指标&#xff0c;用于衡量数据集中各数值偏离平均值的程度。总体标准偏差考虑了整个数据集&#xff0c;而不是样本。 8.8.1 函数简介 STDEVP函数用于返回总体数据的标准偏…

2023年工作回顾与总结

一、工作概述 主要工作职责和任务 完善XXX平台的基础能力优化XXX平台基础架构负责平台小组代码评审负责复杂功能的提供技术支持负责复杂功能的技术方案评审负责XXX国家项目中平台相关项目通过验收 具体内容和完成情况 XX平台架构优化 &#xff08;1&#xff09;完成通用控…

Redis 中的通用命令(命令的返回值、复杂度、注意事项及操作演示)

Redis 中的通用命令(高频率操作) 文章目录 Redis 中的通用命令(高频率操作)Redis 的数据类型redis-cli 命令Keys 命令Exists 命令Expire 命令Ttl 命令Type命令 Redis 的数据类型 Redis 支持多种数据类型&#xff0c;整体来说&#xff0c;Redis 是一个键值对结构的&#xff0c;…

第N7周:seq2seq翻译实战-pytorch复现-小白版

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 理论基础 seq2seq&#xff08;Sequence-to-Sequence&#xff09;模型是一种用于机器翻译、文本摘要等序列转换任务的框架。它由两个主要的递归神经网络&#…

【leetcode】双指针算法题

文章目录 1.算法思想2.移动零3.复写零方法一方法二 4.快乐数5.盛水最多的容器方法一&#xff08;暴力求解&#xff09;方法二&#xff08;左右指针&#xff09; 6.有效三角形的个数方法一&#xff08;暴力求解&#xff09;方法二&#xff08;左右指针&#xff09; 7.两数之和8.…

CNN文献综述

卷积神经网络&#xff08;Convolutional Neural Networks&#xff0c;简称CNN&#xff09;是深度学习领域中的一种重要模型&#xff0c;主要用于图像识别和计算机视觉任务。其设计灵感来自于生物学中视觉皮层的工作原理&#xff0c;能够高效地处理图像和语音等数据。 基本原理…

UVa1265/LA4848 Tour Belt

UVa1265/LA4848 Tour Belt 题目链接题意分析AC 代码 题目链接 本题是2010年icpc亚洲区域赛大田赛区的F题 题意 给出一个有n个结点m条边的加权无向图G&#xff08;2≤n≤5000&#xff0c;1≤m≤n(n-1)/2&#xff09;&#xff0c;满足如下条件的结点集B&#xff08;2≤|B|≤n&am…

剪画小程序:手机制作音乐串烧,用它,真的很简单!

Hello&#xff0c;大家好呀&#xff0c;我是不会画画的小画。 相伴关注歌手的小伙伴们&#xff0c;上周五的《歌手 2024》第八期大家看了吧&#xff01;那期节目里有好几首歌都让我沉醉其中&#xff0c;像汪苏泷的《听见下雨的声音》、谭维维的《兰花花儿》等等。 为了能让大…

c++之旅第十一弹——顺序表

大家好啊&#xff0c;这里是c之旅第十一弹&#xff0c;跟随我的步伐来开始这一篇的学习吧&#xff01; 如果有知识性错误&#xff0c;欢迎各位指正&#xff01;&#xff01;一起加油&#xff01;&#xff01; 创作不易&#xff0c;希望大家多多支持哦&#xff01; 一,数据结构…

基于docker环境及Harbor部署{很简短一点了,耐心看吧}

用到的环境&#xff1a; docker 、nacos、compose、harbor&#xff08;自行安装 ,以下连接作为参考&#xff09; nacos&#xff1a;史上最全整合nacos单机模式整合哈哈哈哈哈_nacos 源码启动 单机模式-CSDN博客 docker、compose、harbor:史上最全的整合Harbor安装教程&#…

ChatGPT:AOP配置中的切入点定义

ChatGPT&#xff1a;AOP配置中的切入点定义 <aop:pointcut id“addTime” expression“execution(* com.xrq.aop.HelloWorld.print*(…))” /> 这是什么 这是一个AOP&#xff08;面向方面编程&#xff09;配置中的切入点定义。AOP是一种编程范式&#xff0c;用于将跨越多…

把鼠标光标移到一段文字的首部,尾部,以及翻行查找文字等

如果当前的键盘无单独的End、Home、PgDn、PdUp。 1、如果光标在一段文字的中间&#xff1a; 需要快速移到文字尾部&#xff0c;按住&#xff1a;shiftEnd(如果End在数字1键扣上,shift1&#xff09; 需要快速移到文字首部&#xff1a;按住&#xff1a;shiftHome(如果End在数字…

vue2 img src 无法显示问题

1、页面标签这样写 <img :src"pdf2wordUrl" alt"Image">2、data这样定义 pdf2wordUrl: imgOff,3、import这样写 import imgOn from ../../assets/on.pngimport imgOff from ../../assets/off.png4、转换代码 if (type pdf2word) {this.convertTit…

xxx_proc 重写遇到的问题

1. & 与 && 的使用 需要使用 按位 & 逻辑时 &#xff0c;必须使用& &#xff1b; 其他非按位与 逻辑&#xff0c; 推荐使用 &&&#xff0c; 使用& 会导致覆盖率分析不全&#xff08;软件问题&#xff09; 2. 数据加&#xff0c;减溢出 数据…

数据结构之顺序表专题

在学习数据结构之前我们要先了解什么是数据结构&#xff1f; 1.数据结构相关概念 1.什么是数据结构&#xff1f; 数据结构是由“数据”和“结构”两词组合而来。 什么是数据?常见的数值1、2、3、4.、教务系统里保存的用户信息(姓名、性别、年龄、学历等等)、网页里肉眼可以…

qt 关于大端小端的一个实验 简单实验

1.概要 起因我用滚动是x86电脑&#xff0c;我用一个usort a11a ,我期待转换长的char字符应该是这样的“1aa1”,因为x86是小端的&#xff0c;这也是这个16位 类型的实际内存顺序&#xff0c;但是输出的结果是 “a11a”&#xff0c;难道这环境不是小端&#xff1f;难道qt能智能…

TensorBoard进阶

文章目录 TensorBoard进阶1.设置TensorBoard2.图像数据在TensorBoard中可视化3.模型结构在TensorBoard中可视化&#xff08;重点✅&#xff09;4.高维数据在TensorBoard中低维可视化5.利用TensorBoard跟踪模型的训练过程&#xff08;重点✅&#xff09;6.利用TensorBoard给每个…

complex复数库学习

此头文件是数值库的一部分。本篇介绍complex的基本用法。 常用的API如下&#xff1a; 运算 real 返回实部 (函数模板) imag 返回虚部 (函数模板) abs(std::complex) 返回复数的模 (函数模板) arg 返回辐角 (函数模板) norm 返回模(范数)的平方 (函数模板) conj 返回复共轭 (函…