Go内存优化与垃圾收集
Go提供了自动化的内存管理机制,但在某些情况下需要更精细的微调从而避免发生OOM错误。本文介绍了如何通过微调GOGC和GOMEMLIMIT在性能和内存效率之间取得平衡,并尽量避免OOM的产生。原文: Memory Optimization and Garbage Collector Management in Go

本文将讨论Go的垃圾收集器、应用程序内存优化以及如何防止OOM(Out-Of-Memory)错误。
Go中的堆(Heap)栈(Stack)
我不会详细介绍垃圾收集器如何工作,已经有很多关于这个主题的文章和官方文档(比如A Guide to the Go Garbage Collector[1]和源码[2])。但是,我会提到一些有助于理解本文主题的基本概念。
你可能已经知道,Go的数据可以存储在两个主要的内存存储中: 栈(stack)和堆(heap)。

通常,栈存储的数据的大小和使用时间可以由Go编译器预测,包括函数局部变量、函数参数、返回值等。
栈是自动管理的,遵循后进先出(LIFO)原则。当调用函数时,所有相关数据都放在栈的顶部,函数结束时,这些数据将从栈中删除。栈不需要复杂的垃圾收集机制,其内存管理开销最小,在栈中检索和存储数据的过程非常快。
然而,并不是所有数据都可以存储在栈中。在执行过程中动态更改的数据或需要在函数范围之外访问的数据不能放在栈上,因为编译器无法预测其使用情况,这种数据应该存储在堆中。
与栈不同,从堆中检索数据并对其进行管理的成本更高。
栈里放什么,堆里放什么?
正如前面提到的,栈用于具有可预测大小和寿命的值,例如:
-
在函数内部声明的局部变量,例如基本数据类型变量(例如数字和布尔值)。 -
函数参数。 -
函数返回后不再被引用的返回值。
Go编译器在决定将数据放在栈中还是堆中时会考虑各种细微差别。
例如,预分配大小为64 KB的数据将存储在栈中,而大于64 KB的数据将存储在堆中。这同样适用于数组,如果数组超过10 MB,将存储在堆中。

可以使用逃逸分析(escape analysis)来确定特定变量的存储位置。
例如,可以通过命令行编译参数-gcflags=-m
来分析应用程序:
go build -gcflags=-m main.go
如果使用-gcflags=-m
参数编译下面的main.go
:
package main
func main() {
var arrayBefore10Mb [1310720]int
arrayBefore10Mb[0] = 1
var arrayAfter10Mb [1310721]int
arrayAfter10Mb[0] = 1
sliceBefore64 := make([]int, 8192)
sliceOver64 := make([]int, 8193)
sliceOver64[0] = sliceBefore64[0]
}
结果是:
# command-line-arguments
./main.go:3:6: can inline main
./main.go:7:6: moved to heap: arrayAfter10Mb
./main.go:10:23: make([]int, 8192) does not escape
./main.go:11:21: make([]int, 8193) escapes to heap
可以看到arrayAfter10Mb
数组被移动到堆中,因为大小超过了10MB,而arrayBefore10Mb
仍然留在栈中(对于int
变量,10MB等于10 * 1024 * 1024 / 8 = 1310720个元素)。
此外,sliceBefore64
没有存储在堆中,因为它的大小小于64KB,而sliceOver64
被存储在堆中(对于int
变量,64KB等于64 * 1024 / 8 = 8192个元素)。
要了解更多关于在堆中分配的位置和内容,可以参考malloc.go源码[3]。
因此,使用堆的一种方法是尽量避免用它!但是,如果数据已经落在堆中了呢?
与栈不同,堆的大小是无限的,并且不断增长。堆存储动态创建的对象,如结构体、分片和映射,以及由于其限制而无法放入栈中的大内存块。
在堆中重用内存并防止其完全阻塞的唯一工具是垃圾收集器。
浅谈垃圾收集器的工作原理
垃圾收集器(GC)是一种专门用于识别和释放动态分配内存的系统。
Go使用基于跟踪和标记和扫描算法的垃圾收集算法。在标记阶段,垃圾收集器将应用程序正在使用的数据标记为活跃堆。然后,在清理阶段,GC遍历所有未标记为活跃的内存并复用。
垃圾收集器不是免费工作的,需要消耗两个重要的系统资源: CPU时间和物理内存。
垃圾收集器中的内存由以下部分组成:
-
活跃堆内存(在前一个垃圾收集周期中标记为"活跃"的内存) -
新的堆内存(尚未被垃圾收集器分析的堆内存) -
存储元数据的内存,与前两个实体相比,这些元数据通常微不足道。
垃圾收集器所消耗的CPU时间与其工作细节有关。有一种称为"stop-the-world"的垃圾收集器实现,它在垃圾收集期间完全停止程序执行,导致CPU时间被花在非生产性工作上。
在Go里,垃圾收集器并不是完全"stop-the-world",而是与应用程序并行执行其大部分工作(例如标记堆)。
但是,垃圾收集器的操作仍然有一些限制,并且会在一个周期内多次完全停止工作代码的执行,想要了解更多可以阅读源码[4]。

