如何在单线程应用程序中使用FileStream提高吞吐量

时间:2012-11-21 20:34:05

标签: windows-server-2008-r2 .net-4.5 ntfs c#-5.0

我正在尝试在RAID-5中使用8个SSD的数据流应用程序中获得最高I / O性能(每个SSD通告并提供500 MB /秒读取)。

我使用64KB缓冲区创建FileStream,并以阻塞方式读取多个块(不打算使用)。这就是我现在拥有的80K 20K文件,没有片段: 传统阻塞读取的单线程为1270 MB /秒,6线程为1556 MB /秒。

我发现单线程的注意事项是内核花费了单个核心的CPU时间(在12个核心的Process Explorer中为8.3%红色)。使用6个线程,在内核中花费大约5倍的CPU时间(在Process Explorer中有12个内核,占用41%的红色)。

我真的希望避免I / O绑定场景中多线程应用程序的复杂性。

是否可以在单线程应用程序中实现这些传输速率?那么,什么是减少内核模式中的时间量的好方法?

如果有的话,C#中的新异步功能会如何帮助?

为了进行比较,ATTO disk benchmark在这些硬件上以这些块大小显示2500 MB /秒且CPU利用率低。但是,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;
      }
   }

1 个答案:

答案 0 :(得分:1)

这里有很多问题。

首先,我发现您并未尝试使用非缓存I / O.这意味着系统将尝试将数据缓存在RAM中,并将服务读取出来。所以你可以从中获得额外的数据传输。做非缓存的I / O.

接下来,您似乎正在创建/销毁循环内的线程。这是低效的。

最后,您需要调查数据的对齐方式。跨越读取块边界可能会增加成本。

我主张使用非缓存的异步I / O.我不确定如何在C#中实现这一点(但它应该很容易)。

编辑:另外,你为什么使用RAID 5?除非数据是一次写入,否则这可能会在SSD上产生可怕的性能。值得注意的是,擦除块大小通常为512K,这意味着当您编写较小的内容时,SSD将需要读取其固件中的512K,更改数据,然后将其写入其他位置。您可能希望使条带大小=擦除块的大小。此外,您应该检查写入的对齐方式。