2

我正在尝试在具有 RAID-5 中的八个 SSD 的数据流应用程序中获得顶级 I/O 性能(每个 SSD 宣传并提供 500 MB/秒的读取)。

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

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

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

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

如果有的话,C# 中的新 Async 特性将如何提供帮助?

作为比较,ATTO 磁盘基准测试显示在此硬件上的这些块大小和低 CPU 利用率下为 2500 MB/秒。但是,ATTO 数据集大小仅为 2GB。

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

图像1

摘要图像

i/o 计数

QD10

ATTO 单一请求

这是正在使用的代码的草图。我不会以这种方式编写生产代码,它只是一个概念证明。

   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;
      }
   }
4

1 回答 1

1

这里有很多问题。

首先,我看到您没有尝试使用非缓存 I/O。这意味着系统将尝试将您的数据缓存在 RAM 中并从中读取服务。因此,您可以从事物中获得额外的数据传输。执行非缓存 I/O。

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

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

我会提倡使用非缓存的异步 I/O。我不确定如何在 C# 中完成此操作(但应该很容易)。

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

于 2012-11-28T17:42:13.797 回答