如何管理垃圾收集器
在Go中可以通过某些参数管理垃圾收集器: GOGC
环境变量或runtime/debug
包中的等效函数SetGCPercent
。
GOGC
参数确定将触发垃圾收集的新未分配堆内存相对于活跃内存的百分比。
GOGC
的默认值是100,意味着当新内存达到活跃堆内存的100%时将触发垃圾收集。

我们以示例程序为例,通过go tool trace
跟踪堆大小的变化,我们用Go 1.20.1版本来运行程序。
在本例中,performMemoryIntensiveTask
函数使用了在堆中分配的大量内存。这个函数启动一个队列大小为NumWorker
的工作池,任务数量等于NumTasks
。
package main
import (
"fmt"
"os"
"runtime/debug"
"runtime/trace"
"sync"
)
const (
NumWorkers = 4 // Number of workers.
NumTasks = 500 // Number of tasks.
MemoryIntense = 10000 // Size of memory-intensive task (number of elements).
)
func main() {
// Write to the trace file.
f, _ := os.Create("trace.out")
trace.Start(f)
defer trace.Stop()
// Set the target percentage for the garbage collector. Default is 100%.
debug.SetGCPercent(100)
// Task queue and result queue.
taskQueue := make(chan int, NumTasks)
resultQueue := make(chan int, NumTasks)
// Start workers.
var wg sync.WaitGroup
wg.Add(NumWorkers)
for i := 0; i < NumWorkers; i++ {
go worker(taskQueue, resultQueue, &wg)
}
// Send tasks to the queue.
for i := 0; i < NumTasks; i++ {
taskQueue <- i
}
close(taskQueue)
// Retrieve results from the queue.
go func() {
wg.Wait()
close(resultQueue)
}()
// Process the results.
for result := range resultQueue {
fmt.Println("Result:", result)
}
fmt.Println("Done!")
}
// Worker function.
func worker(tasks <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for task := range tasks {
result := performMemoryIntensiveTask(task)
results <- result
}
}
// performMemoryIntensiveTask is a memory-intensive function.
func performMemoryIntensiveTask(task int) int {
// Create a large-sized slice.
data := make([]int, MemoryIntense)
for i := 0; i < MemoryIntense; i++ {
data[i] = i + task
}
// Latency imitation.
time.Sleep(10 * time.Millisecond)
// Calculate the result.
result := 0
for _, value := range data {
result += value
}
return result
}
跟踪程序执行的结果被写入文件trace.out
:
// Writing to the trace file.
f, _ := os.Create("trace.out")
trace.Start(f)
defer trace.Stop()
通过go tool trace
,可以观察堆大小的变化,并分析程序中垃圾收集器的行为。
请注意,go tool trace的精确细节和功能可能因go版本不同而有所差异,因此建议参考官方文档,以获取有关其在特定go版本中使用的详细信息。
GOGC的默认值
GOGC
参数可以使用runtime/debug
包中的debug.SetGCPercent
进行设置,GOGC
默认设置为100%。
用下面命令运行程序:
go run main.go
程序执行后,将会创建trace.out
文件,可以使用go tool
工具对其进行分析。要做到这一点,执行命令:
go tool trace trace.out
然后可以通过打开web浏览器并访问http://127.0.0.1:54784/trace来查看基于web的跟踪查看器。

在"STATS"选项卡中,可以看到"Heap"字段,显示了在应用程序执行期间堆大小的变化情况,图中红色区域表示堆占用的内存。
在"PROCS"选项卡中,"GC"(垃圾收集器)字段显示的蓝色列表示触发垃圾收集器的时刻。
一旦新堆的大小达到活动堆大小的100%,就会触发垃圾收集。例如,如果活跃堆大小为10 MB,则当当前堆大小达到10 MB时将触发垃圾收集。
跟踪所有垃圾收集调用使我们能够确定垃圾收集器处于活动状态的总时间。

