sort.Slice 对千万级切片通常只需 1–2 秒,盲目并发反而更慢;应先实测单次耗时,低于 500ms 无需优化;推荐分段并发排序加复用底层数组归并,控制 goroutine 数为 runtime.NumCPU()。

并发排序千万级切片前,先确认 sort.Slice 是否真成瓶颈
Go 标准库的 sort.Slice 对千万级 []int 通常只需 1–2 秒(i7-11800H 测试),远快于多数人预期。盲目上并发反而可能因 goroutine 调度、内存分配和锁竞争拖慢整体速度。建议先用 time.Now() 或 runtime/pprof 实测单次排序耗时;若稳定低于 500ms,基本无需并发优化。
用 sort.Sort + 分段并发 + 归并,才是可控方案
标准库不提供并发排序接口,但可手动分段:把大切片切成 N 段,每段用 sort.Ints(比 sort.Slice 略快),再用两两归并合并结果。关键不是“越多 goroutine 越快”,而是控制并发数(通常 runtime.NumCPU() 最优)和避免归并时重复分配内存。
- 分段数建议设为
min(4 * runtime.NumCPU(), len(data)/1e5),防止段过小导致调度开销占比过高 - 每段排序后不立即归并,先存入
[][]int,最后用双指针归并,避免频繁append - 归并时复用目标切片底层数组:
result := make([]int, len(data)),传入归并函数复用 - 别用
sync.WaitGroup等待所有 goroutine——改用chan struct{}控制并发上限更轻量
unsafe 强制共享底层数组时,sort.Ints 可能 panic
若原始切片来自 unsafe.Slice 或 mmap 文件映射,其底层数组可能不可写或非连续。此时调用 sort.Ints 会触发运行时检查失败,报错 fatal error: unexpected signal during runtime execution 或静默崩溃。必须提前验证:
- 用
reflect.ValueOf(slice).UnsafeAddr()和cap(slice)判断是否为合法堆分配数组 - 对 mmap 数据,先
copy到新[]int再排序,别图省事复用 - 启用
GODEBUG=asyncpreemptoff=1不能绕过该检查,仅影响抢占,不解除内存合法性校验
实测对比:1000 万 []int 在不同方案下的耗时(单位:ms)
测试环境:Go 1.22、Linux x86_64、32GB 内存、无其他负载
立即学习“go语言免费学习笔记(深入)”;
单 goroutine sort.Ints: 1240 4 goroutines 分段+归并: 980 8 goroutines 分段+归并: 1050 sort.Slice + 自定义 Less: 1320 纯 channel 归并(无预分配): 2100
归并逻辑本身不复杂,但容易忽略的是:归并时如果每次 append 都触发扩容,性能会掉回单线程水平。真正起效的点,在于分段大小、goroutine 数量、以及归并时是否复用目标切片——这三个变量里,任意一个失控,就抵消了并发收益。


















