为什么在Go中阅读和写入文件比在Perl中慢得多?

gab6jxml  于 2023-05-20  发布在  Go
关注(0)|答案(1)|浏览(134)

我使用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倍?

wribegjk

wribegjk1#

你这是拿苹果和橘子做比较。
至少有两个方法上的错误:
1.您的Perl咒语测量cat如何读取文件并通过pipe(2)发送其内容,perl从那里读取数据,处理它并写入其标准输出。

  • 你的围棋咒语
  • 衡量Go工具链的完整构建过程(包括编译、链接和写出可执行映像文件)*,然后 * 运行编译后的程序¹,以及
  • 测量对标准输出的无缓冲写入(fmt.Print*调用),而在Perl代码中,对标准输出的写入是引用文档-«<...>如果输出到终端,则通常是行缓冲,否则是块缓冲»。

让我们试着把苹果和苹果进行比较。
首先,这里有一个类似的Go实现:

package main

import (
    "bufio"
    "bytes"
    "fmt"
    "os"
)

func main() {
    in := bufio.NewScanner(os.Stdin)
    out := bufio.NewWriter(os.Stdout)

    for in.Scan() {
        s := bytes.TrimSpace(in.Bytes())

        if _, err := out.Write(s); err != nil {
            fmt.Fprint(os.Stderr, "failed to write file:", err)
            os.Exit(1)
        }
    }

    if err := out.Flush(); err != nil {
        fmt.Fprint(os.Stderr, "failed to write file:", err)
        os.Exit(1)
    }

    if err := in.Err(); err != nil {
        fmt.Fprint(os.Stderr, "reading failed:", err)
        os.Exit(1)
    }
}

让我们将其保存为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代码:

$ time { perl -ne 'chomp; print "$_";' <sample.csv >out1.txt; }

real    0m0.226s
user    0m0.102s
sys 0m0.048s

1.再次运行它,确保它已经从文件系统缓存中读取了输入文件:

$ time { perl -ne 'chomp; print "$_";' <sample.csv >out1.txt; }

real   0m0.123s
user   0m0.090s
sys    0m0.033s

请注意执行时间是如何缩短的。
1.在缓存的输入上运行Go代码:

$ time { ./chomp <sample.csv >out2.txt; }

real   0m0.063s
user   0m0.032s
sys    0m0.032s

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解释器实际上 * 解释 * 您的(非常简单的)脚本,而chompprint是所谓的“内置”函数,由解释器随时提供给执行脚本。相比之下,构建Go程序涉及编译器解析源代码文件并将其转换为机器码,链接器实际上阅读Go标准库的编译包的文件-所有这些都是import ed的,-从它们中提取代码位,组合所有这些机器代码并写出一个可执行的映像文件(这很像perl二进制文件本身!);当然,这是一个非常消耗资源的过程,与实际的程序执行无关。

相关问题