示例中,当GOGC
值为100时,将调用垃圾收集器16次,总执行时间为14 ms。
更频繁的调用GC
如果我们将debug.SetGCPercent(10)
设置为10%后运行代码,将观察到垃圾收集器调用的频率更高。现在,如果当前堆大小达到活跃堆大小的10%时,将触发垃圾收集。
换句话说,如果活跃堆大小为10 MB,则当前堆大小达到1 MB时就将触发垃圾收集。

在本例中,垃圾收集器被调用了38次,总垃圾收集时间为28 ms。

可以观察到,将GOGC设置为低于100%的值可以增加垃圾收集的频率,可能导致CPU使用率增加并降低程序性能。
更少的调用GC
如果运行相同程序,但将debug.SetGCPercent(1000)
设置为1000%,我们将得到以下结果:

可以看到,当前堆的大小一直在增长,直到达到活跃堆大小的1000%。换句话说,如果活跃堆大小为10 MB,则当前堆大小达到100 MB时将触发垃圾收集。

在当前情况下,垃圾收集器被调用一次并执行2毫秒。
关闭GC
还可以通过设置GOGC=off
或调用debug.SetGCPercent(-1)
来禁用垃圾收集。
下面是禁用垃圾收集器而不设置GOMEMLIMIT时堆的行为:

可以看到,在关闭GC后,应用程序的堆大小一直在增长,直到程序执行为止。
堆占用多少内存?
在活跃堆的实际内存分配中,通常不像我们在trace中看到的那样定期和可预测的工作。
活跃堆随着每个垃圾收集周期动态变化,并且在某些条件下,其绝对值可能出现峰值。
例如,如果由于多个并行任务的重叠,活跃堆的大小可以增长到800 MB,那么只有在当前堆大小达到1.6 GB时才会触发垃圾收集。

现代开发通常在具有内存使用限制的容器中运行应用。因此,如果容器将内存限制设置为1 GB,并且总堆大小增加到1.6 GB,则容器将失效,并出现OOM(out of memory)错误。
让我们模拟一下这种情况。例如,我们在内存限制为10 MB的容器中运行程序(仅用于测试目的)。Dockerfile:
FROM golang:latest as builder
WORKDIR /src
COPY . .
RUN go env -w GO111MODULE=on
RUN go mod vendor
RUN CGO_ENABLED=0 GOOS=linux go build -mod=vendor -a -installsuffix cgo -o app ./cmd/
FROM golang:latest
WORKDIR /root/
COPY --from=builder /src/app .
EXPOSE 8080
CMD ["./app"]
Docker-compose描述:
version: '3'
services:
my-app:
build:
context: .
dockerfile: Dockerfile
ports:
- 8080:8080
deploy:
resources:
limits:
memory: 10M
让我们使用前面设置GOGC=1000%的代码启动容器。
可以使用以下命令运行容器:
docker-compose build
docker-compose up
几秒钟后,容器将崩溃,并产生与OOM相对应的错误。
exited with code 137
这种情况非常令人不快: GOGC只控制新堆的相对值,而容器有绝对限制。

如何避免OOM?
从1.19版本开始,在GOMEMLIMIT
选项的帮助下,Golang引入了一个名为"软内存管理"的特性,runtime/debug
包中名为SetMemoryLimit
的类似函数(可以阅读48409-soft-memory-limit.md了解有关此选项的一些有趣的设计细节)提供了相同的功能。
GOMEMLIMIT
环境变量设置Go运行时可以使用的总体内存限制,例如: GOMEMLIMIT = 8MiB
。要设置内存值,需要使用大小后缀,在本例中为8 MB。
让我们启动将GOMEMLIMIT
境变量设置为8MiB的容器。为此,我们将环境变量添加到docker-compose文件中:
version: '3'
services:
my-app:
environment:
GOMEMLIMIT: "8MiB"
build:
context: .
dockerfile: Dockerfile
ports:
- 8080:8080
deploy:
resources:
limits:
memory: 10M
现在,当启动容器时,程序运行没有任何错误。该机制是专门为解决OOM问题而设计的。
这是因为启用GOMEMLIMIT=8MiB
后,会定期调用垃圾收集器,并将堆大小保持在一定限制内,结果就是会频繁调用垃圾收集器以避免内存过载。

