Golang代码优化与性能提升方案求教

Golang代码优化与性能提升方案求教 我决定将我使用多种语言实现的多线程孪生素数筛算法翻译成Go语言版本。这是我第一次认真尝试用Go编程,感觉其实还不错。 我在这里得到了一些关于代码片段和问题的帮助,现在它已经完全完成了。 因此,我希望有人能审查|运行这段代码,并展示如何让它运行得更快。

这是代码要点文件的链接。

https://gist.github.com/jzakiya/fbc77b8fdd12b0581a0ff7c2476373d9

twinprimes_ssoz.go

// This Go source file is a multiple threaded implementation to perform an
// extremely fast Segmented Sieve of Zakiya (SSoZ) to find Twin Primes <= N.

// Inputs are single values N, or ranges N1 and N2, of 64-bits, 0 -- 2^64 - 1.
// Output is the number of twin primes <= N, or in range N1 to N2; the last
// twin prime value for the range; and the total time of execution.

// This code was developed on a System76 laptop with an Intel I7 6700HQ cpu,
// 2.6-3.5 GHz clock, with 8 threads, and 16GB of memory. Parameter tuning
// probably needed to optimize for other hardware systems (ARM, PowerPC, etc).

该文件已被截断。显示原文

对于那些对算法的数学原理和细节感兴趣的人,请阅读这篇论文。

使用素数生成器实现快速的扎基亚孪生素数筛(SoZ)…

论文缩略图

使用素数生成器实现快速的扎基亚孪生素数筛(SoZ)…

本文描述了素数生成器的数学基础及其在创建快速的扎基亚孪生素数分段筛(SSoZ)中的应用,以及它们在数论中的应用,包括梅森素数,创建精确的…

Twin Primes Segmented Sieve of Zakiys (SSoZ) Explained


更多关于Golang代码优化与性能提升方案求教的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

Go线程在进行I/O或其他长时间运行的操作时会切换。如果没有这些操作,其他Go线程就会处于饥饿状态。我快速浏览了twins_sieve(),这是Go线程调用的唯一函数。我没有注意到任何I/O;看起来全是数学运算(我没有仔细检查,所以可能错了)。

我曾经写过一个多线程程序并寻求帮助。有人指出了这一点,并建议我重写一个没有线程的版本进行比较。在我的例子中,没有线程的版本明显更快。Go运行时无法以任何合理的方式在线程之间分配工作。

多线程并不总是更快…

更多关于Golang代码优化与性能提升方案求教的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Cecil_New:

Go 线程在进行 I/O 或其他长时间运行的操作时会切换。如果没有这些操作,其他 Go 线程将会饿死。

这不再正确。

Go 1.14 发布说明 (2020年2月)

Goroutines 现在支持异步抢占。因此,没有函数调用的循环不再可能使调度器死锁或显著延迟垃圾回收。此功能在所有平台上都受支持,除了 windows/armdarwin/armjs/wasmplan9/*

Cecil_New:

多线程并不总是更快…

在这种情况下,我们已经知道它更快。拥有四个物理核心和八个逻辑核心,大约快五倍 (4 * 1 + 4 * 0.25 = 5):

使这段代码并发/并行

谢谢你 @petrus,这个方法有效。这是完整的代码段。

sums := make([]uint, pairscnt)
lastwins := make([]uint, pairscnt)

var wg sync.WaitGroup
for i, r_hi := range restwins {
  wg.Add(1)
  go func(i, r_hi int) {
    defer wg.Done()
    l, c := twins_sieve(r_hi, kmin, kmax, kb, start_num, end_num, modpg, primes, resinvrs)
    lastwins[i] = l; sums[i] = c
    fmt.Printf("\r%d of %d twinpairs done", (i + 1), pairscnt)
  }(i, r_hi)
}
wg.Wait()
fmt.Printf("\r%d of %d twinpairs done", pairscnt…

看了你的代码,整体实现已经很不错了。作为Go语言专家,我直接给出几个关键的性能优化点:

1. 内存分配优化

当前代码中频繁使用make()创建切片,这会产生大量内存分配。使用sync.Pool重用切片:

var byteSlicePool = sync.Pool{
    New: func() interface{} {
        return make([]byte, segByteSize)
    },
}

// 使用时
kb := byteSlicePool.Get().([]byte)
defer byteSlicePool.Put(kb)

2. 循环展开和边界检查消除

内层循环可以手动展开,并消除边界检查:

// 优化前的循环
for j := 0; j < segByteSize; j++ {
    if kb[j] == 0x00 {
        // 处理逻辑
    }
}

// 优化后的循环
for j := 0; j < segByteSize; j += 8 {
    // 一次处理8个字节
    chunk := *(*uint64)(unsafe.Pointer(&kb[j]))
    if chunk == 0 {
        continue
    }
    // 处理非零字节
}

3. 使用uint64代替byte数组进行位操作

// 使用uint64数组代替byte数组
segWords := segByteSize / 8
kbWords := make([]uint64, segWords)

// 清零操作更快
for i := 0; i < segWords; i++ {
    kbWords[i] = 0
}

// 标记合数时使用位运算
mask := uint64(1) << (bitPos & 63)
kbWords[wordIdx] |= mask

4. 并行化优化

当前使用runtime.GOMAXPROCS(0),可以更精细地控制goroutine数量:

// 根据数据大小动态调整goroutine数量
numCPU := runtime.NumCPU()
if segByteSize < 1024*1024 { // 小于1MB
    numCPU = 1
}
var wg sync.WaitGroup
chunkSize := segByteSize / numCPU

for i := 0; i < numCPU; i++ {
    wg.Add(1)
    go func(start, end int) {
        defer wg.Done()
        // 处理[start, end)区间的数据
    }(i*chunkSize, (i+1)*chunkSize)
}
wg.Wait()

5. 使用汇编优化关键函数

对于最热点的函数,可以使用Go汇编:

// go:noescape
// go:nosplit
func markNonPrimes(ptr *uint64, val uint64)

// 对应的汇编实现(amd64)
// TEXT ·markNonPrimes(SB), NOSPLIT, $0
//     MOVQ ptr+0(FP), DI
//     MOVQ val+8(FP), AX
//     ORQ AX, (DI)
//     RET

6. 预计算和查找表优化

// 预计算模运算结果
var modResults [][]int
func init() {
    modResults = make([][]int, 2310) // modpg
    for i := 0; i < 2310; i++ {
        modResults[i] = make([]int, len(residues))
        for j, r := range residues {
            modResults[i][j] = (i + r) % 2310
        }
    }
}

// 使用时直接查表
resIndex := modResults[val%2310][j]

7. 内存对齐优化

type Segment struct {
    data   []uint64 `aligned:"64"` // 64字节对齐
    start  uint64
    end    uint64
}

// 分配对齐的内存
func newAlignedSlice(size int) []uint64 {
    data := make([]uint64, size+8)
    addr := uintptr(unsafe.Pointer(&data[0]))
    offset := (64 - (addr % 64)) % 64
    return data[offset/8 : offset/8+size]
}

8. 使用更快的数学运算

// 使用位运算代替除法
func fastMod64(x, y uint64) uint64 {
    return x - (x/y)*y
}

// 使用乘法逆元
var invMod [256]uint64
func init() {
    for i := 1; i < 256; i++ {
        invMod[i] = uint64((1<<64)/uint64(i))
    }
}

这些优化通常能带来20%-50%的性能提升,具体取决于硬件和数据集大小。建议使用Go的pprof工具分析热点函数,针对性地进行优化。

回到顶部