WinFormsで画像を表示するまでの時間を計測

コンピュータ

画像ファイルを読み込んでGUIに表示するまでの時間を計測してみました。

結果

同期SSD

File.Exists():1ms
FileSize.Get():0ms Size:26615436B
MemoryStream.Open():0ms
FileStream new():7ms
FileStream ⇢ MemoryStream:22ms
MemoryStream Seek:0ms
MemoryStream ⇢ Bitmap(Decode):336ms
Bitmap ⇢ UI(PictureBox):0ms
WinForms Total:366ms

ファイルの有無を確認するExists()は一瞬で終わる感じがしますが1msということですので、短時間ではありますが多量のファイルチェックを考えると馬鹿にならない時間になりそうです。
ファイルのサイズの取得は0msですが、前段のExits()と順番を入れかると結果が変わるかもしれません。
メモリストリームのオープンは0msで非常に短時間。
ファイルストリームのオープンは7msで流石にストレージのストリームだと少し時間がかかる。
ファイルストリームからメモリストリームのコピー命令は実質、ファイルの読み込み時間となり22msでさすがSSDといった感じです。
メモリストリームの移動は0ms。
メモリストリームからビットマップはデコード処理で336msと一連のが慣れでは一番重い処理。
トータルは366msでその時間の殆どがデコードにかかった時間で有ることが確認できました。

同期NAS

File.Exists():6ms
FileSize.Get():0ms Size:26615436B
MemoryStream.Open():0ms
FileStream new():14ms
FileStream ⇢ MemoryStream:338ms
MemoryStream Seek:0ms
MemoryStream ⇢ Bitmap(Decode):337ms
Bitmap ⇢ UI(PictureBox):0ms
WinForms Total:695ms

次にNASです。
ファイルの有無を確認の段階で6msと1msのSSDと比べると遅くなっています。やはり有無のチェックは最低限にとどめた方が良さそうです。
ファイルストリームのオープンはSSDが7msでNASが14msで遅くなっています。
またファイルの読み込みがSSDが22msのところNASが338msと大きく差がついています。
トータルは695msで、SSDと比べてだいぶ遅いことが確認できます。

非同期SSD

File.Exists():0ms
FileSize.Get():0ms Size:26615436B
MemoryStream.Open():0ms
FileStream new():7ms
FileStream ⇢ MemoryStream(Async):32ms
MemoryStream Seek:0ms
MemoryStream ⇢ Bitmap(Decode)(Async):338ms
Bitmap ⇢ UI(PictureBox):0ms
WinForms Total:377ms

次に非同期処理のSSDになります。
重たい処理を別スレッドに任せることでUIがフリーズする時間を短くすることが目的で、重たい処理を割り当てると効果的と言われています。
ただ、並列処理では無いのでパフォーマンスの向上は望めず、スレッドの切り替えの関係で逆に遅くなると考えられます。
計測結果を見てみるとトータルで同期が366msで非同期377msとほぼ同等といった感じでした。誤差かもしれませんが若干非同期の方が遅い結果となります。

非同期NAS

File.Exists():6ms
FileSize.Get():0ms Size:26615436B
MemoryStream.Open():0ms
FileStream new():13ms
FileStream ⇢ MemoryStream(Async):134ms
MemoryStream Seek:0ms
MemoryStream ⇢ Bitmap(Decode)(Async):341ms
Bitmap ⇢ UI(PictureBox):0ms
WinForms Total:494ms

WinFormsの最後は非同期のNAS
トータルは同期版の695msから非同期494msとだいぶ速くなっています。ただし速くなった要素がファイルの読み込み部分なので、ネットワークの混雑具合で発生したイレギュラーだと思われます。内部ストレージの場合比較的安定した速度が出ますが、NASなどのネットワーク機器の場合、速度がばらつく現象を確認できたと思います。

ChatGPTからコードの改善提案がありましたので盛り込んで見ました。

改善非同期版SSD

File.Exists():1ms
FileSize.Get():0ms Size:26615436B
MemoryStream.Open():0ms
FileStream new():9ms
FileStream ⇢ MemoryStream(Async):38ms
MemoryStream Seek:0ms
MemoryStream ⇢ Bitmap(Decode)(Async):342ms
Bitmap ⇢ UI(PictureBox):0ms
WinForms Total:390ms

改善非同期版NAS

File.Exists():6ms
FileSize.Get():0ms Size:26615436B
MemoryStream.Open():0ms
FileStream new():17ms
FileStream ⇢ MemoryStream(Async):285ms
MemoryStream Seek:0ms
MemoryStream ⇢ Bitmap(Decode)(Async):341ms
Bitmap ⇢ UI(PictureBox):0ms
WinForms Total:649ms

まとめ

