为什么 Go 中读写文件比 Perl 慢很多?

qq.*_*ang 2 perl benchmarking go

我使用Go是为了提高代码效率,但是当我使用Go读写文件时,发现它的读写效率没有Perl高。是我代码的问题还是其他原因?

\n

构建输入文件:

\n
# Input File:\nfor i in $(seq 1 600000) do     echo SERVER$((RANDOM%800+100)),$RANDOM,$RANDOM,$RANDOM >> sample.csv done\n
Run Code Online (Sandbox Code Playgroud)\n

使用 Perl\xef\xbc\x9a 读写文件

\n
time cat sample.csv | perl -ne 'chomp;print"$_"' > out.txt\n
Run Code Online (Sandbox Code Playgroud)\n
real    0m0.249s\nuser    0m0.083s\nsys 0m0.049s\n
Run Code Online (Sandbox Code Playgroud)\n

使用 Go 读写文件:

\n
package main\n\nimport (\n    "bufio"\n    "fmt"\n    "io"\n    "os"\n    "strings"\n)\n\nfunc main() {\n\n    filepath := "./sample.csv"\n    file, err := os.OpenFile(filepath, os.O_RDWR, 0666)\n    if err != nil {\n        fmt.Println("Open file error!", err)\n        return\n    }\n    defer file.Close()\n    buf := bufio.NewReader(file)\n    for {\n        line, err := buf.ReadString('\\n')\n        line = strings.TrimSpace(line)\n        fmt.Println(line)\n        if err != nil {\n            if err == io.EOF {\n                fmt.Println("File read ok!")\n                break\n            } else {\n                fmt.Println("Read file error!", err)\n                return\n            }\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

然后我运行:

\n
time go run read.go > out.txt\n
Run Code Online (Sandbox Code Playgroud)\n
real    0m2.332s\nuser    0m0.326s\nsys 0m2.038s\n
Run Code Online (Sandbox Code Playgroud)\n

为什么 Go 的读写速度比 Perl 慢近 10 倍?

\n

kos*_*tix 15

您正在将苹果与橙子进行比较。

\n

至少有两个方法论错误:

\n
    \n
  1. 您的 Perl 咒语测量如何cat读取文件并将其内容发送到pipe(2),并perl从那里读取数据,处理它并将结果写入其标准输出。

    \n
  2. \n
  3. 你的围棋咒语

    \n
      \n
    • 测量 Go 工具链的完整构建过程(包括编译、链接和写出可执行映像文件),然后运行编译后的程序\xc2\xb9,以及
    • \n
    • 测量对 stdout(fmt.Print*调用)的无缓冲写入,而在 Perl 代码中写入标准输出 - 引用文档- “如果输出到终端,则通常可以进行行缓冲,否则进行块缓冲。”
    • \n
    \n
  4. \n
\n

让我们试着比较一下苹果。

\n

首先,这是一个类似的 Go 实现:

\n
package main\n\nimport (\n    "bufio"\n    "bytes"\n    "fmt"\n    "os"\n)\n\nfunc main() {\n    in := bufio.NewScanner(os.Stdin)\n    out := bufio.NewWriter(os.Stdout)\n\n    for in.Scan() {\n        s := bytes.TrimSpace(in.Bytes())\n\n        if _, err := out.Write(s); err != nil {\n            fmt.Fprint(os.Stderr, "failed to write file:", err)\n            os.Exit(1)\n        }\n    }\n\n    if err := out.Flush(); err != nil {\n        fmt.Fprint(os.Stderr, "failed to write file:", err)\n        os.Exit(1)\n    }\n\n    if err := in.Err(); err != nil {\n        fmt.Fprint(os.Stderr, "reading failed:", err)\n        os.Exit(1)\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

让我们将其另存为chomp.go并测量:

\n
    \n
  1. 构建代码:

    \n

    $ go build chomp.go

    \n
  2. \n
  3. 生成输入文件:

    \n

    $ for i in $(seq 1 600000); do echo SERVER$((RANDOM%800+100)),$RANDOM,$RANDOM,$RANDOM; done >sample.csv

    \n
  4. \n
  5. 运行 Perl 代码:

    \n
    $ time { perl -ne \'chomp; print "$_";\' <sample.csv >out1.txt; }\n\nreal    0m0.226s\nuser    0m0.102s\nsys 0m0.048s\n
    Run Code Online (Sandbox Code Playgroud)\n
  6. \n
  7. 再次运行它以确保它已从文件系统缓存中读取输入文件:

    \n
    $ time { perl -ne \'chomp; print "$_";\' <sample.csv >out1.txt; }\n\nreal   0m0.123s\nuser   0m0.090s\nsys    0m0.033s\n
    Run Code Online (Sandbox Code Playgroud)\n

    请注意执行时间是如何减少的。

    \n
  8. \n
  9. 在缓存的输入上运行 Go 代码:

    \n
    $ time { ./chomp <sample.csv >out2.txt; }\n\nreal   0m0.063s\nuser   0m0.032s\nsys    0m0.032s\n
    Run Code Online (Sandbox Code Playgroud)\n
  10. \n
  11. 确保结果相同:

    \n

    $ cmp out1.txt out2.txt

    \n
  12. \n
\n

正如您所看到的,在我的linux/amd64配备 SSD 的系统上,结果大致相同。

\n

好吧,我还应该指出,为了获得合理的结果,您需要运行每个命令,例如 1000 次,并对每个批次中的结果进行平均,然后比较这些数字,但我认为这足以证明你的方法有什么问题。

\n

还有一件事需要考虑:这两个程序的运行时间绝大多数由文件系统 I/O 主导,所以如果你认为 Go 会更快,那么你的期望是没有根据的:这两个程序大部分时间都在内核中休眠的系统调用read(2)write(2). 在某些涉及 CPU 运算的情况下(特别是如果它是为了利用多核系统而编写的),Go 程序可能比 Perl 程序更快,但您的示例根本不是这种情况。

\n

哦,只是为了明确未说明的事实:虽然 Go 语言规范没有说明Go 实现的运行时系统必须如何完成,但这两个现有的最先进的 Go 实现(其中之一你\表面上使用)依赖AOT,并且go run是一种一次性的一次性工作的黑客,既不适合严肃的工作,也不适合执行任何严重复杂程度的代码。简而言之,Go-that-you-are-using 并不是一种解释性语言,尽管它的可用性go run可能使它看起来如此。事实上,它执行正常操作go build,然后运行生成的可执行文件,然后将其丢弃。

\n
\n

\xc2\xb9\xc2\xa0 你可能会想说 Perl 也处理“源代码”,但 Perl 解释器在处理脚本方面进行了高度优化,而 Go 的构建工具链\xe2\x80\x93与大多数其他编译语言相比,速度非常快\xe2\x80\x93 并未对此进行优化
\n可能更明显的区别是,Perl 解释器实际上解释您的(非常简单的)脚本,并且chompprint所谓的“内置函数”\xe2\x80\x93 函数,很容易由口译员。与构建Go程序相比,编译器解析源代码文件并将其转换为机器代码,链接器实际上读取Go标准库的编译包\xe2\x80\x94的文件,所有这些都是edimport的,\xe2\x80\x93 从中获取代码位,组合所有这些机器代码位并写出可执行映像文件(这与二进制文件perl本身非常相似!);当然,这是一个非常消耗资源的过程,与实际的程序执行无关。

\n