为什么我的 Rust 程序在执行相同的位运算和 I/O 操作时比 Go 程序慢了 4 倍?

huangapple go评论91阅读模式
英文:

Why is my Rust program 4x slower than a Go program doing the same bitwise and I/O operations?

问题

我有一个用于实现64位无符号整数的暴力奇偶校验的Rust程序:

use std::io;
use std::io::BufRead;

fn parity(mut num: u64) -> u8 {
    let mut result: u8 = 0;
    while num > 0 {
        result = result ^ (num & 1) as u8;
        num = num >> 1;
    }
    result
}

fn main() {
    let stdin = io::stdin();
    let mut num: u64;
    let mut it = stdin.lock().lines();
    // skip 1st line with number of test cases
    it.next();
    for line in it {
        num = line.unwrap().parse().unwrap();
        println!("{}", parity(num));
    }
}

当我使用包含1000000个无符号整数的输入文件运行它时:

$ rustc parity.rs
$ time cat input.txt | ./parity > /dev/null
cat input.txt  0.00s user 0.02s system 0% cpu 4.178 total
./parity > /dev/null  3.87s user 0.32s system 99% cpu 4.195 total

然而,令人惊讶的是,用Go编写的几乎相同的程序运行速度快了4倍:

$ go build parity.go
$ time cat input.txt | ./parity > /dev/null
cat input.txt  0.00s user 0.03s system 3% cpu 0.952 total
./parity > /dev/null  0.63s user 0.32s system 99% cpu 0.955 total

以下是Go代码:

package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
)

func parity(line string) uint64 {
    var parity uint64
    u, err := strconv.ParseUint(line, 10, 64)
    if err != nil {
        panic(err)
    }
    for u > 0 {
        parity ^= u & 1
        u >>= 1
    }
    return parity
}

func main() {
    scanner := bufio.NewScanner(os.Stdin)
    // skip line with number of cases
    if !scanner.Scan() {
        // panic if there's no number of test cases
        panic("missing number of test cases")
    }
    for scanner.Scan() {
        fmt.Println(parity(scanner.Text()))
    }
    if err := scanner.Err(); err != nil {
        fmt.Fprintln(os.Stderr, "reading standard input:", err)
    }
}

版本信息:

$ rustc --version
rustc 1.7.0
$ go version
go version go1.6 darwin/amd64

输入文件的示例,第一行包含文件中输入值的数量:

8
7727369244898783789
2444477357490019411
4038350233697550492
8106226119927945594
1538904728446207070
0
1
18446744073709551615

为什么我编写的Rust和Go程序在性能上有如此大的差异?我预期Rust在这种情况下会比Go快一点。我在Rust代码中做错了什么吗?

英文:

I have a Rust program that implements a brute-force parity check for 64-bit unsigned integers:

<!-- language: rust -->

use std::io;
use std::io::BufRead;

fn parity(mut num: u64) -&gt; u8 {
    let mut result: u8 = 0;
    while num &gt; 0 {
        result = result ^ (num &amp; 1) as u8;
        num = num &gt;&gt; 1;
    }
    result
}

fn main() {
    let stdin = io::stdin();
    let mut num: u64;
    let mut it = stdin.lock().lines();
    // skip 1st line with number of test cases
    it.next();
    for line in it {
        num = line.unwrap().parse().unwrap();
        println!(&quot;{}&quot;, parity(num));
    }
}

When I feed it with input file containing 1000000 unsigned integers:

$ rustc parity.rs
$ time cat input.txt | ./parity &amp;&gt; /dev/null
cat input.txt  0.00s user 0.02s system 0% cpu 4.178 total
./parity &amp;&gt; /dev/null  3.87s user 0.32s system 99% cpu 4.195 total

And here comes a surprise - the effectively same program in Go does 4x faster:

$ go build parity.go
$ time cat input.txt | ./parity &amp;&gt; /dev/null
cat input.txt  0.00s user 0.03s system 3% cpu 0.952 total
./parity &amp;&gt; /dev/null  0.63s user 0.32s system 99% cpu 0.955 total

