Golang与Kotlin性能对比 - 第N个质数计算

Golang与Kotlin性能对比 - 第N个质数计算 我进行了一项Go与Kotlin的性能对比测试,决定比较两者在计算第N个质数时的表现。根据我的测试,我发现平均而言,Go计算任意第N个质数所需的时间大约是Kotlin的两倍。例如,如果Kotlin计算第100个质数需要~250,000ns,那么Go则需要~500,000ns

顺便提一下,我知道计算第N个质数存在优化方法,例如埃拉托斯特尼筛法,但我最初是在RESTful微服务中使用这两个函数来模拟繁重的计算工作。我在下面贴出了我使用的两个函数。

在这方面,Kotlin是否真的比Go更快?还是我遗漏了什么?

非常感谢

fun simplePrime(primeN : Int) : Int {
    var primeI = 0
    var currNum = 1
    while (primeI < primeN) {
        currNum += 1
        var isPrime = true
        for (i in 2..currNum/2) {
            if (num % i == 0) {
                isPrime = false
                break
            }
        }
        if (isPrime) {
            primeI += 1
        }
    }
    return currNum
}
func getNthPrime(num int) int {
	primeNum := 0
	currNum := 1
	for primeNum < num {
		currNum++
		isPrime := true
		for i := 2; i < currNum/2; i++ {
			if currNum % i == 0 {
				isPrime = false
				break
			}
		}
		if isPrime {
			primeNum++
		}
	}
	return primeNum
}

更多关于Golang与Kotlin性能对比 - 第N个质数计算的实战教程也可以访问 https://www.itying.com/category-94-b0.html

13 回复

这是一个绝佳的观点!

更多关于Golang与Kotlin性能对比 - 第N个质数计算的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


算法认为4是质数?…

抱歉,我发布的函数版本不一致。我会更新帖子。

我最初让两个函数都遍历整个数字集合,但后来进行了一些优化以使其更符合实际情况,只是我没有意识到我发布的是 Kotlin 的优化版本,而不是 Go 的。不过,根据我找到的数字和数据,我测试时对两者都使用了“优化”后的函数。

在这种情况下,您需要分享更多关于您实际如何进行基准测试的细节。

顺便提一下,对同一算法在两种不同编程语言中进行有意义的比较可能相当困难。这完全取决于具体测量的是什么、测试函数执行了多少次(例如,CPU缓存是冷启动还是热启动)等因素。

呃,你是在暗示它不是这样吗?思考

幸好我没有在任何地方使用这段代码汗颜微笑

我相信在其他条件相同的情况下,Go 的性能略高一些,并且这项研究显示了相当令人印象深刻的指标。 此外,Go 提供了强大的故障排除功能,如这篇文章所述。

Kotlin 代码和 Go 代码并不相同。在 Go 中,循环条件应该是

for i := 2; i < currNum/2; i++

这样才能与 Kotlin 版本保持一致。经过此修正后,Go 代码的运行时间实际上减少了约 50%,因此我认为 Kotlin 和 Go 的性能大致相当。

除此之外,我建议在两个版本中使用相同的变量名,这样会更便于代码比较。我认为 Kotlin 代码也存在一些拼写错误:变量 num 应该为 currNum

所以,如果我运行一个 Go 基准测试,每次迭代得到大约 100,000ns

goos: linux
goarch: amd64
BenchmarkNthPrime-8   	   10000	    104896 ns/op
PASS

如果我使用 Kotlin 基准测试运行 Kotlin 函数,每次迭代得到大约 40,000ns

我之前进行的原始测试涉及读取从另一台服务器输入的请求,该服务器每秒触发一次查询,然后我计算了所有迭代的平均值。我猜测差异在于测试方法?

无论如何,差异仍然存在。我假设在这种特定场景下,Kotlin 的表现优于 Go。我只是对如此简单/常见的场景存在如此显著的差异感到惊讶。

如果我说错了请纠正我,但 Kotlin 的 Int 大小是 32 位,而 Golang 的版本取决于平台,因此在 64 位平台上你使用的是 64 位整数。 如果你想比较它们,必须使用 int32。 我对两种变体进行了小规模基准测试,始终得到以下结果:

Benchmark_getNthPrime
Benchmark_getNthPrime-16      	       1	3251722454 ns/op
Benchmark_getNthPrime32
Benchmark_getNthPrime32-16    	       1	1365153369 ns/op

看起来 int(64 位版本)比使用 int32 慢 2.38 倍。

请告诉我你在你的设置中发现了什么。

BullardLA: 呃,你的意思是它不是这样吗?🤔

只是有一点点啦 😊

我认为这段代码应该会稍微好一点。

package main

import "fmt"