成本是什么?
GOMEMLIMIT
是强有力的工具,但也可能适得其反。
在上面的堆跟踪图中可以看到这种场景的一个示例。
当总内存大小由于活跃堆或持久程序泄漏的增长而接近GOMEMLIMIT
时,将开始根据该限制不断调用垃圾收集器。
由于频繁调用垃圾收集器,应用程序的运行时可能会无限增加,从而消耗应用程序的CPU时间。
这种行为被称为死亡螺旋[5],可能导致应用程序性能下降,与OOM错误不同,这种问题很难检测和修复。
这正是GOMEMLIMIT
机制作为软限制起作用的原因。
Go不能100%保证GOMEMLIMIT
指定的内存限制会被严格执行,而是会允许使用超出限制的内存,并防止频繁调用垃圾收集器的情况。
为了实现这一点,需要对CPU使用设置限制。目前,这个限制被设置为所有处理器时间的50%,CPU窗口为2 * GOMAXPROCS
秒。
这就是为什么我们不能完全避免OOM错误,而是会将其推迟到很久以后发生。
在哪里应用GOMEMLIMIT和GOGC
如果默认垃圾收集器设置在大多数情况下是足够的,那么带有GOMEMLIMIT
的软内存管理机制可以使我们避免不愉快的情况。
使用GOMEMLIMIT
内存限制可能有用的例子:
-
在内存有限的容器中运行应用程序时,最好将 GOMEMLIMIT
设置为保留5-10%的可用内存。 -
在运行资源密集型库或代码时,对 GOMEMLIMIT
进行实时管理是有好处的。 -
当在容器中以脚本形式运行应用程序时(意味着应用程序在一段时间内执行某些任务,然后终止),禁用垃圾收集器但设置 GOMEMLIMIT
可以提高性能并防止超出容器的资源限制。
避免使用GOMEMLIMIT
的情况:
-
当程序已经接近其环境的内存限制时,不要设置内存限制。 -
在无法控制的执行环境中部署时,不要使用内存限制,特别是在程序的内存使用与其输入数据成正比的情况下,例如CLI工具或桌面应用程序。
如上所述,通过深思熟虑的方法,我们可以管理程序中的微调设置,例如垃圾收集器和GOMEMLIMIT
。然而,仔细考虑应用这些设置的策略无疑非常重要。
你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。为了方便大家以后能第一时间看到文章,请朋友们关注公众号"DeepNoMind",并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的支持和动力,激励我持续写下去,和大家共同成长进步!
A Guide to the Go Garbage Collector: https://tip.golang.org/doc/gc-guide
[2]mgc.go: https://go.dev/src/runtime/mgc.go
[3]malloc.go: https://go.dev/src/runtime/malloc.go
[4]mgc.go: https://go.dev/src/runtime/mgc.go
[5]Soft Memory Limit Death Spirals: https://github.com/golang/proposal/blob/master/design/48409-soft-memory-limit.md#death-spirals
本文由 mdnice 多平台发布
相关文章:

Go内存优化与垃圾收集
Go提供了自动化的内存管理机制,但在某些情况下需要更精细的微调从而避免发生OOM错误。本文介绍了如何通过微调GOGC和GOMEMLIMIT在性能和内存效率之间取得平衡,并尽量避免OOM的产生。原文: Memory Optimization and Garbage Collector Management in Go 本…...

【Spring】Bean 的生命周期
一、Bean 的生命周期 Spring 其实就是一个管理 Bean 对象的工厂,它负责对象的创建,对象的销毁等 所谓的生命周期就是:对象从创建开始到最终销毁的整个过程 什么时候创建 Bean 对象?创建 Bean 对象的前后会调用什么方法…...

云计算基础-存储基础
存储概念 什么是存储: 存储就是根据不同的应用程序环境,通过采取合理、安全、有效的方式将数据保存到某些介质上,并能保证有效的访问,存储的本质是记录信息的载体。 存储的特性: 数据临时或长期驻留的物理介质需要保…...

问题:人的安全知识和技能是天生的。() #媒体#知识分享#学习方法
问题:人的安全知识和技能是天生的。() 人的安全知识和技能是天生的。() 参考答案如图所示 问题:()是党和国家的根本所在、命脉所在,是全国各族人民的利益所在、幸福所在。 A.人民当家作主 B.坚持和完善…...

【数据分享】2001~2020年青藏高原植被净初级生产力数据集
各位同学们好,今天和大伙儿分享的是2001~2020年青藏高原植被净初级生产力数据集。如果大家有下载处理数据等方面的问题,您可以私信或评论。 朱军涛. (2022). 青藏高原植被净初级生产力数据集(2001-2020). 国家青藏高原数据中心. …...

