我正在将大型文件复制操作从NSStream
转换为使用GCD的调度IO实现。
将两个1GB文件一起复制到一个2GB文件时,该应用程序使用GCD消耗2GB内存。 NSStream
实现只消耗50MB。
在Instruments中,我可以看到start_wqthread
调用分配1MB块,正如我所请求的块大小用于调度IO高水位标记,但是在写入输出流后没有被释放,它们会挂起
如何在将缓冲区写入输出流后释放缓冲区?
如果我在Xcode中创建一个全新的OS X Cocoa应用程序并在applicationDidFinishLaunching:
方法中粘贴以下代码,它将消耗500-2000MB的内存。 (要测试,请使用本地文件引用替换临时文件引用。)
使用面向OS 10.9的OS 10.9 SDK创建新项目时,ARC禁止调用dispatch_release()
。在旧项目中定位OS 10.6时,即使启用了ARC,也允许调用dispatch_release()
,但不会影响内存占用。
NSArray* files = @[@"/1GBFile.tmp", @"/1GBFile2.tmp"];
NSString* outFile = @"/outFile.tmp";
NSString* queueName = [NSString stringWithFormat:@"%@.IO", [[NSBundle mainBundle].infoDictionary objectForKey:(id)kCFBundleIdentifierKey]];
dispatch_queue_t queue = dispatch_queue_create(queueName.UTF8String, DISPATCH_QUEUE_SERIAL);
dispatch_io_t io_write = dispatch_io_create_with_path(DISPATCH_IO_STREAM, outFile.UTF8String, (O_RDWR | O_CREAT | O_APPEND), (S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH), queue, NULL);
dispatch_io_set_high_water(io_write, 1024*1024);
[files enumerateObjectsUsingBlock:^(NSString* file, NSUInteger idx, BOOL *stop) {
dispatch_io_t io_read = dispatch_io_create_with_path(DISPATCH_IO_STREAM, file.UTF8String, O_RDONLY, 0, queue, NULL);
dispatch_io_set_high_water(io_read, 1024*1024);
dispatch_io_read(io_read, 0, SIZE_MAX, queue, ^(bool done, dispatch_data_t data, int error) {
if (error) {
dispatch_io_close(io_write, 0);
return;
}
if (data) {
size_t bytesRead = dispatch_data_get_size(data);
if (bytesRead > 0) {
dispatch_io_write(io_write, 0, data, queue, ^(bool doneWriting, dispatch_data_t dataToBeWritten, int errorWriting) {
if (errorWriting) {
dispatch_io_close(io_read, DISPATCH_IO_STOP);
}
});
}
}
if (done) {
dispatch_io_close(io_read, 0);
if (files.count == (idx+1)) {
dispatch_io_close(io_write, 0);
}
}
});
}];
答案 0 :(得分:2)
我相信我已经使用调度组制定了解决方案。
代码实质上是按顺序复制每个文件(阻止循环处理下一个文件,直到完全读取和写入前一个文件),但允许文件读写操作异步排队。
我认为内存过度消耗是因为多个文件的读取同时排队。我原本以为串口队列就好了,但它似乎阻止了调度组的进度,所以只有读取和写入单个文件的工作才会排队等待。使用以下代码,峰值内存使用量约为7MB。
现在,单个输入文件排队等待读取,每个读取操作将其相应的写入操作排队,并且阻止输入文件上的循环,直到所有读取和写入操作都完成。
NSArray* files = @[@"/1GBFile.tmp", @"/1GBFile2.tmp"];
NSString* outFile = @"/outFile.tmp";
NSString* queueName = [NSString stringWithFormat:@"%@.IO", [[NSBundle mainBundle].infoDictionary objectForKey:(id)kCFBundleIdentifierKey]];
dispatch_queue_t queue = dispatch_queue_create(queueName.UTF8String, DISPATCH_QUEUE_SERIAL);
dispatch_group_t group = dispatch_group_create();
dispatch_io_t io_write = dispatch_io_create_with_path(DISPATCH_IO_STREAM, outFile.UTF8String, (O_RDWR | O_CREAT | O_APPEND), (S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH), queue, NULL);
dispatch_io_set_high_water(io_write, 1024*1024);
[files enumerateObjectsUsingBlock:^(NSString* file, NSUInteger idx, BOOL *stop) {
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
if (*stop) {
return;
}
dispatch_group_enter(group);
dispatch_io_t io_read = dispatch_io_create_with_path(DISPATCH_IO_STREAM, file.UTF8String, O_RDONLY, 0, queue, NULL);
dispatch_io_set_high_water(io_read, 1024*1024);
dispatch_io_read(io_read, 0, SIZE_MAX, queue, ^(bool done, dispatch_data_t data, int error) {
if (error || *stop) {
dispatch_io_close(io_write, 0);
*stop = YES;
return;
}
if (data) {
size_t bytesRead = dispatch_data_get_size(data);
if (bytesRead > 0) {
dispatch_group_enter(group);
dispatch_io_write(io_write, 0, data, queue, ^(bool doneWriting, dispatch_data_t dataToBeWritten, int errorWriting) {
if (errorWriting || *stop) {
dispatch_io_close(io_read, DISPATCH_IO_STOP);
*stop = YES;
dispatch_group_leave(group);
return;
}
if (doneWriting) {
dispatch_group_leave(group);
}
});
}
}
if (done) {
dispatch_io_close(io_read, 0);
if (files.count == (idx+1)) {
dispatch_io_close(io_write, 0);
}
dispatch_group_leave(group);
}
});
}];
答案 1 :(得分:1)
我不确定[self cleanUpAndComplete];
是什么,但是,您似乎没有为您创建的其他频道拨打dispatch_close(仅限 io_read)。
- 来自dispatch_create:
返回的对象在返回之前保留;这是你的 有责任关闭频道,然后释放此对象 你已经完成了它的使用。