Hi there 👋

Welcome to BruceDing’s blog

GO性能提升的相关改动

随着GO版本的迭代,GO语言的性能也在不断提升。下文梳理下GO种提升性能的几处改动。 math/rand/v2 Go 1.22 版本引入,提供了一个新的随机数生成器,性能更好。 代码测试如下: package main import ( "math/rand" randV2 "math/rand/v2" "testing" ) func BenchmarkRand(b *testing.B) { b.StartTimer() for range b.N { rand.Intn(100) } } func BenchmarkRandV2(b *testing.B) { b.StartTimer() for range b.N { randV2.IntN(100) } } 测试结果 goos: darwin goarch: amd64 pkg: test cpu: Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz BenchmarkRand-12 84788529 14.24 ns/op BenchmarkRandV2-12 154128462 7.790 ns/op math/rand/v2 不需要设置随机种子,使用加密安全的随机数生成器。 Go maps with Swiss Tables Go 1.24版本引入,使用 Swiss Table 的理念,全新重写了map。直接使用Go1....

November 2, 2025 · 1 min · 149 words

io_uring 学习

io_uring 是 Linux 内核提供的异步 IO 接口,它可以用于替代传统的同步 IO 接口,如 read、write 等。io_uring 提供了一种基于事件循环的异步 IO 模型,能够在单个线程中处理多个 IO 操作,从而提高 IO 操作的并发性和效率。io_uring 从内核5.1版本开始引入,在这之前,AIO 提供了异步IO 的功能。但是存在诸多的限制,包括 只能作用于文件IO, 不能作用于网络IO. 在文件IO 中,只能使用O_DIRECT标记打开文件,无法使用buffer cache 。这样使用的范围比较少,只有在数据库领域有应用。 不能完全提供异步功能,如果元数据不可用时,也得同步等待 不能完全做到zero-copy 在某些设备场景中,无法做到真正异步,也得同步等待 io_uring 使用统一的接口解决异步IO问题,包括文件IO 和 网络IO。非阻塞的接口,减少了线程上下文的切换。并且可以使用系统的buffer cache 。 io_uring 主要的数据结构包括两个ring buffer 。 Submission Queue (SQ):用于提交IO请求的环形缓冲区。应用程序将IO请求放入SQ中,内核从SQ中获取请求并处理。 IO 请求放入tail 中, 内核通过head 读取进行处理。 Completion Queue (CQ):用于存储IO操作完成的结果。内核将IO操作的完成结果放入tail中,应用程序从head中获取结果并处理。 io_uring 提供一系列系统调用的接口,目前可以通过liburing 库来更方便的使用。 在这个库的examples 目录下自带了很多的使用例子。具体的例子也可以通过这里有更多的解释。 liburing-cp 这是一个文件IO的例子,使用liburing 实现的 cp 命令。从这里 看到最新的代码。 初始化 io_uring 实例, entries 是 SQ 的大小, 一般设置为 2 的幂次方。 通常情况下CQ 是SQ 大小的2倍。...

October 22, 2025 · 2 min · 365 words

阿里云云盘性能测试

应用服务对磁盘的使用基本模式是顺序写,随机读。通过具体的ECS来测试下云盘的性能。 测试ECS机型ecs.r7.4xlarge(16核128G) 分别测试三种磁盘性能PL1, PL2, PL3 大小都是一样,1300G。选择 1300G 主要是 PL3 有磁盘的最小限制。 挂载之后 PL1 挂载到 /root/data1 PL2 挂载到 /root/data2 PL3 挂载到 /root/data3 挂载之后重启实例进行测试。测试方法参考阿里云官方文档。 顺序写测试 PL1 顺序写性能 PL2 顺序写性能 PL3 顺序写性能 随机读测试 PL1 随机读性能 PL2 随机读性能 PL3 随机读性能 结论 PL2/PL3 比 PL1 有比较好的性能。因为1300G 已经超过了 PL1 本身的性能限制。PL2和PL3 是相差不大的。但是PL3 会支持更大的磁盘,如果磁盘容量更大的话,选择PL3。 在时延上,PL2 也比 PL1 有比较大的提升。

July 19, 2025 · 1 min · 49 words

Badger Scan 性能探究

