gpt4 book ai didi

swift - 在 Swift 中增量写入大型文本文件的最佳方法

转载 作者:行者123 更新时间:2023-12-05 01:03:23 25 4
gpt4 key购买 nike

我正在编写一个相当大的文本文件(它实际上更像是 ascii 编码的数据),而且它......非常慢。并且占用大量内存。

这是我用来测试如何更快地编写文件的代码的简约版本。 writeFileIncrementally 在 for 循环中一次写入一行,而 writeFileFromBigData 创建一个大字符串,然后将其转储到磁盘。我完全期望 writeFileFromBigData 更快,但它的速度快了 20 倍! 这比我预期的要多一点。对于size=10_000_000,增量写入需要20-25秒,一次性写入需要1-1.5秒。另外,增量版本实际上分配了越来越多的内存。到最后,它已进入 GiB 范围。我不明白这里发生了什么。

func writeFileIncrementally(toUrl url: URL, size: Int) {
// ensure file exists and is empty
try? "".write(to: url, atomically: true, encoding: .ascii)

guard let handle = try? FileHandle(forWritingTo: url) else {return}

defer {
handle.closeFile()
}

for i in 0..<size {
let s = "\(i)\n"
handle.write(s.data(using: .ascii)!)
}
}

func writeFileFromBigData(toUrl url: URL, size: Int) {
let s = (0..<size).map{String($0)}.joined(separator: "\n")

try? s.write(to: url, atomically: true, encoding: .ascii)
}

将其与 Python 中的相同内容进行比较。在 Python 中,create-string-then-write-it 也更快。这是合理的,但 Python 的不同之处在于增量编写它需要大约 2.7 秒(大约 98% 的用户时间),而一次性编写它大约需要 1 秒(包括创建字符串)。此外,增量版本具有恒定的内存使用量。在写入文件时它不会上升。

def writeFileIncrementally(path, size):
with open(path, "w+") as f:
for i in range(size):
f.write(f"{i}\n")

def writeFileFromBigData(path, size):
with open(path, "w+") as f:
f.write("\n".join(str(i) for i in range(size)))

所以我的问题是双重的:

  1. 为什么我的 writeFileIncrementally 函数这么慢,为什么它使用这么多内存?我希望能够逐步写入以减少内存使用量。
  2. 有没有更好的方法可以在 Swift 中以增量方式编写大型文本文件?

最佳答案

有关内存,请参阅 Duncan C 的回答。你需要一个自动释放池。但是为了速度,你有一个小问题和一个大问题。

小问题是这一行:

    handle.write(s.data(using: .ascii)!)

重写将节省大约 40% 的时间(在我的测试中从 27 秒到 17 秒):

    handle.write(Data(s.utf8)) 

字符串通常在内部以 UTF8 存储。虽然 ASCII 是其中的一个完美子集,但您的代码需要检查任何不是 ASCII 的内容。使用 .utf8 通常可以直接抓取内部缓冲区。它还避免了创建和解包 Optional。

但是 17s 仍然比 1-2s 多很多。那是因为你的大问题。

write 的每次调用都必须将数据一直获取到操作系统的文件缓冲区。不是一直到磁盘,但它仍然是一项昂贵的操作。除非数据很珍贵,否则您通常希望将其分 block 成更大的 block (4k 非常常见)。如果你这样做,写入时间会减少到 1.5 秒:

let bufferSize = 4*1024
var buffer = Data(capacity: bufferSize)
for i in 0..<size {
autoreleasepool {
let s = "\(i)\n"
buffer.append(contentsOf: s.utf8)
if buffer.count >= bufferSize {
handle.write(buffer)
buffer.removeAll(keepingCapacity: true)
}
}
}
// Write the final buffer
handle.write(buffer)

这与我系统上的“大数据”功能的 1.1 秒“非常接近”。仍然有很多内存分配和清理正在进行。根据我的经验,至少在最近,[UInt8] 比 Data 快得多。我不确定这是否总是正确的,但我最近在 Mac 上的所有测试都是这样。因此,使用较新的 write(contentsOf:) 接口(interface)编写是:

let bufferSize = 4*1024
var buffer: [UInt8] = []
buffer.reserveCapacity(bufferSize)
for i in 0..<size {
autoreleasepool {
let s = "\(i)\n"
buffer.append(contentsOf: s.utf8)
if buffer.count >= bufferSize {
try? handle.write(contentsOf: buffer)
buffer.removeAll(keepingCapacity: true)
}
}
}
// Write the final buffer
try? handle.write(contentsOf: buffer)

这比大数据功能更快,因为它不必制作数据。 (在我的机器上是 830 毫秒)

但是等等,它会变得更好。这段代码不需要自动释放池,如果你去掉它,我可以在 730 毫秒内写这个文件。

let bufferSize = 4*1024
var buffer: [UInt8] = []
buffer.reserveCapacity(bufferSize)
for i in 0..<size {
let s = "\(i)\n"
buffer.append(contentsOf: s.utf8)
if buffer.count >= bufferSize {
try? handle.write(contentsOf: buffer)
buffer.removeAll(keepingCapacity: true)
}
}
// Write the final buffer
try? handle.write(contentsOf: buffer)

但是 Python 呢?为什么它不需要缓冲区来快速?因为它默认为您提供缓冲区。您的 open 调用 returns a BufferedWriter使用 8k 缓冲区,或多或少类似于上面的代码。您需要以二进制模式写入并通过 buffering=0 将其关闭。见 the docs on open了解详情。

关于swift - 在 Swift 中增量写入大型文本文件的最佳方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74347486/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com