我使用Go是为了提高代码效率,但当我使用Go读写文件时,我发现它的阅读效率没有Perl高。是我的代码问题还是其他原因?
构建输入文件:
# Input File:
for i in $(seq 1 600000) do echo SERVER$((RANDOM%800+100)),$RANDOM,$RANDOM,$RANDOM >> sample.csv done
用Perl读写文件:
time cat sample.csv | perl -ne 'chomp;print"$_"' > out.txt
real 0m0.249s
user 0m0.083s
sys 0m0.049s
使用Go读写文件:
package main
import (
"bufio"
"fmt"
"io"
"os"
"strings"
)
func main() {
filepath := "./sample.csv"
file, err := os.OpenFile(filepath, os.O_RDWR, 0666)
if err != nil {
fmt.Println("Open file error!", err)
return
}
defer file.Close()
buf := bufio.NewReader(file)
for {
line, err := buf.ReadString('\n')
line = strings.TrimSpace(line)
fmt.Println(line)
if err != nil {
if err == io.EOF {
fmt.Println("File read ok!")
break
} else {
fmt.Println("Read file error!", err)
return
}
}
}
}
然后我跑:
time go run read.go > out.txt
real 0m2.332s
user 0m0.326s
sys 0m2.038s
为什么Go的读写速度比Perl慢10倍?
1条答案
按热度按时间wribegjk1#
你这是拿苹果和橘子做比较。
至少有两个方法上的错误:
1.您的Perl咒语测量
cat
如何读取文件并通过pipe(2)
发送其内容,perl
从那里读取数据,处理它并写入其标准输出。fmt.Print*
调用),而在Perl代码中,对标准输出的写入是引用文档-«<...>如果输出到终端,则通常是行缓冲,否则是块缓冲»。让我们试着把苹果和苹果进行比较。
首先,这里有一个类似的Go实现:
让我们将其保存为
chomp.go
并测量:1.构建代码:
$ go build chomp.go
1.生成输入文件:
$ for i in $(seq 1 600000); do echo SERVER$((RANDOM%800+100)),$RANDOM,$RANDOM,$RANDOM; done >sample.csv
1.运行Perl代码:
1.再次运行它,确保它已经从文件系统缓存中读取了输入文件:
请注意执行时间是如何缩短的。
1.在缓存的输入上运行Go代码:
1.确保结果相同:
$ cmp out1.txt out2.txt
正如您所看到的,在我的
linux/amd64
系统上使用SSD,结果大致相同。好吧,我还应该声明,为了得到合理的结果,你需要运行每个命令,比如,1000次,并对每个批处理中的结果取平均值,然后比较这些数字,但我认为这足以证明你的方法存在什么问题。
还有一件事要考虑:这两个程序的运行时间主要由文件系统I/O控制,所以如果你认为Go在这方面会更快,你的期望是没有根据的:这两个程序在内核系统中的大部分时间 sleep 调用
read(2)
和write(2)
。在某些涉及CPU处理的情况下,Go程序可能比Perl程序快 *(特别是 * 如果它是为了利用多核系统而编写的),但您的示例根本不是这种情况。哦,让我们把未陈述的事实说清楚:虽然Go语言规范没有说明Go语言实现的runtime system必须如何完成,但现有的两个最先进的Go语言实现(其中一个你表面上正在使用)都依赖于AOT,而
go run
是一个一次性的丢弃gigs * 不是 * 既不用于严肃的工作,也不用于执行任何严重复杂程度的代码。简单地说,Go-that-you-are-using不是一种解释型语言,尽管go run
的可用性可能使它看起来像是解释型语言。事实上,它做了普通go build
会做的事情,然后运行产生的可执行文件,然后将其丢弃。¹你可能会说Perl也处理“源代码”,但Perl解释器高度优化以处理脚本,而Go的构建工具链-虽然与大多数其他编译语言相比非常快-并没有针对 * 进行优化。
可能更明显的区别是,Perl解释器实际上 * 解释 * 您的(非常简单的)脚本,而
chomp
和print
是所谓的“内置”函数,由解释器随时提供给执行脚本。相比之下,构建Go程序涉及编译器解析源代码文件并将其转换为机器码,链接器实际上阅读Go标准库的编译包的文件-所有这些都是import
ed的,-从它们中提取代码位,组合所有这些机器代码并写出一个可执行的映像文件(这很像perl
二进制文件本身!);当然,这是一个非常消耗资源的过程,与实际的程序执行无关。