Badger 是go实现的高性能KV库,与RocksDB类似,也是使用LSM实现的。但不同的是,对于大value, 为了减少了LSM的读写放大的问题,把value记录到单独的value log 中。在LSM记录的value 只是相对应的value log 的offset , 也就是log file ID 和所在文件的offset。本文描述是基于 1.6.2 版本。 KKV是一种数据结构,尤其在推荐领域广泛用到。比如说用户浏览过的文章,视频或者商品列表。对于同一个用户来说,浏览过的物料实际是一个list 。可以简单表示如下: user_id Item_id Timestamp User1 Item1 1745070155 User1 Item2 1745070155 User1 Item3 1745070155 那么在底层,如果使用badger进行存储的时候,会把(user_id, item_id) 当成 key 存储。那么如果针对某个 user_id ,我们需要进行前缀匹配操作,匹配到user_id开头的数据都获取到,进而获取到item_id 列表。badger 的scan操作,官方的例子如下: db.View(func(txn *badger.Txn) error { it := txn.NewIterator(badger.DefaultIteratorOptions) defer it.Close() prefix := []byte("1234") for it.Seek(prefix); it.Valid(); it.Next() { item := it.Item() k := item.Key() err := item.Value(func(v []byte) error { fmt.Printf("key=%s, value=%s\n", k, v) return nil }) if err !...

April 19, 2025 · 5 min · 880 words

Mcp Go Client开发示例

在mcp协议解析文章中,我们已经了解了mcp协议的基本概念,以及如何使用mcp协议来开发一个mcp server。本文将介绍如何使用mcp协议来开发一个mcp client。 我们使用 mcp-go来开发,并且模型调用基于阿里云百炼平台。 源码可以参考这里。 整体的步骤可以描述如下: 初始化 mcp client, 由于使用 stdio transport 方式,通过 command 方式,启动一个mcp server。 mcp client 初始化之后,通过 list tools 接口获取所有的工具信息。 处理用户请求的信息,把 prompt 和 tools 信息传递给大模型。我们使用了qwen-plus 模型,本身支持 function call 功能,我们直接使用 tools 把工具信息传递给大模型。 大模型返回结果后,解析结果,判断是否调用了工具。如果没有使用工具,直接返回大模型的结果。如果使用了工具,大模型返回的 ToolCalls 信息包含了工具名称以及调用工具需要的参数信息。 根据ToolCalls 返回的信息,mcp client 通过 call tool 接口,调用工具。 拿到相应的工具返回的结果后,把用户的 prompt 和工具的结果传递给大模型。 拿到大模型的结果后,返回给用户。 首先在 pairec-mcp-demo 目录下进行编译,生成 pairec-mcp-demo 二进制文件, 然后 copy 到 client 目录下。 初始化mcp client 这里 serverPath 就是 pairec-mcp-demo 二进制文件的路径。 首先通过 Initialize 接口初始化mcp client。 func connectToServer(serverPath string) (*client....

April 4, 2025 · 2 min · 352 words

使用Mcp Inspector调试MCP Server

工具的介绍参考这里,使用方式也比较简单。 使用类似的命令,启动服务。 npx @modelcontextprotocol/inspector <command> 我们使用前文提到的 pairec-mcp-demo 作为参考。 启动命令 npx -y @modelcontextprotocol/inspector "/Users/bruceding 1/Projects/go/src/go-echo-mcp/pairec-mcp-demo/pairec-mcp-demo" --log_dir ./log /Users/bruceding 1/Projects/go/src/go-echo-mcp/pairec-mcp-demo/pairec-mcp-demo 是sever 的二进制路径。 --log_dir ./log 中两个是命令行的启动参数。 启动成功后,会出现如下的提示,浏览器打开相应的地址。 在打开的页面中,选择 transport type ,确认 command 和 arguments 没有问题后,点击 connect ,会返回连接成功的信息。 连接成功后,如果测试 Tools 功能,选择 Tools tab, 调用 List Tools 方法,会列出 tools 工具,进行测试即可。

March 30, 2025 · 1 min · 45 words

Mcp协议解析

从上文 中,介绍了MCP 协议的基本内容,如何使用以及如何基于 stdio transport 开发mcp server 。 本文主要介绍下mcp协议的实现细节。通过从0开始开发一个mcp server, 并且通过日志的方式记录mcp client 和 mcp server 之间的数据是如何交互的。 开发思路是通过 stdio 进行数据读取,然后写入到日志中。注册到 cline 中这样就能接受到 mcp client 的请求了。 通过mcp 协议规范,处理请求,然后返回respone 数据。 处理完一种请求后,不断的更新服务,不断的和mcp client 进行交互,进入下一轮请求,循环往复,直至一个完整的链路完成。 MCP 协议使用 JSON-RPC格式,具体的request,response schema 可以参考这里。 全部的源码参考这里。 还是使用 Tools 走通整个链路。 下图中可以看出 Server 启动时,client 就是发起初始化请求,Server 要把相应的能力高速 Client (Resource, Promot, Tool)。 假设如果只支持 Tool 能力,client 会发起 tools/list 请求,把所有的工具信息获取到。包括 名称、参数列表、描述等等。 如果用户发起了请求, Client 会把用户的提示 + tools 列表信息给到 LLM LLM 会选择格式的工具,并组装请求高速 Client Client 发起tools/call 调用, mcp server 执行相应的工具功能 返回请求后,client 把结果告诉LLM, LLM 进行总结后,输出到客户。 我们看下核心的代码功能。 实际就是监听标准的输入,解析出 method 方法,然后进行相应的处理。...

March 27, 2025 · 3 min · 611 words

MCP协议介绍

MCP(Model Context Protocol,模型上下文协议) 是一个开源的协议,提供了一种通用、标准的方式把LLM AI 大模型和不同数据源以及工具连接起来。类似USB HUB 方式。 与传统调用 API 的方式不同,每个工具都有不同的调用方式,不同的AI 应用或者 LLM 调用时,都需要集成一遍。MCP 的出现,本质上来说出现一个中间层,把工具的提供方和工具的调用方进行了解耦,这样AI应用可以灵活的使用现有已经实现的工具,而不需要单独的集成,提供了开发效率。 协议架构 MCP HOSTS 面向终端用户的AI 应用,类似 Claude Desktop, IDEs 等通过MCP 协议来访问数据 MCP Client 协议的客户端,可以和mcp server 1:1 进行连接。他们之间通过 mcp 协议交互。Client 通常被集成到 host 中。 MCP Server 实现了mcp 协议的服务提供方,它会把数据源以及工具的使用通过服务暴露出去。 目前包括两种 transport layer: Stdio transport 通过标准的输入输出进行通讯(stdin, stdout) mcp server 运行在本机的进程中,比如 IDE 中的 AI 助手操作本地的终端,或者管理目录和文件等等 HTTP with SSE transport 使用 SSE 协议进行远程服务通讯 使用http post 请求方式 MCP 的协议是通过 JSON-RPC 2.0的方式来通讯的,具体协议可以参考这里。需要理解消息类型 request/response, 一个请求,正确的相应后返回 response , 具体response 业务数据封装在 result 字段中。 当请求出错的情况下,返回的错误信息封装到 error 字段中。 还有一种特殊的请求叫 Notifications , 这个请求不会带RequestId, 也不需要返回响应数据。 在MCP server 的实现中,需要定义好需要提供哪几类数据到client 使用。...

March 20, 2025 · 3 min · 440 words

Wasm component model介绍

在前面的文章中,可以看到如何生成 wasip1 的wasm 模块。wasip1 中wasm 的使用中还有很多限制: 函数的入参只支持整型和浮点类型,不支持字符串类型。 使用共享内存的方式传递数据,数据要传递的话,入参只能使用指针和数据长度。 wasip1中一个wasm 文件就可以认为是一个module. wasip2 引入了 component model的概念,它扩展了module的功能,使用更高抽象的WIT(wasm interface type) 来描述module的接口。通过WIT, 可以把各个语言的实现翻译成canonical ABI ,定义了底层的内存是如何组织的,更高级的对象如何在内存中表示等等。 目前 component model 还是发展中,对于guest 语言来说,都有成熟的demo介绍如何使用WIT并生成wasm 文件。但是对于 host 来说,目前只有rust支持运行,还没有看到其他语言的例子。 WIT 的定义可以参考 https://component-model.bytecodealliance.org/design/wit.html。从这里也能看到具体语言的应用。 wasmtime 也支持了如何运行component model,只不过目前只能运行 “command” component。也就是说需要引入了wasi:cli/command world。 C 支持 component model 在介绍之前,首先安装 wit-bindgen, wasm-tools, WASI SDK , wkg这几个工具。 如果安装过 rust, 可以通过 cargo 安装。 安装 wkg cargo install wkg 安装 wit-bindgen cargo install wit-bindgen-cli 安装 wasm-tools cargo install wasm-tools 安装 WASI SDK,可以通过这里下载已经预编译的。...

March 11, 2025 · 1 min · 202 words

Wasm导入函数示例

从wasm技术介绍 里可以看到如何导出函数,并且在宿主机里进行调用。本文介绍如何进行导入函数,从宿主机导入函数并在wasm函数里使用。 还是基于tinygo 来进行演示。 首先定义导入函数 //go:build tinygo package main import ( "fmt" "reflect" "unsafe" ) //export CallFromHost func CallFromHost() int32 //export Add func Add(a, b int32) int32 { return a + b } type User struct { Name string Age int } //export CallHost func CallHost() int32 { return CallFromHost() } //export AddUser func AddUser(nameData *byte, nameLen int32, age int32) bool { fmt.Println(nameLen, age) name := RawBytePtrToString(nameData, int(nameLen)) user := User{Name: name, Age: int(age)} fmt....

March 8, 2025 · 3 min · 488 words