// IsPrime ...
func IsPrime(n int) bool {
	switch {
	case n == 2:
		return true
	case n < 2 || n%2 == 0:
		return false

	default:
		for i := 3; i*i <= n; i += 2 {
			if n%i == 0 {
				return false
			}
		}
	}
	return true
}

func getNthPrime(n int) int {
	var result int = 2
	for {

		if IsPrime(result) {
			n--
		}

		if n == 0 {
			return result
		}

		result++
	}
}

func main() {
	fmt.Println(getNthPrime(100))
}

我在一台私有服务器上运行了对比测试,服务器配置为 Intel XEON-E3-1270-V3-Haswell 3.5GHz 处理器和 2x 4GB Kingston 4GB DDR3 1Rx8 内存。我禁用了交换分区,并运行了数千次迭代的测试。每次迭代之间,我让程序休眠一秒。我通过用 nanoTime 调用来包裹函数并计算时间差来简单地计时,虽然我知道这可能存在缺陷,但无论如何,我认为这无法解释我观察到的差异。测试期间没有运行任何 CPU 密集型后台进程(CPU 空闲率约 100%)。

我的结果:

  • n=100 时,
    • Go:每次迭代约 600,000 纳秒
    • Kotlin:每次迭代约 300,000 纳秒
  • n=1000
    • Go:每次迭代约 50,000,000 纳秒
    • Kotlin:每次迭代约 25,000,000 纳秒

我进行的其他性能测试表明 Go 的表现优于 Kotlin(例如冒泡排序、字符串转哈希映射),但我主要想知道我是否在某些根本性的地方做错了,或者 Go 是否在某些特定场景(比如这个)中性能表现不佳。

在我的旧笔记本上(配备 Intel® Core™ i5-6200U CPU @ 2.30GHz,且并非空闲状态),使用以下基准测试:

package nthprime

import "testing"

var result int

func getNthPrime(num int) int {
	primeNum := 0
	currNum := 1
	for primeNum < num {
		currNum++
		isPrime := true
		for i := 2; i < currNum / 2; i++ {
			if currNum%i == 0 {
				isPrime = false
				break
			}
		}
		if isPrime {
			primeNum++
		}
	}
	return primeNum
}

func BenchmarkNthPrime(b *testing.B) {
	var r int
	for i := 0; i < b.N; i++ {
		r = getNthPrime(100)
	}
	result = r
}

我得到了以下结果:

$  go test -bench=. .
goos: linux
goarch: amd64
pkg: nthprime
BenchmarkNthPrime-4        10000            151375 ns/op
PASS
ok      nthprime       1.532s

根据 cpu.userbenchmark.com 上的 CPU 对比,Intel XEON 的性能要好得多,但我在笔记本电脑上测试得到的每次操作耗时(约 150000 ns/op)仍然更优。

你的测试结果确实令人意外,因为Go通常以其高性能著称。从你提供的代码来看,两个算法逻辑基本相同,但存在一些关键差异可能导致性能差距。

首先,你的Go代码中有个明显错误:for i := 2; i < currNum/2; i++ 应该是 i <= currNum/2,否则会错误地将某些质数判定为合数。不过这个错误不会影响性能。

更重要的是,Kotlin代码中使用了 for (i in 2..currNum/2),这个范围包含两端,而Go代码中 i < currNum/2 不包含上界。这会导致Go检查的除数更少,理论上应该更快。

让我提供一个修正后的Go版本,并进行公平比较:

func getNthPrime(num int) int {
    primeNum := 0
    currNum := 1
    
    for primeNum < num {
        currNum++
        isPrime := true
        
        // 优化1:使用平方根作为上限
        limit := int(math.Sqrt(float64(currNum)))
        for i := 2; i <= limit; i++ {
            if currNum%i == 0 {
                isPrime = false
                break
            }
        }
        
        if isPrime {
            primeNum++
        }
    }
    return currNum
}

性能差异的可能原因:

  1. 编译器优化:Kotlin/JVM可能进行了更激进的循环优化
  2. 边界检查:Go在数组/切片访问时有边界检查,但这里不适用
  3. 测试方法:确保测试时使用相同的环境和预热

建议的测试代码:

func benchmarkPrime(n int) {
    start := time.Now()
    result := getNthPrime(n)
    elapsed := time.Since(start)
    fmt.Printf("第%d个质数: %d, 耗时: %v\n", n, result, elapsed)
}

func main() {
    // 预热
    getNthPrime(100)
    
    // 正式测试
    benchmarkPrime(100)
    benchmarkPrime(1000)
}

对于微服务场景,还需要考虑:

  • 垃圾回收策略差异
  • 内存分配模式
  • 并发处理能力

Go在并发计算方面通常有优势,你可以尝试使用goroutine并行检查多个候选数。

回到顶部