如何使用GCD有效地读取数千个小文件

ken*_*nyc 10 cocoa objective-c grand-central-dispatch

我想尽可能高效地从潜在的数千个文件中读取一些元数据数据(例如:EXIF数据),而不会影响用户体验.我很感兴趣,如果有人对如何使用常规GCD队列,dispatch_io频道甚至其他实现方式进行最佳解决方案有任何想法.

选项#1:使用常规GCD队列.

这个非常简单我只能使用如下内容:

for (NSURL *URL in URLS) {
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
    // Read metadata information from file.
    CGImageSourceCopyProperties(...);
  });
}
Run Code Online (Sandbox Code Playgroud)

我认为(并且经历过)这个实现的问题是,GCD不知道块中的操作是否与I/O相关,因此它将数十个这些块提交给全局队列进行处理,而后者又进行了饱和处理. I/O. 系统最终会恢复,但如果我正在阅读数千或数万个文件,那么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);
}
Run Code Online (Sandbox Code Playgroud)

在第二个选项中,我觉得我有点滥用dispatch_read.我对它读取的数据不感兴趣,我只是希望dispatch_io能够为我节省I/O. 256大小只是一个随机数,因此即使我从不使用它,也会读取一定数量的数据.

在第二个选项中,我有几次运行,系统工作"相当不错",但我也有一个实例,我的整个机器锁定(甚至光标),我不得不硬复位.在其他情况下(同样随机),应用程序只是退出堆栈跟踪,看起来像是几十个试图清理的dispatch_io调用.(在所有这些情况下,我试图读取超过10,000张图像.)

(由于我自己没有打开任何文件描述符,而且GCD块现在对ARC友好,我认为在dispatch_io_read完成之后我不需要做任何明确的清理工作,但也许我错了?)

解决方案?

我可以使用另一种选择吗?我考虑手动限制请求的NSOperationQueue值和值都很低,maxConcurrentOperationCount但这似乎是错误的,因为与较旧的非SSD MacBook相比,较新的MacPros可以清楚地处理更多的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);
}
Run Code Online (Sandbox Code Playgroud)

这似乎会减慢dispatch_io调用速度,但它现在导致调用CGImageSourceCreateWithURL在应用程序的不同部分失败的情况.(现在CGImageSourceCreateWithURL随机返回NULL,如果我不得不猜测,它表示它无法打开文件描述符,因为该文件肯定存在于给定路径中.)

更新2

在尝试了其他六个想法之后,像使用NSOperationQueue和调用一样简单的实现结果与addOperationWithBlock我能提出的其他任何内容一样有效.手动调整maxConcurrentOperationCount有一些效果,但没有我想象的那么多.

显然,SSD和外部USB 3.0驱动器之间的性能差异是巨大的.虽然我可以在合理的时间内在SSD上迭代超过100,000张图像(甚至可以逃脱大约200,000张图像),但USB驱动器上的许多图像都无望.简单的数学:(读取*文件计数/驱动器速度所需的字节数)表明我无法真正获得我希望的用户体验.(仪器似乎显示_CGImageSourceBindToPlugin每个文件读取大约40KB到1MB.)

bbu*_*bum 4

现实情况是,在多种硬件配置上运行的现代多任务、多用户系统,自动限制 I/O 密集型任务对于系统来说几乎是不可能的。

你必须自己进行节流。这可以通过 NSOperationQueue、信号量或任何其他机制来完成。

通常,我建议您尝试将 I/O 与任何计算分开,以便可以序列化 I/O(这将是所有系统中最普遍合理的性能),但在使用高级时这几乎是不可能的蜜蜂。事实上,尚不清楚 CG* I/O API 如何与dispatch_io_* 咨询 API 进行交互。

这不是一个非常有帮助的答案。如果不了解更多具体情况,就很难说得更具体。我建议缓存可能是这里的关键;建立所有各种图像的元数据数据库。当然,这样你就会遇到同步和验证问题。