【问题标题】:Write operation cost写操作成本
【发布时间】:2016-01-06 08:18:48
【问题描述】:

我有一个将字符串写入文件的 Go 程序。我有一个循环,它被迭代 20000 次,在每次迭代中,我将大约 20-30 个字符串写入文件。我只是想知道将其写入文件的最佳方法。

  • 方法一:在代码开头保持打开文件指针, 为每个字符串编写它。它可以进行 20000*30 次写入操作。

  • 方法 2:使用 bytes.Buffer 将所有内容存储在缓冲区中,然后 把它写在最后。同样在这种情况下文件指针应该是 从代码的开头或代码的末尾打开。做 有关系吗?

我假设方法 2 应该会更好。有人可以用一个理由来证实这一点。 一次写作比定期写作更好。因为文件指针无论如何都会打开。 我正在使用f.WriteString(<string>)buffer.WriteString(<some string>) 缓冲区的类型为bytes.Bufferf 是打开的文件指针。

【问题讨论】:

    标签: performance go io


    【解决方案1】:

    bufio 包正是为此类任务而创建的。在进行系统调用之前,bufio.Writer 不会为每个写入调用进行系统调用,而是在内部存储器中缓冲固定数量的字节。在系统调用之后,内部缓冲区被重用于下一部分数据

    与您的第二种方法相比bufio.Writer

    • 进行更多系统调用(N/S 而不是 1
    • 使用更少的内存(S 字节而不是 N 字节)

    其中S - 是缓冲区大小(可以通过bufio.NewWriterSize 指定),N - 需要写入的数据的总大小。

    使用示例(https://play.golang.org/p/AvBE1d6wpT):

    f, err := os.Create("file.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()
    
    w := bufio.NewWriter(f)
    fmt.Fprint(w, "Hello, ")
    fmt.Fprint(w, "world!")
    err = w.Flush() // Don't forget to flush!
    if err != nil {
        log.Fatal(err)
    }
    

    【讨论】:

    • 第二种方法如何需要固定数量的内存。你能解释一下吗?因为缓冲区会根据要求不断增长。
    • 是的,感谢您的编辑,但仍然一次写入所有字节会使其更快吗?假设有 20,000 个字符串,每个字符串有 1000 个字符。它几乎没有 20 MB。
    • 你可以测量它。但是使用 4kb 缓冲区写入 20Mb 所需的 5000 次系统调用的开销不到一毫秒。此外,除非您知道在创建字节缓冲区之前需要写入的数据的最终大小,否则它 (bytes.Buffer) 可能必须重新调整大小,这肯定会减慢您的应用程序的速度。
    • 抱歉,我不明白为什么它必须调整大小以及它如何减慢应用程序的速度?如果用 4kb 缓冲区写入 20Mb 所需的 5000 次系统调用的开销不到一毫秒,那么没有必要深入研究这个主题,只是想知道。
    • 想象一下你有 byte.Buffer,它由 10kb 数组备份。当您将 20kb 的数据写入此 byte.Buffer 时,它将自动分配 20kb,将现有数组复制到新数组的开头(稍后将不得不对旧数组进行垃圾收集)。可以想象,这些操作都不是免费的。
    【解决方案2】:

    写入文件需要时间的操作是系统调用和磁盘 I/O。文件指针打开的事实不会花费您任何费用。天真地,我们可以说第二种方法是最好的。

    现在,您可能知道,您的操作系统不会直接写入文件,它使用内部内存缓存来存储已写入的文件并稍后执行真正的 I/O。我不知道具体的细节,一般来说我不需要。

    我建议的是一个中间解决方案:为每个循环迭代做一个缓冲区,并将这个写 N 次。这样可以减少系统调用和(可能)磁盘写入数量的很大一部分,但不会在缓冲区中消耗太多内存(取决于字符串的大小,这是需要考虑的一点)。

    我建议对最佳解决方案进行基准测试,但由于系统完成缓存,对磁盘 I/O 进行基准测试是一场真正的噩梦。

    【讨论】:

      【解决方案3】:

      系统调用并不便宜,所以第二种方法更好。

      您可以使用 lmbench 中的 lat_syscall 工具来测量调用单个 write 所需的时间:

      $ ./lat_syscall write
      Simple write: 0.1522 microseconds
      

      因此,在我的系统上,为每个字符串调用 write 大约需要 20000 * 0.15μs = 3ms 额外时间。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-02-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-26
        • 1970-01-01
        • 2011-04-29
        相关资源
        最近更新 更多