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_DONTNEED
		atomic.Store(&adviseUnused, _MADV_DONTNEED)
		fallthrough
	case _MADV_DONTNEED:
		// 使用 MADV_DONTNEED 提示操作系统这些内存页可以被释放。 内核可以自由地延迟释放页面,直到适当的时刻。 然而,调用进程的驻留集大小(RSS)将立即减少。
		if madvise(v, n, _MADV_DONTNEED) == 0 {
			break
		}
		// 如果 MADV_DONTNEED 不被支持,则设置 adviseUnused 为 madviseUnsupported
		atomic.Store(&adviseUnused, madviseUnsupported)
		fallthrough
	case 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 / pageSize
	if max%pageSize != 0 {
		maxPages++
	}

  // 最少要回收的页面数计算
	minPages := physPageSize / pageSize
	if 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.
    // 如果成功从页面缓存分配,则尝试获取 mspan
		base, 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 bool
			growth, 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 bool
			growth, 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,则在持有堆锁的情况下分配 mspan
	if 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 := growth
			if overage := uintptr(retained + uint64(growth) - goal); todo > overage {
				todo = overage
			}
			if todo > bytesToScavenge {
				bytesToScavenge = todo
			}
		}
	}

  // 系统内存回收
	var now int64
	if 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 和需要休眠的时间 workTime
	for {
		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所属goroutine
	lock(&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 = 10e3
		if 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.mfbz.cn/a/774929.html

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

相关文章

TC3xx NvM小细节解读

目录 1.FlsLoader Driver和FlsDmu Driver 2. FlsLoader小细节 3.小结 大家好&#xff0c;我是快乐的肌肉&#xff0c;今天聊聊TC3xx NvM相关硬件细节以及MCAL针对NvM的驱动。 1.FlsLoader Driver和FlsDmu Driver 在最开始做标定的时候&#xff0c;认为标定数据既然是数据&…

力扣双指针算法题目:复写零

1.题目 . - 力扣&#xff08;LeetCode&#xff09; 2.解题思路 本题要求就是对于一个数组顺序表&#xff0c;将表中的所有“0”元素都向后再写一遍&#xff0c;且我们还要保证此元素之后的元素不受到影响&#xff0c;且复写零之后此数组顺序表的总长度不可以改变&#xff0c;…

C#(asp.net)房屋租赁管理系统-计算机毕业设计源码64421

目 录 摘要 1 绪论 1.1 研究背景与意义 1.2开发现状 1.3论文结构与章节安排 2 房屋租赁管理系统分析 2.1 可行性分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 法律可行性分析 2.2 系统功能分析 2.2.1 功能性分析 2.2.2 非功能性分析 2.3 系统用例分析 …

如何利用好用便签提高工作效率?

在忙碌的工作中&#xff0c;我们经常需要记住许多琐碎的任务。如果这些任务被遗忘&#xff0c;可能会对我们的工作产生影响。这时&#xff0c;便签就成为了我们的得力助手。通过合理的使用和管理&#xff0c;便签不仅能帮助我们记住重要的事项&#xff0c;还能提高我们的工作效…

中科蓝讯AB5607E蓝牙5.4 低成本带插卡带U盘音箱方案

方案概述 中科蓝讯AB5607E蓝牙5.4 低成本带插卡带U盘音箱方案&#xff0c;我们已有成熟的方案&#xff0c;用户可以免开发&#xff08;零代码&#xff09;快速完成带插卡带U盘蓝牙音箱&#xff0c;提供原理图&#xff0c;PCB Layout指导。 方案优势 低成本&#xff0c;IC成本低…

【Linux进程】进程优先级 Linux 2.6内核进程的调度

前言 进程是资源分配的基本单位, 在OS中存在这很多的进程, 那么就必然存在着资源竞争的问题, 操作系统是如何进行资源分配的? 对于多个进程同时运行, 操作系统又是如何调度达到并发呢? 本文将以Linux kernel 2.6为例 , 向大家介绍进程在操作系统中 (OS) 的调度原理; 1. 进程优…

【开发工具-前端必备神器】WebStrom2024版-安装和使用(小白学习)

一、官方下载地址 Other Versions - WebStorm 选择适合自己电脑的下载 二、安装步骤 1、双击下载的exe安装 2、选择安装目录【建议不要安装在C盘下】 3、安装选项&#xff0c;可以全选 4一直点击下一步就行了 5.双击运行 安装遇到问题&#xff1a; 我是下错版本了&#xff0…

Motion Guidance: 扩散模型实现图像精确编辑的创新方法

在深度学习领域&#xff0c;扩散模型&#xff08;diffusion models&#xff09;因其能够根据文本描述生成高质量图像而备受关注。然而&#xff0c;这些模型在精确编辑图像中对象的布局、位置、姿态和形状方面仍存在挑战。本文提出了一种名为“运动引导”&#xff08;motion gui…

【LLM】一、利用ollama本地部署大模型

目录 前言 一、Ollama 简介 1、什么是Ollama 2、特点&#xff1a; 二、Windows部署 1.下载 2.安装 3.测试安装 4.模型部署&#xff1a; 5.注意 三、 Docker部署 1.docker安装 2.ollama镜像拉取 3.ollama运行容器 4.模型部署&#xff1a; 5.注意&#xff1a; 总结 前言…

【C++】哈希表 ---开散列版本的实现

你很自由 充满了无限可能 这是很棒的事 我衷心祈祷你可以相信自己 无悔地燃烧自己的人生 -- 东野圭吾 《解忧杂货店》 开散列版本的实现 1 前言2 开散列版本的实现2.1 节点设计2.2 框架搭建2.3 插入函数2.4 删除函数2.5 查找操作2.6 测试 Thanks♪(&#xff65;ω&#x…

OpenCV 灰度直方图及熵的计算

目录 一、概述 1.1灰度直方图 1.1.1灰度直方图的原理 1.1.2灰度直方图的应用 1.1.3直方图的评判标准 1.2熵 二、代码实现 三、实现效果 3.1直方图显示 3.2 熵的计算 一、概述 OpenCV中的灰度直方图是一个关键的工具&#xff0c;用于分析和理解图像的灰度分布情况。直…

Excel多表格合并

我这里一共有25张表格: 所有表的表头和格式都一样,但是内容不一样: 现在我要做的是把所有表格的内容合并到一起,研究了一下发现WPS的这项功能要开会员的,本来想用代码撸出来的,但是后来想想还是找其他办法,后来找到"易用宝"这个插件,这个插件可以从如下地址下载:ht…

图像处理中的二维傅里叶变换

图像处理中的二维傅里叶变换 问题来源是对彩色图像进行压缩时得出的傅里叶系数的图像如何解释&#xff0c;导入图片&#xff0c;转化为灰度图片&#xff1a; #彩色图片一般是由RGB组成&#xff0c;其实就是3个二维数组叠加而成&#xff0c;当RGB时&#xff0c;彩色图片就会变成…

【线性代数的本质】矩阵与线性变换

线性变化要满足两点性质&#xff1a; 直线&#xff08;连续的点&#xff09;在变换后还是直线。原点不变。 假设有坐标轴&#xff08;基底&#xff09; i ^ \widehat{i} i 和 j ^ \widehat{j} j ​&#xff1a; i ^ [ 1 0 ] , j ^ [ 0 1 ] \widehat{i}\begin{bmatrix} 1 \…

【leetcode】双指针算法题

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

ONLYOFFICE 8.1版本震撼来袭,让办公更高效、更智能

官网链接&#xff1a; 在线PDF查看器和转换器 | ONLYOFFICE 在线办公套件 | ONLYOFFICE 随着科技的不断发展&#xff0c;办公软件已经成为现代企业提高工作效率、实现信息共享的重要工具。在我国&#xff0c;一款名为ONLYOFFICE的在线办公套件受到了越来越多企业的青睐。今天…

Prompt-Free Diffusion: Taking “Text” out of Text-to-Image Diffusion Models

CVPR2024 SHI Labshttps://arxiv.org/pdf/2305.16223https://github.com/SHI-Labs/Prompt-Free-Diffusion 问题引入 在SD模型的基础之上&#xff0c;去掉text prompt&#xff0c;使用reference image作为生成图片语义的指导&#xff0c;optional structure image作为生成图片…

深入理解【 String类】

目录 1、String类的重要性 2、常用方法 2、1 字符串构造 2、2 String对象的比较 2、3 字符串查找 2、4字符转换 数值和字符串转换&#xff1a; 大小写转化&#xff1a; 字符串转数组&#xff1a; 格式转化&#xff1a; 2、5 字符串替换 2、6字符串拆分 2、7 字符串…

知名品牌因商标痛失市场:114家直营店山寨店7000多家!

奶茶知名品牌“鹿角巷”当年红遍大江南北&#xff0c;是最早的新茶饮品牌&#xff0c;但是当年商标注册存在问题&#xff0c;被同行奶茶品牌抢占了先机&#xff0c;发声明“对大陆商标注册细则不详&#xff0c;在商标注册过程中让假店钻了法律空档”&#xff0c;最夸张的时候全…

python如何不保留小数

1、int() 向下取整&#xff08;内置函数&#xff09; n 3.75 print(int(n)) >>> 3 n 3.25 print(int(n)) >>> 3 2、round() 四舍五入&#xff08;内置函数&#xff09; n 3.75 print(round(n)) >>> 4 n 3.25 print(round(n)) >>> 3 …