2014-06-09 20 views
3

我正在將大文件複製操作從NSStream轉換爲使用GCD的調度IO實現。使用GCD的大文件複製 - 調度IO消耗大量內存

將兩個1GB文件一起復制到一個2GB文件中時,該應用程序在使用GCD時消耗2GB內存。 NSStream實現只消耗50MB。

在儀器中,我可以看到start_wqthread調用分配1MB塊,正如我請求的塊大小爲調度IO高位標記,但不是在寫入輸出流後被釋放,而是掛起。

如何在緩衝區寫入輸出流之後釋放緩衝區?

如果我在Xcode中創建一個全新的OS X Cocoa應用程序,並將以下代碼粘貼到applicationDidFinishLaunching:方法中,它將消耗500-2000MB的內存。 (要進行測試,請將臨時文件引用替換爲本地文件引用。)

使用OS 10.9 SDK以OS 10.9爲目標創建新項目時,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); 
      } 
     } 
    }); 
}]; 

回答

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); 
     } 
    }); 
}]; 
+0

我也在考慮順序文件操作可能會更好。我沒有測試過最新的代碼,雖然時間大致相同?我很高興你找到了解決辦法;因爲這是一個很好的例子,我已經提出了你的問題和答案。 - 最好的祝福 :) –

1

我不知道什麼[self cleanUpAndComplete];然而,它不會出現你曾經打電話dispatch_close爲您創建的其他渠道( IO-READ)。

- 從dispatch_create

返回前它返回的對象被保留;這是您的 責任關閉通道,然後釋放這個對象,當你使用它完成 。

+0

你說得對 - 我刪除了'[自cleanUpAndComplete]',因爲它是不相關的,並增加了一個調用'dispatch_io_close(io_write,0)',使之更加明確,雖然它沒有什麼區別了問題在眼前。在該函數結束時,IO通道超出範圍,寫入完成時應釋放對該通道的引用,並且通道自動關閉並清理 - 「通道關閉文件描述符並在調用清理處理程序時調用其清理處理程序...所有對該頻道的引用都已發佈。「 – nekno

+0

@nekno,您需要在dispatch_io_close(它關閉文件描述符)之後使用'dispatch_io_release()',但不會釋放dispatch_io_create已分配的內容。在最後,你可能還需要一個'dispatch_io_release(queue);' –

+0

謝謝 - 正如你所看到的,我已經在每個通道的清除處理程序中調用了'dispatch_release()'調用。清理處理程序在通道錯誤或顯式調用dispatch_io_close()後執行。 – nekno