首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在单线程应用程序中使用FileStream提高吞吐量

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

Stack Overflow用户
提问于 2012-11-22 04:34:05
回答 1查看 392关注 0票数 2

我正在尝试在一个数据流应用程序中获得最佳I/O性能,该应用程序具有8个RAID-5固态硬盘(每个固态硬盘通告并提供500 MB/秒的读取)。

我用64KB的缓冲区创建了FileStream,并以阻塞的方式读取了许多块(不是双关语)。这是我现在拥有的20K文件中的80 MB,没有碎片:对于单线程,传统的阻塞读取速度是1270MB/秒,对于6个线程,是1556MB/秒。

我在单线程中注意到的是,单核的CPU时间花费在内核中(在具有12个内核的Process Explorer中,有8.3%是red )。在使用6个线程的情况下,内核占用的CPU时间约为5倍(在具有12个内核的Process Explorer中,CPU占用的时间为41%)。

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

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

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

作为比较,在此硬件上,ATTO disk benchmark在这些块大小下显示为2500 MB/秒,并且CPU利用率较低。然而,ATTO数据集大小仅为2 2GB。

使用LSI 9265-8i RAID控制器,条带大小为64k,集群大小为64k。

下面是正在使用的代码的草图。我不会以这种方式编写产品代码,这只是一种概念验证。

代码语言:javascript
复制
   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;
      }
   }
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2012-11-29 01:42:14

这里有很多问题。

首先,我发现您并没有尝试使用非缓存I/O,这意味着系统将尝试将您的数据缓存到RAM中,并从RAM中读取数据。所以你得到了额外的数据传输。执行非缓存I/O。

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

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

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

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

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/13501664

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档