項目 SSD(同期) SSD(非同期) SSD(改善) NAS(同期) NAS(非同期) NAS(改善)
File.Exists() 1ms 0ms 1ms 6ms 6ms 6ms
File ⇢ MemStream 22ms 32ms 38ms 338ms 134ms 285ms
MemStream ⇢ Bitmap 336ms 338ms 342ms 337ms 341ms 341ms
Total 366ms 377ms 390ms 695ms 494ms 649ms

ソースコード

同期版

using System.Diagnostics;
using System.Net;

namespace PreviewSeed01;

public partial class Form1 : Form
{
    PictureBox _picbox = new()
    {
        Dock = DockStyle.Fill,
        SizeMode = PictureBoxSizeMode.Zoom,
    };
    public Form1()
    {
        InitializeComponent();

        this.Controls.Add(_picbox);

        this.Load += async (sender, e) =>
        {
            const int DELAY_TIME = 5000;
            const string IMAGE_FILE = "G:\\testdata\\00001un.png"; // SSD
            //const string IMAGE_FILE = "X:\\testdata\\00001un.png"; // NAS

            Stopwatch sw = new();
            long totalTime = 0;

            // 待ち
            await Task.Delay(DELAY_TIME);

            sw.Start();
            if (!File.Exists(IMAGE_FILE))
            {
                return;
            }
            sw.Stop();
            totalTime += sw.ElapsedMilliseconds;
            Debug.Print($"File.Exists():{sw.ElapsedMilliseconds}ms");

            sw.Restart();
            FileInfo fi = new (IMAGE_FILE);
            sw.Stop();
            totalTime += sw.ElapsedMilliseconds;
            Debug.Print($"FileSize.Get():{sw.ElapsedMilliseconds}ms Size:{fi.Length}B");

            sw.Restart();
            using MemoryStream ms = new();
            sw.Stop();
            totalTime += sw.ElapsedMilliseconds;
            Debug.Print($"MemoryStream.Open():{sw.ElapsedMilliseconds}ms");

            sw.Restart();
            using FileStream fs = new(IMAGE_FILE, FileMode.Open, FileAccess.Read);
            sw.Stop();
            totalTime += sw.ElapsedMilliseconds;
            Debug.Print($"FileStream new():{sw.ElapsedMilliseconds}ms");

            sw.Restart();
            fs.CopyTo(ms);
            sw.Stop();
            totalTime += sw.ElapsedMilliseconds;
            Debug.Print($"FileStream ⇢ MemoryStream:{sw.ElapsedMilliseconds}ms");

            sw.Restart();
            ms.Seek(0, SeekOrigin.Begin);
            sw.Stop();
            totalTime += sw.ElapsedMilliseconds;
            Debug.Print($"MemoryStream Seek:{sw.ElapsedMilliseconds}ms");

            sw.Restart();
            var bmp = new Bitmap(ms);
            sw.Stop();
            totalTime += sw.ElapsedMilliseconds;
            Debug.Print($"MemoryStream ⇢ Bitmap(Decode):{sw.ElapsedMilliseconds}ms");

            sw.Restart();
            _picbox.Image = bmp;
            sw.Stop();
            totalTime += sw.ElapsedMilliseconds;
            Debug.Print($"Bitmap ⇢ UI(PictureBox):{sw.ElapsedMilliseconds}ms");

            Debug.Print($"WinForms Total:{totalTime}ms");
        };
    }
}

非同期版

using System.Diagnostics;

namespace PreviewSeed01;

