C#匿名パイプのバッファサイズ調査

また下らない実験をしてみました。

子プロセスを実行してパイプで繋ぐ場合、普通は実行と同時に別スレッドなどでパイプの読み込みも始めます。そうしないと子プロセスの出力がバッファの上限を超えたところでブロックされてしまい、下手をするとデッドロックするからです。

今回はその辺C#(ちょっと古いですが.NET 5.0)でどうなるのか試してみました。

目的

匿名パイプのバッファサイズを知ること

調査

コード

全てのコードは以下に入れてあります。

https://git.elephantcat.work/first_user/PipeTest

親プロセス

親プロセスは指定したバイト数分出力するよう子プロセスに指定しながら子プロセスを起動し、一切パイプを読み込まずに子プロセスの終了を待ちます。所定時間以内に終了しない場合はバッファの上限であると判断し、子プロセスをkill()します。バッファサイズを見つけるため、子プロセスに指定するバイト数を二分探索で上下しながらこれを繰り返します。

Parent.cpp

using System;
using System.Diagnostics;

namespace PipeTest
{
    class Parent
    {
        private static bool Test(long size)
        {
            Console.WriteLine($"Testing: {size}");
            bool result = true;
            using (var p = new Process())
            {
                p.StartInfo.FileName = @".\Child.exe";
                p.StartInfo.Arguments = size.ToString();
                p.StartInfo.RedirectStandardOutput = true;
                if (! p.Start()) throw new Exception("子プロセス開始失敗");
                if (!p.WaitForExit(2000))
                {
                    result = false;
                    p.Kill();
                }
                //Console.Write(p.StandardOutput.ReadToEnd());
            }
            Console.WriteLine($"Result: {result}");
            return result;
        }
        static void Main(string[] args)
        {
            long size = 1;
            while (Test(size)) size *= 2;
            var max = size;
            var min = size / 2;
            while (min + 1 < max)
            {
                size = (min + max) / 2;
                if (Test(size))
                {
                    min = size;
                } else
                {
                    max = size;
                }
            }
            Console.WriteLine($"size of buffer: {min}");
        }
    }
}

子プロセス

子プロセスは引数で指定されたバイト数分標準出力に文字を書き込むだけです。

Child.cpp

using System;

namespace PipeTest
{
    class Child
    {
        static void Main(string[] args)
        {
            var size = long.Parse(args[0]);
            for (long i = 0; i < size; ++i)
            {
                Console.Write((char)(0x20+(i & 0x3f)));
            }
        }
    }
}

実行

C:\PipeTest\Parent\bin\Debug\net5.0>parent
Testing: 1
Result: True
Testing: 2
Result: True
Testing: 4
Result: True
Testing: 8
Result: True
Testing: 16
Result: True
Testing: 32
Result: True
Testing: 64
Result: True
Testing: 128
Result: True
Testing: 256
Result: True
Testing: 512
Result: True
Testing: 1024
Result: True
Testing: 2048
Result: True
Testing: 4096
Result: True
Testing: 8192
Result: False
Testing: 6144
Result: False
Testing: 5120
Result: False
Testing: 4608
Result: False
Testing: 4352
Result: False
Testing: 4224
Result: False
Testing: 4160
Result: False
Testing: 4128
Result: False
Testing: 4112
Result: False
Testing: 4104
Result: False
Testing: 4100
Result: False
Testing: 4098
Result: False
Testing: 4097
Result: False
size of buffer: 4096

C:\PipeTest\Parent\bin\Debug\net5.0>

結果

子プロセスの出力がバッファの上限を超えたところで、想定通りブロックされ、タイムアウトしていた。計測した環境のバッファサイズは4096だった。この数に依存した作りはご法度だが、およその目安を知ることができた。

まとめ

Windows C#だと、子プロセスと匿名パイプでのインタラクションが必要な場合は、4096バイト程度はバッファがある前提で、非同期プログラミング出来る。

未分類csharp

Posted by first_user