为什么当有其他任务运行时,dotnet 中的 IO 线程会急剧变慢?

Why does an IO Thread in dotnet dramatically slow down when there are other Tasks running?

提问人:mjames 提问时间:5/14/2021 最后编辑:mjames 更新时间:5/14/2021 访问量:367

问:

在我的机器上,使用以下代码读取 ~1.3GB 文件大约需要 ~13 秒:

 static void ReadFileToBuffer()
        {
            var inputBuffer = new byte[1_300_000_000];
            var ioStopwatch = new System.Diagnostics.Stopwatch();
            ioStopwatch.Start();

            int idx = 0;
            using (var sr = new StreamReader(path))
            {
                while (sr.Peek() > -1)
                {
                    //Could make this faster using ReadBlock
                    var data = sr.Read();
                    inputBuffer[idx] = (byte)data;
                    idx++;
                }
            }
            ioStopwatch.Stop();
            Console.WriteLine($"Write bytes To Buffer Read finished: {ioStopwatch.ElapsedMilliseconds}");

        }

在单独的线程中运行此代码,然后等待它加入也需要 ~13 秒

public static void Main()
{
    Thread t = new Thread(() =>
    {
        ReadFileToBuffer();
    });
    t.Priority = ThreadPriority.Highest;
    t.Start();
    t.Join();
}

但是,一旦我开始使用其他线程执行一堆非分配工作,IO 线程就会急剧减慢速度。此程序需要 30 秒才能在相同的输入文件上运行:

 public static void Main()
        {
            var mainStopwatch = new System.Diagnostics.Stopwatch();
            mainStopwatch.Start();

            Thread t = new Thread(() =>
            {
                ReadFileToBuffer();
            });
            t.Priority = ThreadPriority.Highest;
            t.Start();


            Task[] taskArray = new Task[System.Environment.ProcessorCount-4];

            var dummy_arr = new string[10000].Select((e, i) => i).ToArray();
            long idx = 0;
            bool run = true;

            for (int i = 0; i < taskArray.Length; i++)
            {
                taskArray[i] = Task.Factory.StartNew(() =>
                {

                    while (run)
                    {
                        //Do some dummy CPU bound work that doesn't allocate
                        var myIdx= System.Threading.Interlocked.Increment(ref idx);
                        dummy_arr[myIdx % 10000] = dummy_arr[myIdx % 10000].GetHashCode();
                    }
                });
            }
            t.Join();
            run = false;
            mainStopwatch.Stop();
            Console.WriteLine($"Full Program {mainStopwatch.ElapsedMilliseconds} - dummy work ran {idx} times");
        }

有谁知道为什么会这样,可以做些什么?

我对“为什么”特别感兴趣。线程调度器是否不擅长不理会 IO 线程?

  • DotNet Core 3.1
  • Windows 10 版本 10.0.19042 内部版本 19042
  • 已安装的物理内存 (RAM) 16.0 GB
  • 处理器 AMD Ryzen 5 1600X 六核处理器, 3600 Mhz, 6 核, 12 逻辑处理器
  • NVMe 三星 SSD 960 SCSI
C# .NET 多线程 .net-core io

评论

0赞 Peter Duniho 5/14/2021
没有更多的信息,就不可能说。也就是说,为什么当你把大量线程放入繁忙的循环中时,其他线程会受到影响,你会感到惊讶?在上面的代码中,唯一稍微站得住脚的是,您将繁忙线程数设置为处理器计数减去 4。...
0赞 Peter Duniho 5/14/2021
...但是没有证据表明你正在考虑超线程的问题,这使得 CPU 看起来它的有用内核数量是实际数量的两倍。您必须将活动线程计数减少到不超过 5 个,甚至可能更少,以便剩下一个 CPU 内核来处理您的 I/O。
0赞 mjames 5/14/2021
有 5 个繁忙线程时,IO 线程比没有繁忙线程(~13 秒)慢 50%(~18 秒)。你的看法是,这是预期的行为吗?这意味着在 IO 发生时没有有效的方法来使用其他线程?作为上下文点 - 当您启动其他不相关的线程时,非 IO 线程不会显着减慢速度。
0赞 Peter Duniho 5/14/2021
“non IO threads not slowed” -- 非 I/O 线程不会屈服。您的 I/O 线程是最坏的情况。它一次读取一个字节,这是清空底层缓冲区的最慢方法,然后它产生。受 CPU 限制的线程在必须屈服之前不会屈服(IIRC 线程量程为 50 毫秒,远远超过 I/O 线程一次可能使用的长度)。所以,是的......我看不出你的例子有什么不妥之处。你已经蹒跚地运行了 I/O 线程,然后发送了一堆 CPU 绑定的线程来将其带到地面上。它输了也就不足为奇了。
0赞 Peter Duniho 5/14/2021
Nit:您的 5 线程测试不会产生“慢 50%”的结果。它产生的结果“50%”更长(实际上只有 40%)。实际 I/O 速率约为 70%,仅 30%。

答: 暂无答案