Golang中的磁盘写入性能

13
在以下代码中,我使用golang中的bufio将消息写入文件。我的磁盘I / O速度约为1000M / s。奇怪的是,当写入文件的大小小于20G时,写入速度约为每秒800M〜900M,略低于I / O速度。但是,当文件大小超过21G时,我发现写入速度约为每秒200M,远低于I / O速度。我不知道原因,有人可以帮助我吗?谢谢。
package main

import "fmt"
import (
    "os"
    "time"
    "flag"
    "bufio"
)

func main() {
    var pRound = flag.Int64("round", 3500000, "loop round")
    flag.Parse()

    var message string
    for i := 0; i < 1024; i++ {
        message += "1234567890"
    }
    message += "\n"

    f, err := os.OpenFile("server", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
    if err != nil {
        fmt.Println(err)
        return
    }
    w := bufio.NewWriter(f)

    var i int64 = 0
    round := *pRound
    start := time.Now()
    for i = 0; i < round; i++ {
        w.WriteString(message)
    }
    w.Flush()
    f.Close()
    end := time.Now()

    nanoseconds := end.Sub(start).Nanoseconds()
    speed := 1000000000 * round / nanoseconds

    fmt.Printf("round: %v\n", round)
    fmt.Printf("Nanoseconds: %v\n", nanoseconds)
    fmt.Printf("speed: %v\n", speed)
}

我认为我提出了一个愚蠢的问题。在回应 Vorsprung 后,我在此展示我的 C 代码,并且在 C 语言中再次进行了测试。我发现与 Go 语言得到了相同的结果。 我的测试结果是:

./a.out 10000
seconds: 7
milliseconds: 260910
total: 10485760000 Bytes
total: 10000 M
total: 9.76562 G
speed: 1377.24 M/s


./a.out 20000
seconds: 24
milliseconds: 7249
total: 20971520000 Bytes
total: 20000 M
total: 19.5312 G
speed: 833.082 M/s


./a.out 30000
seconds: 80
milliseconds: 518970
total: 31457280000 Bytes
total: 30000 M
total: 29.2969 G
speed: 372.583 M/s



./a.out 40000
seconds: 134
milliseconds: 615910
total: 41943040000 Bytes
total: 40000 M
total: 39.0625 G
speed: 297.142 M/s

下面是我的C代码:
#include <fcntl.h>
#include <errno.h>
#include <limits.h>
#include <sys/uio.h>
#include <time.h>
#include <sys/time.h>
#include <iostream>
#include <string.h>
#include <stdlib.h>

int main(int argc, char* argv[]) {
  if (argc != 2) {
    std::cout << "usage: " << argv[0] << " round" << std::endl;
    return -1;
  }
  int round = atoi(argv[1]);

  int fd = open("file.data", O_CREAT | O_APPEND | O_RDWR, 0666);
  if (fd == -1) {
    std::cout << "open file error: " << strerror(errno) << std::endl;
    return -1;
  }

  struct iovec vec[IOV_MAX];
  int len = 1024;
  for (int i = 0; i < IOV_MAX; i++) {
    vec[i].iov_base = new char[len];
    vec[i].iov_len = len;

    char *buf = NULL;
    for (int j = 0; j < len - 1; j++) {
      buf = (char*)vec[i].iov_base;
      buf[j] = j % 10 + '1';
    }
    buf[len - 1] = '\n';
  }

  timeval tv1;
  gettimeofday(&tv1, NULL);
  for (int i = 0; i < round; i++) {
    writev(fd, vec, IOV_MAX);
  }

  close(fd);

  timeval tv2;
  gettimeofday(&tv2, NULL);

  for (int i = 0; i < IOV_MAX; i++) {
     char* buf = (char*)vec[i].iov_base;
     delete[] buf;
  }

  std::cout << "seconds: " << tv2.tv_sec - tv1.tv_sec << std::endl;
  std::cout << "milliseconds: " << tv2.tv_usec - tv1.tv_usec << std::endl;
  int64_t total = int64_t(len) * IOV_MAX * round;
  float t = (tv2.tv_sec - tv1.tv_sec) * 1000000.0 + (tv2.tv_usec - tv1.tv_usec);
  float speed = 1000000.0 * total / t / 1024 / 1024;
  std::cout << "total: " << total << " Bytes" << std::endl;
  std::cout << "total: " << total / 1024.0 / 1024.0 << " M" << std::endl;
  std::cout << "total: " << total / 1024.0 / 1024.0 / 1024.0 << " G" << std::endl;
  std::cout << "speed: " << speed << " M/s" << std::endl;
  return 0;
}

现在我的diskio.go测试结果在这里展示。因为我不知道如何在评论中注释易于阅读的结果,所以我在这里展示。

time ./diskio -size=4
written: 4294967296B 26237051975ns 4.29GB 26.24s 163.70MB/s
real    0m26.980s
user    0m0.397s
sys     0m4.874s


time ./diskio -size=8
written: 8589934592B 57803019028ns 8.59GB 57.80s 148.61MB/s
real    0m59.192s
user    0m0.813s
sys     0m9.607s



time ./diskio -size=10
written: 10737418240B 68331989999ns 10.74GB 68.33s 157.14MB/s
real    1m10.288s
user    0m0.946s
sys     0m12.024s




time ./diskio -size=20
written: 21474836480B 141169506440ns 21.47GB 141.17s 152.12MB/s
real    2m25.037s
user    0m1.881s
sys     0m24.029s


time ./diskio -size=30
written: 32212254720B 203807569664ns 32.21GB 203.81s 158.05MB/s
real    3m29.345s
user    0m2.925s
sys     0m33.528s

diskio.go源自https://dev59.com/KlYN5IYBdhLWcg3wK1fO#47889346

我认为我得到了答案,测试结果是由于磁盘缓冲区原因。我使用hdparm命令测试了我的磁盘速度,并获得了以下结果:

hdparm -Tt /dev/sde1
/dev/sde1:
Timing cached reads:   18166 MB in  2.00 seconds = 9093.93 MB/sec
Timing buffered disk reads: 584 MB in  3.01 seconds = 194.18 MB/sec

也许我的程序在文件大小不到约18166M时会向缓冲区写入字节。之后,程序会开始向磁盘写入数据,因此速度会变慢。


可能是因为此时您的磁盘缓冲区已满? - Jonathan Hall
几个月前,我用C语言编写了一个类似的进程,使用了writev方法。我发现写入速度几乎等于磁盘I/O速度。这能证明它与磁盘缓存无关吗? - Nature.Li
1
我的磁盘I/O速度大约是1000M/s。但事情并不那么简单。你的IO操作有多大?是否存在任何寻道操作,会在IO操作之间移动任何物理磁头?我保证,如果你对物理旋转磁盘进行512字节的随机位置I/O操作,你不会看到任何接近“大约1000M/s”的IO速率。在消费级SATA硬盘上,你可能会看到低至20 KB/sec甚至更低的IO速率。 - Andrew Henle
我认为我们需要看一下C程序或其他证据... - Vorsprung
我在这里上传了我的C代码。我认为我做错了什么。感谢大家。 - Nature.Li
显示剩余3条评论
3个回答

6
您的问题无法重现。您的代码有漏洞。
我们会预期磁盘写入时间受到许多因素的影响,如程序、其他程序、操作系统、硬件等等。
启动一个独立的、专用的机器,并运行diskio.go程序。您得到了什么结果?例如,在我的机器上:
$ go build diskio.go
$ time ./diskio -size=32
written: 34359738368B 154333936544ns 34.36GB 154.33s 222.63MB/s
real    2m35.323s
user    0m6.418s
sys     0m41.994s
$ time ./diskio -size=16
written: 17179869184B 77901269159ns 17.18GB 77.90s 220.53MB/s
real    1m18.746s
user    0m2.849s
sys     0m21.721s
$ time ./diskio -size=8
written: 8589934592B 38940248134ns 8.59GB 38.94s 220.59MB/s
real    0m39.625s
user    0m1.719s
sys     0m12.493s
$ time ./diskio -size=1
written: 1073741824B 4738082404ns 1.07GB 4.74s 226.62MB/s
real    0m4.851s
user    0m0.069s
sys     0m0.755s
$ 

正如预期的那样,程序中花费的时间很少,操作系统中花费的时间更多,大量时间用于等待磁盘。写入速度没有急剧变化。

diskio.go

package main

import (
    "bufio"
    "flag"
    "fmt"
    "os"
    "time"
)

func writeFile(fSize int64) error {
    fName := `/home/peter/diskio` // test file
    defer os.Remove(fName)
    f, err := os.Create(fName)
    if err != nil {
        return err
    }
    const defaultBufSize = 4096
    buf := make([]byte, defaultBufSize)
    buf[len(buf)-1] = '\n'
    w := bufio.NewWriterSize(f, len(buf))

    start := time.Now()
    written := int64(0)
    for i := int64(0); i < fSize; i += int64(len(buf)) {
        nn, err := w.Write(buf)
        written += int64(nn)
        if err != nil {
            return err
        }
    }
    err = w.Flush()
    if err != nil {
        return err
    }
    err = f.Sync()
    if err != nil {
        return err
    }
    since := time.Since(start)

    err = f.Close()
    if err != nil {
        return err
    }
    fmt.Printf("written: %dB %dns %.2fGB %.2fs %.2fMB/s\n",
        written, since,
        float64(written)/1000000000, float64(since)/float64(time.Second),
        (float64(written)/1000000)/(float64(since)/float64(time.Second)),
    )
    return nil
}

var size = flag.Int("size", 8, "file size in GiB")

func main() {
    flag.Parse()
    fSize := int64(*size) * (1024 * 1024 * 1024)
    err := writeFile(fSize)
    if err != nil {
        fmt.Fprintln(os.Stderr, fSize, err)
    }
}

示例: https://play.golang.org/p/vnjnpgMzsV


我试图在这里注释代码,但我不知道如何操作。所以我又在我的问题中上传了测试结果。我认为我在速度测试方面做错了什么。谢谢。 - Nature.Li
谢谢。我想我已经得到了答案。 - Nature.Li
非常好的帖子,谢谢!我正在尝试理解你的说法,“操作系统中花费更多时间,等待磁盘的时间也很长”。由于没有具体的度量磁盘的指标,我认为您是在说(即使只是效果上),real - (sys + user) = "磁盘延迟"? https://dev59.com/4XRB5IYBdhLWcg3wroyB#62975331 和 https://dev59.com/4XRB5IYBdhLWcg3wroyB#53937376(*两个进程的sys时间与单个进程的相同,但wall time更长,因为这些进程争夺磁盘读取访问权限。*)似乎支持这种思路。再次感谢。 - Zach Young

1

我想我已经得到了这个问题的答案。因为我的磁盘I/O速度约为194M/s,磁盘缓存I/O速度约为9093M/s。但磁盘缓存大小约为18166M。因此,当文件大小小于20G时,写入速度更快。我使用以下命令测试我的磁盘I/O速度:

hdparm -Tt /dev/sde1
/dev/sde1:
Timing cached reads:   18166 MB in  2.00 seconds = 9093.93 MB/sec
Timing buffered disk reads: 584 MB in  3.01 seconds = 194.18 MB/sec

0
读取了“18166 MB在2.00秒内”。那不是您缓存的大小。可能有带有巨大缓存的磁盘,但它们的范围更多是三位数字MB。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接