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

下面是我用来测试如何更快地编写文件的简约版本的代码.writeFileIncrementally在for循环中一次写入一行,而writeFileFromBigData创建一个大字符串,然后将其转储到磁盘.我完全预计writeFileFromBigData会更快,但20 times faster!比我预期的要快一点.对于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语言中,创建-字符串-然后-写-它也更快.这是合理的,但在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中,有没有更好的增量编写大型文本文件的方法?

推荐答案

关于记忆,请看邓肯·C的答案.您需要一个自动释放池.但在速度方面,你有一个小问题和一个大问题.

小问题是这一行:

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

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

    handle.write(Data(s.utf8)) 

字符串通常存储在UTF8内部.虽然ASCII是其中的一个完美子集,但您的代码需要判断任何不是ASCII的内容.使用.utf8通常只能直接获取内部缓冲区.它还避免了创建和展开可选的.

但17分仍然比1-2分多得多.这要归功于你的大问题.

每次调用write都必须将数据一路送到操作系统的文件缓冲区.虽然不是一直到磁盘,但这仍然是一个昂贵的操作.除非数据非常宝贵,否则您通常希望将其分成更大的块(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)

这与我系统上" Big Data "功能的1.1版本"非常接近".仍然有大量的内存分配和清理工作在进行.根据我的经验,至少在最近,[UInt8]比数据快得多.我不确定这是否总是正确的,但我最近在Mac上的所有测试都是这样的.因此,使用较新的write(contentsOf:)界面编写代码如下:

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)

这比 Big Data 功能要高出faster%,因为它不需要生成数据.(我的机器上有830毫秒)

但等等,事情会变得更好.这段代码不需要自动释放池,如果删除它,我可以在730ms内编写此文件.

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使用8k缓冲区调用returns a BufferedWriter,其工作原理与上面的代码大致相似.您需要在二进制模式下写入代码,并通过buffering=0将其关闭.详细信息请参见the docs on open.

Swift相关问答推荐

OBJC代码中Swift 演员的伊瓦尔:原子还是非原子?

解码器是否需要具有密钥的外部数据表示?

如何在 Vapor 中制作可选的查询过滤器

为什么 UITapGestureRecognizer 对于 Swift 集合视图中的单元格图像无法正常工作?

如何制作进度加载动画?

如何让飞机与 RealityKit 中的物体相撞

将每个枚举 case 与 Swift 中的一个类型相关联

MacOS KIND 是如何实现的

自定义 DispatchQueue 服务质量

Swift Swiftui - 将 colored颜色 保存到 UserDefaults 并从 @AppStorage 使用它

无法分配给属性:absoluteString是一个只能获取的属性

在 Swift 中实现自定义异步序列

在 SwiftUI 中,如何在 UIView 内或作为 UIView 使用 UIHostingController?

快速的 AES 加密

为 UIImagePicker 设置委托返回错误

如何在 SwiftUI 中创建带有图像的按钮?

自定义 Google 登录按钮 - iOS

在 Swift 中子类化 NSObject - 初始化器的最佳实践

如何将 Int 拆分为其各个数字?

如何快速格式化用户显示(社交网络等)的时间间隔?