【问题标题】:Buffer write array of characters to a file in python缓冲区将字符数组写入python中的文件
【发布时间】:2017-01-27 21:04:11
【问题描述】:

如果我有以下代码:

f = open("test.txt","w"):
x = []
for z in range(0,400)
    x.append(z)

如何在使用缓冲的同时将整个阵列写入磁盘?

这是否内置了自动缓冲功能?

for z in range(0,len(x)):
    f.write(str(z))
f.close()

它看起来好像会进行一次写入调用,我担心这会对磁盘进行一次写入操作,一次一个字符,而不是缓冲它以获得更快的写入速度。

注意:我研究了这个类似的帖子,但没有找到我的问题的答案。 Writing a list to a file with Python

【问题讨论】:

  • 写命令填充内部io写缓冲区。在操作系统说应该写之前什么都不会写。但是,您可以使用 f.flush() 操作强制它。

标签: python arrays output buffering


【解决方案1】:

确实,正如您的直觉,如果您可以将 N 减少到 1,则永远不要对磁盘进行 N 次写入操作。对于您的特定情况,让我们测量通过使用 json.dumps 将 N 减少到 1 会增加多少序列化你的测试数组:

import timeit
import json

x = []
for z in range(0, 800000):
    x.append(z)


def f1():
    with open("f1.txt", "w") as f:
        for z in range(0, len(x)):
            f.write(str(z))


def f2():
    with open("f1.txt", "w") as f:
        f.write(json.dumps(x))

N = 10

print(timeit.timeit('f1()', setup='from __main__ import f1', number=N))
print(timeit.timeit('f2()', setup='from __main__ import f2', number=N))

输出速度会快约 5.5 倍:

8.25567383571545
1.508784956563721

如果我将数组大小增加到 8000000,输出将快 3.6 倍:

82.87025446748072
22.56552114259503

如果我设置 N=1 并将数组大小设置为 8000000,输出将快 3.8 倍:

8.355991964475688
2.227114091734297

并不是说使用 json.dumps 是转储自定义数据的最佳方式,而是一种合理的好方式。我的观点是,如果可能,尽量减少磁盘操作的数量。磁盘写入操作通常非常昂贵。

附加信息:如果你想对更快的序列化方法进行一个很好的比较,我建议你看看这个article

编辑:添加了@ShadowRanger 的建议,即在比较 write&writelines 的实验中添加更多测试,开始吧:

import timeit
import json

K = 8000000
x = []
for z in range(0, K):
    x.append(z)


def f1():
    with open("f1.txt", "w") as f:
        for z in range(0, len(x)):
            f.write(str(z))


def f2():
    with open("f1.txt", "w") as f:
        f.write(json.dumps(x))


def f3():
    with open("f1.txt", "w") as f:
        f.write(''.join(map(str, x)))


def f4():
    with open("f1.txt", "w") as f:
        f.writelines(map(str, x))

N = 1

print(timeit.timeit('f1()', setup='from __main__ import f1', number=N))
print(timeit.timeit('f2()', setup='from __main__ import f2', number=N))
print(timeit.timeit('f3()', setup='from __main__ import f3', number=N))
print(timeit.timeit('f4()', setup='from __main__ import f4', number=N))

输出将是:

8.369193972488317
2.154246855128056
2.667741175272406
8.156553772208122

您仍然可以看到 write 比 writelines 快,并且像 json.dumps 这样的简单序列化方法比使用纯 python 代码的手动方法略快ie: ''.join(map(str, x))

【讨论】:

  • 你并没有真正衡量你认为你正在衡量的东西。 Python 会在内部进行缓冲,因此系统调用的实际数量是write 调用数量的一小部分。问题是 Python 在执行 Python 级代码时具有极高的开销,特别是在调用 C 级函数的开销时,这些函数从 Python 层做的工作很少,所以函数调用的开销超过了工作,这是一个病理案例。 json.dumps 在内部通过单个 C 函数调用完成大部分工作,从而节省了大量资金。
  • @ShadowRanger 我明白了,你知道有什么方法可以测量上述代码中系统调用的真实数量吗?进行比较而不是进行真正的比较会很有趣
  • 为了更公平的比较,请使用类似的测试方法:比较 f.write(''.join(map(str, range(somenum))))f.writelines(map(str, range(somenum)))。他们做同样的工作来从int转换为str,前者只是将所有str组合成一个str来做一个write调用,后者使用writelines(即@987654322 @,但在 C 中,避免解释器开销)。两者之间的差异很明显,但writelines 的差异仅约 50%。
  • 在 Linux 上,strace -c 有效。我试过strace -c python3 -c 'with open("/path/to/file", "w") as f: f.writelines(map(str, range(400000)))'strace -c python3 -c 'with open("/path/to/file", "w") as f: f.write("".join(map(str, range(400000))))'。后者有一个系统调用 write 占用 769 us,前者有 40 个系统调用 write 每个占用 0 us(它是四舍五入的,可能被截断,所以这个数字是一种谎言)。即便如此,较小的写入具有较低的调用开销(可能是因为操作系统在 Python 正在执行设置下一个 write 的工作时懒惰地写入它们)。
  • 顺便说一句,有趣的数字。我用ipython%timeit魔法在我的Linux机器上运行了类似的测试,与writelines(...)相比,它比write(''.join(...))长了50%多一点,而不是4倍长。我猜操作系统或文件系统有一些不同。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-09-24
  • 1970-01-01
  • 2020-08-16
  • 2021-12-11
  • 2019-08-13
  • 2014-01-06
  • 2015-08-20
相关资源
最近更新 更多