RAID-5 で 8 つの SSD を使用してデータ ストリーミング アプリケーションで最高の I/O パフォーマンスを得ようとしています (各 SSD はアドバタイズし、500 MB/秒の読み取りを提供します)。
私は 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 ディスク ベンチマークは、このハードウェアと低い CPU 使用率で、これらのブロック サイズで 2500 MB/秒を示しています。ただし、ATTO データセットのサイズはわずか 2GB です。
LSI 9265-8i RAID コントローラを使用し、64k ストライプ サイズ、64k クラスタ サイズ。
これは、使用中のコードのスケッチです。私はこの方法で製品コードを書きません。これは単なる概念実証です。
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;
}
}