Here's the code in Go:

<!-- language: go -->

package main

import (
    &quot;bufio&quot;
    &quot;fmt&quot;
    &quot;os&quot;
    &quot;strconv&quot;
)

func parity(line string) uint64 {
    var parity uint64
    u, err := strconv.ParseUint(line, 10, 64)
    if err != nil {
        panic(err)
    }
    for u &gt; 0 {
        parity ^= u &amp; 1
        u &gt;&gt;= 1
    }
    return parity
}

func main() {
    scanner := bufio.NewScanner(os.Stdin)
    // skip line with number of cases
    if !scanner.Scan() {
        // panic if there&#39;s no number of test cases
        panic(&quot;missing number of test cases&quot;)
    }
    for scanner.Scan() {
        fmt.Println(parity(scanner.Text()))
    }
    if err := scanner.Err(); err != nil {
        fmt.Fprintln(os.Stderr, &quot;reading standard input:&quot;, err)
    }
}

Versions:

$ rustc --version
rustc 1.7.0
$ go version
go version go1.6 darwin/amd64

Sample of input file, first line contains number of input values in the file:

8
7727369244898783789
2444477357490019411
4038350233697550492
8106226119927945594
1538904728446207070
0
1
18446744073709551615

Why do the Rust and Go programs I've written have such a dramatic difference in performance? I expected Rust to be a bit faster than Go in this case. Am I doing something wrong in my Rust code?

答案1

得分: 6

我认为你没有使用优化进行编译。
尝试使用以下命令进行编译:

$ rustc -O parity.rs
英文:

I think you're not compiling with optimisation.
try

$ rustc -O parity.rs

答案2

得分: 0

你的基准测试没有测量奇偶校验。它测量的是输入加上奇偶校验再加上输出的时间。例如,在Go语言中,你同时测量了scanner.Scanstrconv.ParseUintfmt.Println,以及奇偶校验。

下面是一个只测量1000000次奇偶校验的Go基准测试。

parity_test.go:

package parity

import (
	"math/rand"
	"runtime"
	"testing"
)

func parity(n uint64) uint64 {
	var parity uint64
	for n > 0 {
		parity ^= n & 1
		n >>= 1
	}
	return parity
}

func init() { runtime.GOMAXPROCS(1) }

// 测量1000000次奇偶校验。
func BenchmarkParity1000000(b *testing.B) {
	n := make([]uint64, 1000000)
	for i := range n {
		r := uint64(rand.Uint32())
		n[i] = (r << 32) | r
	}
	p := parity(42)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		for _, n := range n {
			p = parity(n)
		}
	}
	b.StopTimer()
	_ = p
}

输出:

$ go test -bench=.
BenchmarkParity1000000	      50	  34586769 ns/op
$
英文:

Your benchmark doesn't measure the parity check. It measures input plus parity check plus output. For example, in Go, you measure scanner.Scan and strconv.ParseUint and fmt.Println as well as the parity check.

Here's a Go benchmark that just measures 1000000 parity checks.

parity_test.go:

package parity

import (
	&quot;math/rand&quot;
	&quot;runtime&quot;
	&quot;testing&quot;
)

func parity(n uint64) uint64 {
	var parity uint64
	for n &gt; 0 {
		parity ^= n &amp; 1
		n &gt;&gt;= 1
	}
	return parity
}

func init() { runtime.GOMAXPROCS(1) }

// Benchmark 1000000 parity checks.
func BenchmarkParity1000000(b *testing.B) {
	n := make([]uint64, 1000000)
	for i := range n {
		r := uint64(rand.Uint32())
		n[i] = (r &lt;&lt; 32) | r
	}
	p := parity(42)
	b.ResetTimer()
	for i := 0; i &lt; b.N; i++ {
		for _, n := range n {
			p = parity(n)
		}
	}
	b.StopTimer()
	_ = p
}

Output:

$ go test -bench=.
BenchmarkParity1000000	      50	  34586769 ns/op
$

huangapple
  • 本文由 发表于 2016年3月21日 07:14:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/36120811.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定