【Spring MVC篇】返回响应
个人主页:兜里有颗棉花糖 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【Spring MVC】 本专栏旨在分享学习Spring MVC的一点学习心得,欢迎大家在评论区交流讨论💌 目录 一、返回静态页面…...

阿里云BGP多线精品EIP香港CN2线路低时延,价格贵
阿里云香港等地域服务器的网络线路类型可以选择BGP(多线)和 BGP(多线)精品,普通的BGP多线和精品有什么区别?BGP(多线)适用于香港本地、香港和海外之间的互联网访问。使用BGP…...

(08)Hive——Join连接、谓词下推
前言 Hive-3.1.2版本支持6种join语法。分别是:inner join(内连接)、left join(左连接)、right join(右连接)、full outer join(全外连接)、left semi join(左…...

创新技巧|迁移到 Google Analytics 4 时如何保存历史 Universal Analytics 数据
Google Universal Analytics 从 2023 年 7 月起停止收集数据(除了付费 GA360 之外)。它被Google Analytics 4取代。为此,不少用户疑惑:是否可以将累积(历史)数据从 Google Analytics Universal 传输到 Goog…...

一个小而实用的 Python 包 pangu,实现在中文和半宽字符(字母、数字和符号)之间自动插入空格
🍉 CSDN 叶庭云:https://yetingyun.blog.csdn.net/ 一个小巧的库,可以避免自己重新开发功能。利用 Python 包 pangu,可以轻松实现在 CJK(中文、日文、韩文)和半宽字符(字母、数字和符号…...
openJudge | 中位数 C语言
总时间限制: 2000ms 内存限制: 65536kB 描述 中位数定义:一组数据按从小到大的顺序依次排列,处在中间位置的一个数或最中间两个数据的平均值(如果这组数的个数为奇数,则中位数为位于中间位置的那个数;如果这组数的个…...

ctfshow-文件上传(web151-web161)
目录 web151 web152 web153 web154 web155 web156 web157 web158 web159 web160 web161 web151 提示前台验证不可靠 那限制条件估计就是在前端设置的 上传php小马后 弹出了窗口说不支持的格式 查看源码 这一条很关键 这种不懂直接ai搜 意思就是限制了上传类型 允许…...
cudnn免登录下载
现在要下载cuDNN,点击下载的页面后都是出现要求先加入Nvidia developers才能进行下载,但这个注册的过程非常慢,常常卡在第二个步骤,这里根据亲身的经验介绍一个可以绕过这个注册或登陆步骤的方式直接下载cuDNN。遇到此类问题的可以…...

SQLyog安装配置(注册码)连接MySQL
下载资源 博主给你打包好了安装包,在网盘里,只有几Mb,防止你下载到钓鱼软件 快说谢谢博主(然后心甘情愿的点个赞~😊) SQLyog.zip 安装流程 ①下载好压缩包后并解压 ②打开文件夹,双击安装包 ③…...

java+SSM+mysql 开放式实验管理系统78512-计算机毕业设计项目选题推荐(免费领源码)
摘 要 我国高校开放式实验管理普遍存在实验设备使用率较低、管理制度不完善,实验设备共享程度不高等诸多问题。要在更大范围推行开放式实验管理,就必须在开放式实验教学管理流程中,通过引入信息化管理加大信息技术在其中的应用,才能真正发挥这种教学模式的开放性优势。 本系统…...
代码随想录算法训练营第三十三天|1005.K次取反后最大化的数组和、134.加油站、135.分发糖果
1005.K次取反后最大化的数组和 public class Solution {public int LargestSumAfterKNegations(int[] nums, int k) {int cnt0;int sum0;int minint.MaxValue;Array.Sort(nums);for(int i0;i<nums.Length;i){if(nums[i]>0){continue;}else{nums[i]-nums[i];cnt;}if(cntk…...

解决LeetCode编译器报错的技巧:正确处理位操作中的数据类型
一天我在leetcode上刷题时,遇到了这样的题目: 随即我写了如下的代码: int convertInteger(int A, int B) {int count 0;int C A ^ B;int flag 1;while(flag){if (C & flag){count;}flag<<1;}return count;} 但LeetCode显示如下…...

一周学会Django5 Python Web开发-Django5操作命令
锋哥原创的Python Web开发 Django5视频教程: 2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~共计11条视频,包括:2024版 Django5 Python we…...

反转链表【基础算法精讲 06】
视频地址 反转链表【基础算法精讲 06】_哔哩哔哩_bilibili 概念 链表的每一个结点都包含节点值 和1指向下一个结点的next指针 , 链表的最后一个结点指向空; 206 . 反转链表 用cur记录当前遍历到的结点 , 用pre表示下一个结点 , 用nxt表示cur的下一个…...

Git 初学
目录 一、需求的产生 二、版本控制系统理解 1. 认识版本控制系统 2. 版本控制系统分类 (1)集中式版本控制系统 缺点: (2)分布式版本控制系统 三、初识 git 四、git 的使用 例:将 “ OLED文件夹 ”…...
C++中指针与引用的区别详解:从原理到实战
C中指针与引用的区别详解:从原理到实战 1. 引言:指针与引用的重要性 在C编程中,指针和引用是两个极其重要的概念,也是许多初学者容易混淆的地方。作为C的核心特性,它们直接操作内存地址,提供了对内存的直…...

SQLMesh 用户定义变量详解:从全局到局部的全方位配置指南
SQLMesh 提供了灵活的多层级变量系统,支持从全局配置到模型局部作用域的变量定义。本文将详细介绍 SQLMesh 的四类用户定义变量(global、gateway、blueprint 和 local)以及宏函数的使用方法。 一、变量类型概述 SQLMesh 支持四种用户定义变量…...
青少年编程与数学 02-020 C#程序设计基础 13课题、数据访问
青少年编程与数学 02-020 C#程序设计基础 13课题、数据访问 一、使用数据库1. 使用ADO.NET连接数据库连接SQL Server示例连接其他数据库 2. 使用Entity Framework (EF Core)安装EF Core示例代码 3. 数据绑定到WinForms控件DataGridView绑定简单控件绑定 4. 使用本地数据库(SQLi…...
STM32 通过 ESP8266 通信详解
✅作者简介:热爱科研的嵌入式开发者,修心和技术同步精进 ❤欢迎关注我的知乎:对error视而不见 代码获取、问题探讨及文章转载可私信。 ☁ 愿你的生命中有够多的云翳,来造就一个美丽的黄昏。 🍎获取更多嵌入式资料可点击链接进群领…...

HTTP/HTTPS与SOCKS5三大代理IP协议,如何选择最佳协议?
在复杂多变的网络环境中,代理协议的选择直接影响数据安全、访问效率和业务稳定性。HTTP、HTTPS和SOCKS5作为三大主流代理协议,各自针对不同场景提供独特的解决方案。本文将从协议特性、性能对比到选型策略,为您揭示如何根据业务需求精准匹配最…...
基于频分复用导频的MMSE信道估计方法设计与仿真
基于频分复用导频的MMSE信道估计方法设计与仿真 摘要 本文详细研究了基于频分复用(FDM)导频的最小均方误差(MMSE)信道估计方法。首先介绍了无线通信系统中信道估计的基本原理和重要性,然后深入分析了频分复用导频结构的设计和MMSE估计算法的理论基础。我们使用Python实现了完…...

ES分词搜索
ES的使用 前言作者使用的版本作者需求 简介ES简略介绍ik分词器简介 使用es的直接简单使用es的查询 es在java中使用备注说明 前言 作者使用的版本 es: 7.17.27spring-boot-starter-data-elasticsearch: 7.14.2 作者需求 作者接到一个业务需求,我们系统有份数据被…...

mcp-go v0.30.0重磅发布!Server端流式HTTP传输、OAuth支持及多项功能革新全面解读!
随着云原生应用和现代分布式系统需求的不断增长,高效、灵活且稳定的通信协议和客户端交互框架成为开发者关注的焦点。作为开源领域备受期待的项目之一,mcp-go再次迎来重要版本更新——v0.30.0正式发布!本次更新版本不仅实现了众多关键功能&am…...
LangChain整合Milvus向量数据库实战:数据新增与删除操作
导读:在AI应用开发中,向量数据库已成为处理大规模语义搜索和相似性匹配的核心组件。本文通过详实的代码示例,深入探讨LangChain框架与Milvus向量数据库的集成实践,为开发者提供生产级别的向量数据管理解决方案。 文章聚焦于向量数…...
TypeScript 针对 iOS 不支持 JIT 的优化策略总结
# **TypeScript 针对 iOS 不支持 JIT 的优化策略总结** 由于 iOS 的 **JavaScriptCore (JSC)** 引擎 **禁用 JIT(Just-In-Time 编译)**,JavaScript 在 iOS 上的执行性能较差,尤其是涉及动态代码时。 **TypeScript(T…...