
别在 sort.Slice 里写带字段取址的闭包
百万级时序数据(比如 []LogEntry)用 sort.Slice 排序变慢,主因不是算法本身,而是闭包比较函数反复取址触发 CPU 缓存不友好访问。例如每次比较都写 lhs.Timestamp.Unix() > rhs.Timestamp.Unix(),会多次解引用、调用方法、跨 cache line 访问。
- 提前把关键排序字段拷贝到局部变量:
tsL := lhs.Timestamp.Unix(); tsR := rhs.Timestamp.Unix(),再比tsL - 结构体字段多且嵌套深时,直接提取时间戳整数存为新切片:
timestamps := make([]int64, len(logs)); for i := range logs { timestamps[i] = logs[i].Timestamp.Unix() },再用sort.Ints(timestamps)+ 索引重排 - 绝对不要在比较函数里调用
time.Parse、strings.ToLower或任何非内联函数 —— 它们无法被编译器优化,每次调用都是动态调度开销
sort.Sort 实现 sort.Interface 能绕过闭包开销
当排序逻辑稳定、字段固定(如日志按 CreatedAt 升序),用 sort.Sort + 自定义类型实现接口,比 sort.Slice 快 15%~30%,因为比较方法可内联,无闭包捕获和接口跳转。
- 定义类型包装切片:
type ByTime []LogEntry - 实现
Len()、Swap()、Less(i, j int) bool方法,其中Less直接用t[i].CreatedAt.Before(t[j].CreatedAt),不引入额外变量或函数调用 - 调用时:
sort.Sort(ByTime(logs))—— 整个比较路径是静态的,Go 编译器能充分优化
超大有序文件流式归并不能靠并发,而要靠“不越界”
两个 20GB 的 CSV 日志文件按时间戳归并,加载进内存会 OOM;并发读多个文件也不解决根本问题 —— 真正瓶颈是 I/O 阻塞和内存分配,不是 CPU。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 用两个
*csv.Reader同时打开文件,各自维护当前行(recordA,recordB)和对应时间戳(tsA,tsB) - 每次只比较两个时间戳,把小的那个写入输出文件,对应 reader 调用
Read()读下一行;一个读完后,用io.Copy直接把另一个剩余内容刷出,不再逐行判断 - 错误处理必须记录偏移量(
reader.FieldPositions()或自增行号),否则断点续传无法定位失败位置 - 临时缓冲区用
sync.Pool复用:bufPool := sync.Pool{New: func() any { return make([]byte, 0, 4096) }},避免每行 malloc
手写归并排序只在三个场景值得碰
标准库 sort.Sort 已足够快且安全,手写归并唯一合理用途非常窄:
- 需要稳定排序 + 自定义合并策略(比如日志分段归并时,相同时间戳的条目要保持原始顺序,且合并过程需插入校验逻辑)
- 算法题或教学演示,明确要求展示分治思想或控制每层合并行为
- 离线批处理中,对同一份数据做多次不同维度归并(如先按服务名分组,再组内按时间戳归并),复用预分配的
temp切片能省掉反复分配开销
其余所有情况,包括 1000 万条 int64 排序,直接用 sort.Ints;哪怕要并发,也应走「分块 sort.Ints + sync.Pool 归并」,而非手写递归 mergeSort —— 后者栈溢出风险、边界错位 bug、GC 压力都远高于收益。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










