我想在不影响用户体验的情况下尽可能有效地从可能的数千个文件中读取一些元数据数据(例如:EXIF 数据)。如果有人对如何最好地使用常规 GCD 队列之类的方法有任何想法,我很感兴趣,dispatch_io
channel ,甚至是另一种实现方式。
选项 #1:使用常规 GCD 队列。
这个非常简单,我可以使用如下所示的内容:
for (NSURL *URL in URLS) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// Read metadata information from file.
CGImageSourceCopyProperties(...);
});
}
这个实现的问题,我认为(并且经历过),是 GCD 不知道 block 中的操作是 I/O 相关的,所以它提交了几十个这样的 block 到全局队列进行处理,而后者又会饱和输入/输出。系统最终会恢复,但如果我正在读取数千或数万个文件,I/O 就会受到影响。
选项#2:使用 dispatch_io
这似乎是一个很好的竞争者,但实际上我的性能比使用常规 GCD 队列更差。那可能是我的实现。
dispatch_queue_t intakeQueue = dispatch_queue_create("someName"), NULL);
for (NSURL *URL in URLS) {
const char *path = URL.path.UTF8String;
dispatch_io_t intakeChannel = dispatch_io_create_with_path(DISPATCH_IO_RANDOM, path, O_RDONLY, 0, intakeQueue, NULL);
dispatch_io_set_high_water(intakeChannel, 256);
dispatch_io_set_low_water(intakeChannel, 0);
dispatch_io_handler_t readHandler = ^void(bool done, dispatch_data_t data, int error) {
// Read metadata information from file.
CGImageSourceCopyProperties(...);
// Error stuff...
};
dispatch_io_read(intakeChannel, 0, 256, intakeQueue, readHandler);
}
在这第二个选项中,我觉得我有点滥用
dispatch_read
.我对它读取的数据根本不感兴趣,我只是想让 dispatch_io 为我节流 I/O。 256 大小只是一个随机数,因此即使我从未使用过它,也会读取一定数量的数据。在第二个选项中,我已经运行了几次系统“非常好”的运行,但我也有一个实例,我的整个机器都被锁定(甚至是光标),我不得不硬重置。在其他情况下(同样随机),应用程序只是简单地退出堆栈跟踪,看起来像数十个 dispatch_io 调用试图清理。 (在所有这些情况下,我试图读取超过 10,000 张图像。)
(由于我自己没有打开任何文件描述符,而且 GCD block 现在是 ARC 友好的,我认为在
dispatch_io_read
完成后我不必进行任何明确的清理,尽管也许我错了? )解决方案?
我可以使用其他选项吗?我考虑过使用
NSOperationQueue
手动限制请求和 maxConcurrentOperationCount
的低值但这似乎是错误的,因为与较旧的非 SSD MacBook 相比,较新的 MacPro 显然可以处理更多的 I/O。更新 1
我想根据@Ken-Thomases 在下面提到的一些要点对选项 #2 做一点修改。在这次尝试中,我试图阻止
dispatch_io
通过设置 high_water
阻止退出标记低于请求的总字节数。这个想法是读取处理程序将被调用并带有要读取的数据。dispatch_queue_t intakeQueue = dispatch_queue_create("someName"), NULL);
for (NSURL *URL in URLS) {
const char *path = URL.path.UTF8String;
dispatch_io_t intakeChannel = dispatch_io_create_with_path(DISPATCH_IO_RANDOM, path, O_RDONLY, 0, intakeQueue, NULL);
dispatch_io_set_high_water(intakeChannel, 256);
dispatch_io_set_low_water(intakeChannel, 0);
__block BOOL didReadProperties = NO;
dispatch_io_handler_t readHandler = ^void(bool done, dispatch_data_t data, int error) {
// Read metadata information from file.
if (didReadProperties == NO) {
CGImageSourceCopyProperties(...);
didReadProperties = YES;
} else {
// Maybe try and force close the channel here with dispatch_close?
}
};
dispatch_io_read(intakeChannel, 0, 512, intakeQueue, readHandler);
}
这似乎确实减慢了
dispatch_io
调用,但现在导致调用 CGImageSourceCreateWithURL
的情况在应用程序的不同部分失败,而他们从来没有这样做过。 (现在 CGImageSourceCreateWithURL
随机返回 NULL,如果我不得不猜测,这表明它无法打开文件描述符,因为该文件肯定存在于给定的路径中。)更新 2
在尝试了六个其他想法之后,实现就像使用
NSOperationQueue
一样简单。并调用 addOperationWithBlock
结果证明它和我能想到的任何其他东西一样有效。手动调整 maxConcurrentOperationCount
有一些效果,但远没有我想象的那么好。显然,SSD 和外部 USB 3.0 驱动器之间的性能差异是巨大的。虽然我可以在合理的时间内在 SSD 上迭代超过 100,000 个图像(甚至可以避开大约 200,000 个),但 USB 驱动器上的许多图像是没有希望的。简单的数学计算:(读取所需的字节数 * 文件计数/驱动器速度)表明我无法真正获得我所希望的用户体验。 (仪器似乎表明
_CGImageSourceBindToPlugin
正在读取每个文件从大约 40KB 到 1MB 的任何地方。)
最佳答案
现实情况是,现代、多任务、多用户系统运行在多种硬件配置上,自动限制 I/O 绑定(bind)任务对于系统来说几乎是不可能的。
您将不得不自己进行节流。这可以通过 NSOperationQueue、信号量或许多其他机制中的任何一种来完成。
通常,我建议您尝试将 I/O 与任何计算分开,以便您可以序列化 I/O(这将是所有系统中最普遍的合理性能),但是在使用高级别的情况下这几乎是不可能的蜜蜂。事实上,目前尚不清楚 CG* I/O API 如何与 dispatch_io_* 咨询 API 进行交互。
不是一个非常有用的答案。如果不了解您的具体案例,就很难说得更具体。我建议缓存可能是这里的关键;为所有不同的图像建立一个元数据数据库。当然,那么您就会遇到同步和验证问题。
关于objective-c - 如何使用 GCD 高效读取数千个小文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23599251/