public partial class Form1 : Form
{
    PictureBox _picbox = new()
    {
        Dock = DockStyle.Fill,
        SizeMode = PictureBoxSizeMode.Zoom,
    };
    public Form1()
    {
        InitializeComponent();

        this.Controls.Add(_picbox);

        this.Load += async (sender, e) =>
        {
            const int DELAY_TIME = 5000;
            const string IMAGE_FILE = "G:\\testdata\\00001un.png"; // SSD
            //const string IMAGE_FILE = "X:\\testdata\\00001un.png"; // NAS

            Stopwatch sw = new();
            long totalTime = 0;

            // 待ち
            await Task.Delay(DELAY_TIME);

            sw.Start();
            if (!File.Exists(IMAGE_FILE))
            {
                return;
            }
            sw.Stop();
            totalTime += sw.ElapsedMilliseconds;
            Debug.Print($"File.Exists():{sw.ElapsedMilliseconds}ms");

            sw.Restart();
            FileInfo fi = new (IMAGE_FILE);
            sw.Stop();
            totalTime += sw.ElapsedMilliseconds;
            Debug.Print($"FileSize.Get():{sw.ElapsedMilliseconds}ms Size:{fi.Length}B");

            sw.Restart();
            using MemoryStream ms = new();
            sw.Stop();
            totalTime += sw.ElapsedMilliseconds;
            Debug.Print($"MemoryStream.Open():{sw.ElapsedMilliseconds}ms");

            sw.Restart();
            using FileStream fs = new(IMAGE_FILE, FileMode.Open, FileAccess.Read);
            sw.Stop();
            totalTime += sw.ElapsedMilliseconds;
            Debug.Print($"FileStream new():{sw.ElapsedMilliseconds}ms");

            sw.Restart();
            await fs.CopyToAsync(ms);
            sw.Stop();
            totalTime += sw.ElapsedMilliseconds;
            Debug.Print($"FileStream ⇢ MemoryStream(Async):{sw.ElapsedMilliseconds}ms");

            sw.Restart();
            ms.Seek(0, SeekOrigin.Begin);
            sw.Stop();
            totalTime += sw.ElapsedMilliseconds;
            Debug.Print($"MemoryStream Seek:{sw.ElapsedMilliseconds}ms");

            sw.Restart();
            var bmp = await Task.Run(() =>
            {
                return new Bitmap(ms);
            });
            sw.Stop();
            totalTime += sw.ElapsedMilliseconds;
            Debug.Print($"MemoryStream ⇢ Bitmap(Decode)(Async):{sw.ElapsedMilliseconds}ms");

            sw.Restart();
            _picbox.Image = bmp;
            sw.Stop();
            totalTime += sw.ElapsedMilliseconds;
            Debug.Print($"Bitmap ⇢ UI(PictureBox):{sw.ElapsedMilliseconds}ms");

            Debug.Print($"WinForms Total:{totalTime}ms");
        };
    }
}

非同期改良版

using System.Diagnostics;

namespace PreviewSeed01;

public partial class Form1 : Form
{
    PictureBox _picbox = new()
    {
        Dock = DockStyle.Fill,
        SizeMode = PictureBoxSizeMode.Zoom,
    };
    public Form1()
    {
        InitializeComponent();

        this.Controls.Add(_picbox);

        this.Load += async (sender, e) =>
        {
            const int DELAY_TIME = 5000;
            const int BUFF_SIZE = 81920;
            const string IMAGE_FILE = "G:\\testdata\\00001un.png"; // SSD
            //const string IMAGE_FILE = "X:\\testdata\\00001un.png"; // NAS

            Stopwatch sw = new();
            long totalTime = 0;

            // 待ち
            await Task.Delay(DELAY_TIME);

            sw.Start();
            if (!File.Exists(IMAGE_FILE))
            {
                return;
            }
            sw.Stop();
            totalTime += sw.ElapsedMilliseconds;
            Debug.Print($"File.Exists():{sw.ElapsedMilliseconds}ms");

            sw.Restart();
            FileInfo fi = new (IMAGE_FILE);
            sw.Stop();
            totalTime += sw.ElapsedMilliseconds;
            Debug.Print($"FileSize.Get():{sw.ElapsedMilliseconds}ms Size:{fi.Length}B");

            sw.Restart();
            using MemoryStream ms = new((int)fi.Length);// 容量指定
            sw.Stop();
            totalTime += sw.ElapsedMilliseconds;
            Debug.Print($"MemoryStream.Open():{sw.ElapsedMilliseconds}ms");


            sw.Restart();
            using FileStream fs = new(IMAGE_FILE, FileMode.Open, FileAccess.Read, FileShare.Read, BUFF_SIZE, true);
            sw.Stop();
            totalTime += sw.ElapsedMilliseconds;
            Debug.Print($"FileStream new():{sw.ElapsedMilliseconds}ms");

            sw.Restart();
            await fs.CopyToAsync(ms);
            sw.Stop();
            totalTime += sw.ElapsedMilliseconds;
            Debug.Print($"FileStream ⇢ MemoryStream(Async):{sw.ElapsedMilliseconds}ms");

            sw.Restart();
            ms.Seek(0, SeekOrigin.Begin);
            sw.Stop();
            totalTime += sw.ElapsedMilliseconds;
            Debug.Print($"MemoryStream Seek:{sw.ElapsedMilliseconds}ms");

            sw.Restart();
            var bmp = await Task.Factory.StartNew(() =>
            {
                return new Bitmap(ms);
            }, TaskCreationOptions.LongRunning); // 重い処理に適切
            sw.Stop();
            totalTime += sw.ElapsedMilliseconds;
            Debug.Print($"MemoryStream ⇢ Bitmap(Decode)(Async):{sw.ElapsedMilliseconds}ms");

            sw.Restart();
            _picbox.Image = bmp;
            sw.Stop();
            totalTime += sw.ElapsedMilliseconds;
            Debug.Print($"Bitmap ⇢ UI(PictureBox):{sw.ElapsedMilliseconds}ms");

            Debug.Print($"WinForms Total:{totalTime}ms");
        };
    }
}

コメント