2012-11-21 54 views
2

我試圖在具有RAID-5中8個SSD(每個SSD通告並提供500 MB /秒讀取次數)的數據流應用程序中獲得最佳I/O性能。如何通過單線程應用程序中的FileStream提高吞吐量

我創建了帶有64KB緩衝區的FileStream,並以阻塞的方式讀取了許多塊(雙關語並非意圖)。下面是我現在在20K文件中使用80GB的情況,沒有碎片: 傳統的阻塞讀取在單線程時爲1270 MB /秒,在6線程時爲1556 MB /秒。

我在單線程中注意到的是單個內核的CPU時間花費在內核上(在12個內核的Process Explorer中爲8.3%紅色)。使用6個線程,內核消耗大約5倍的CPU時間(在12核心的Process Explorer中,紅色程度爲41%)。

我真的想避免I/O綁定場景中多線程應用程序的複雜性。

是否有可能在單線程應用程序中實現這些傳輸速率?也就是說,在內核模式下減少時間的好方法是什麼?

如果有的話,C#中的新Async特性如何?

作爲比較,ATTO disk benchmark在此硬件上的這些塊大小和CPU利用率較低時顯示爲2500 MB /秒。但是,ATTO數據集大小僅爲2GB。

使用LSI 9265-8i RAID控制器,64k條帶大小,64k羣集大小。

image1

summary image

i/o counts

QD10

ATTO Single request

下面是使用代碼的草圖。我不用這種方式編寫生產代碼,它只是一個概念證明。

volatile bool _somethingLeftToRead = false; 
    long _totalReadInSize = 0; 
    void ProcessReadThread(object obj) 
    { 
     TestThreadJob job = obj as TestThreadJob; 
     var dirInfo = new DirectoryInfo(job.InFilePath); 
     int chunk = job.DataBatchSize * 1024; 

     //var tile = new List<byte[]>(); 

     var sw = new Stopwatch(); 

     var allFiles = dirInfo.GetFiles(); 

     var fileStreams = new List<FileStream>(); 
     long totalSize = 0; 
     _totalReadInSize = 0; 

     foreach (var fileInfo in allFiles) 
     { 
     totalSize += fileInfo.Length; 
     var fileStream = new FileStream(fileInfo.FullName, 
      FileMode.Open, FileAccess.Read, FileShare.None, job.FileBufferSize * 1024); 

     fileStreams.Add(fileStream); 
     } 

     var partial = new byte[chunk]; 
     var taskParam = new TaskParam(null, partial); 
     var tasks = new List<Task>(); 
     int numTasks = (int)Math.Ceiling(fileStreams.Count * 1.0/job.NumThreads); 
     sw.Start(); 

     do 
     { 
     _somethingLeftToRead = false; 

     for (int taskIndex = 0; taskIndex < numTasks; taskIndex++) 
     { 
      if (_threadCanceled) 
       break; 
      tasks.Clear(); 
      for (int thread = 0; thread < job.NumThreads; thread++) 
      { 
       if (_threadCanceled) 
        break; 
       int fileIndex = taskIndex * job.NumThreads + thread; 
       if (fileIndex >= fileStreams.Count) 
        break; 
       var fileStream = fileStreams[fileIndex]; 

       taskParam.File = fileStream; 
       if (job.NumThreads == 1) 
        ProcessFileRead(taskParam); 
       else 
        tasks.Add(Task.Factory.StartNew(ProcessFileRead, taskParam)); 

       //tile.Add(partial); 
      } 
      if (_threadCanceled) 
       break; 
      if (job.NumThreads > 1) 
       Task.WaitAll(tasks.ToArray()); 
     } 

     //tile = new List<byte[]>(); 
     } 
     while (_somethingLeftToRead); 

     sw.Stop(); 

     foreach (var fileStream in fileStreams) 
     fileStream.Close(); 

     totalSize = (long)Math.Round(totalSize/1024.0/1024.0); 
     UpdateUIRead(false, totalSize, sw.Elapsed.TotalSeconds); 
    } 

    void ProcessFileRead(object taskParam) 
    { 
     TaskParam param = taskParam as TaskParam; 
     int readInSize; 
     if ((readInSize = param.File.Read(param.Bytes, 0, param.Bytes.Length)) != 0) 
     { 
     _somethingLeftToRead = true; 
     _totalReadInSize += readInSize; 
     } 
    } 
+0

固態硬盤尋道時間比硬盤要好得多,但你不能忽視它們。典型值爲0.1到0.3毫秒,在cpu週期中仍然很長。有一堆線程爭奪控制器,等待數據訪問完成並強制它尋找不會得到你ATTO號碼。我懷疑你會用20K的文件,1650聽起來不錯。 –

+0

似乎我在桌子上留下了很多足夠的信息。每個SSD的讀取速度爲500 MB /秒,所以其中7個應該讓我達到3.5 GB /秒,對吧? – GregC

+0

順便說一句,這是超過8通道的SATA 6Gb /秒; RAID卡插入到x8 PCIe 2.0中。 – GregC

回答

1

這裏有很多問題。

首先,我看到您並未試圖使用非緩存I/O。這意味着系統會嘗試將數據緩存在RAM中,並將服務從中讀出。所以你會得到額外的數據傳輸。做非高速緩存的I/O。

接下來,您似乎在創建/銷燬循環內的線程。這是低效的。

最後,您需要調查數據的對齊。越過讀取塊邊界會增加您的成本。

我主張使用非緩存的異步I/O。我不知道如何在C#中完成此操作(但應該很簡單)。

編輯:另外,你爲什麼使用RAID 5?除非數據是一次寫入,否則這可能會在SSD上造成可怕的性能。值得注意的是,擦除塊的大小通常是512K,這意味着當您寫入更小的東西時,SSD需要讀取其固件中的512K,更改數據,然後將其寫入其他地方。您可能希望使條帶大小=擦除塊的大小。此外,您應該檢查以確定寫入的對齊方式。

+0

我在哪裏做循環中的線程? – GregC

+0

您是否還可以詳細說明爲什麼緩存的讀取對於流應用程序的整體性能不利?你可能會想到一些東西,但我不太關注。 – GregC

+0

緩存背後的前提是從內存中讀取數據比從設備讀取數據要快。因此,對於第二次訪問數據,將其保存在內存中通常是一個很好的折衷方案,因爲您的聚合性能大致爲 – MJZ

相關問題