86429


あるフォルダに縦80ピクセル、横100ピクセルにリサイズされた4000枚の画像ファイルが置いてあります。これを読み込んでListViewコントロールに画像の一覧を表示します。

問題は時間がかかる事です。読み込み処理の時、最初の1000枚は約30秒かかるのですが、次の1000枚は約56秒、次の1000枚は約79秒、次の1000枚は約105秒かかります。だんだんと読み込み速度が遅くなってるようなのです。これを最初の読み込み速度を維持したまま全部読み込めば、約２分で全部読み込み終わります。

[code]

    Imports System
    Imports System.IO
    
    Public Class Form1
      Dim imgList As New ImageList
      Dim sec As Double = 0
    
      Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim sw As New System.Diagnostics.Stopwatch()
        sw.Start()
        Dim oldsec As Integer = 0
    
        For Each path As Object In Directory.GetFiles(Application.StartupPath & "\cache\", "*", SearchOption.TopDirectoryOnly)
          Dim fs As FileStream = File.OpenRead(path)
          imgList.Images.Add(Image.FromStream(fs, False, False))
    
          ListView1.Items.Add(path, imgList.Images.Count)
    
          If imgList.Images.Count = 1000 Then
            Debug.WriteLine("1 sec = " & sw.Elapsed.TotalSeconds.ToString & "  diff = " & Math.Abs(oldsec - Double.Parse(sw.Elapsed.TotalSeconds.ToString)))
            oldsec = Double.Parse(sw.Elapsed.TotalSeconds.ToString)
          ElseIf imgList.Images.Count = 2000 Then
            Debug.WriteLine("2 sec = " & sw.Elapsed.TotalSeconds.ToString & "  diff = " & Math.Abs(oldsec - Double.Parse(sw.Elapsed.TotalSeconds.ToString)))
            oldsec = Double.Parse(sw.Elapsed.TotalSeconds.ToString)
          ElseIf imgList.Images.Count = 3000 Then
            Debug.WriteLine("3 sec = " & sw.Elapsed.TotalSeconds.ToString & "  diff = " & Math.Abs(oldsec - Double.Parse(sw.Elapsed.TotalSeconds.ToString)))
            oldsec = Double.Parse(sw.Elapsed.TotalSeconds.ToString)
          ElseIf imgList.Images.Count = 4000 Then
            Debug.WriteLine("4 sec = " & sw.Elapsed.TotalSeconds.ToString & "  diff = " & Math.Abs(oldsec - Double.Parse(sw.Elapsed.TotalSeconds.ToString)))
            oldsec = Double.Parse(sw.Elapsed.TotalSeconds.ToString)
          End If
        Next
    
        sw.Stop()
      End Sub
    
      Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        imgList.ImageSize = New Size(100, 80)
        ListView1.LargeImageList = imgList
      End Sub
    
[/code]

出力ウィンドウに出力された文字列

[code]

    1 sec = 31.8310536  diff = 31.8310804
    2 sec = 88.7196354  diff = 56.7196397
    3 sec = 168.8318207  diff = 79.831824
    4 sec = 274.2561862  diff = 105.256191
    
[/code]

どのようにすれば最初の読み込み速度を維持したまま全部読み込み終えるでしょうか？あるいはもっと早く読み込み終えられるでしょうか？  
条件としては、ListViewの仮想モードを使わない、基本的にはマルチスレッドを使わない、です。


CC BY-SA 4.0
2022-02-17T09:33:27.427
86414
2022-02-18T06:07:51.127
2022-02-18T05:41:19.247
4236
51020
question
vb.net winforms
大量の画像ファイルを読み込む時、読み込み速度が次第に遅くなる


コメントして頂いた方のアドバイスに従ってListViewへの追加を最後らへんに行うようにしたところ、かなり早くなりました。

出力ウィンドウに出力された文字列

[code]

    1 sec = 1.7978672  diff = 1.7978888
    2 sec = 4.0005821  diff = 2.0005857
    3 sec = 6.5848129  diff = 2.5848188
    4 sec = 9.9223631  diff = 2.9223676
    sec = 32.1548404  diff = 22.1548443
    
[/code]

変更後のコード

[code]

    Imports System
    Imports System.IO
    
    Public Class Form1
      Dim imgList As New ImageList
      Dim sec As Double = 0
      Dim strArr(4001) As String
    
      Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim sw As New System.Diagnostics.Stopwatch()
        sw.Start()
        Dim oldsec As Integer = 0
        Dim index As Integer = 0
    
        For Each path As Object In Directory.GetFiles(Application.StartupPath & "\cache\", "*", SearchOption.TopDirectoryOnly)
          Dim fs As FileStream = File.OpenRead(path)
          imgList.Images.Add(Image.FromStream(fs, False, False))
    
          strArr(index) = path
          index += 1
    
          If imgList.Images.Count = 1000 Then
            Debug.WriteLine("1 sec = " & sw.Elapsed.TotalSeconds.ToString & "  diff = " & Math.Abs(oldsec - Double.Parse(sw.Elapsed.TotalSeconds.ToString)))
            oldsec = Double.Parse(sw.Elapsed.TotalSeconds.ToString)
          ElseIf imgList.Images.Count = 2000 Then
            Debug.WriteLine("2 sec = " & sw.Elapsed.TotalSeconds.ToString & "  diff = " & Math.Abs(oldsec - Double.Parse(sw.Elapsed.TotalSeconds.ToString)))
            oldsec = Double.Parse(sw.Elapsed.TotalSeconds.ToString)
          ElseIf imgList.Images.Count = 3000 Then
            Debug.WriteLine("3 sec = " & sw.Elapsed.TotalSeconds.ToString & "  diff = " & Math.Abs(oldsec - Double.Parse(sw.Elapsed.TotalSeconds.ToString)))
            oldsec = Double.Parse(sw.Elapsed.TotalSeconds.ToString)
          ElseIf imgList.Images.Count = 4000 Then
            Debug.WriteLine("4 sec = " & sw.Elapsed.TotalSeconds.ToString & "  diff = " & Math.Abs(oldsec - Double.Parse(sw.Elapsed.TotalSeconds.ToString)))
            oldsec = Double.Parse(sw.Elapsed.TotalSeconds.ToString)
          End If
        Next
    
        For i As Integer = 0 To 3999
          ListView1.Items.Add(strArr(i), i)
        Next
        Debug.WriteLine("sec = " & sw.Elapsed.TotalSeconds.ToString & "  diff = " & Math.Abs(oldsec - Double.Parse(sw.Elapsed.TotalSeconds.ToString)))
        sw.Stop()
      End Sub
    
      Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        imgList.ImageSize = New Size(100, 80)
        ListView1.LargeImageList = imgList
      End Sub
    
[/code]

ありがとうございました。

**\--------------------------追記---------------------------**

sayuri様の回答を参考に試してみたらさらに少し早くなりました。

出力ウィンドウに出力された文字列

[code]

    sec = 1.0324989
    sec = 10.7114742
    sec = 17.5112929
    
[/code]

コード

[code]

      Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim sw As New System.Diagnostics.Stopwatch()
        sw.Start()
    
        Dim images As New List(Of Image)
        Dim items As New List(Of ListViewItem)
    
        For Each path In Directory.GetFiles(Application.StartupPath & "\cache\", "*", SearchOption.TopDirectoryOnly)
          Dim fs As FileStream = File.OpenRead(path)
          images.Add(Image.FromStream(fs, False, False))
          items.Add(New ListViewItem(path, images.Count))
        Next
    
        Debug.WriteLine("sec = " & sw.Elapsed.TotalSeconds.ToString)
        imgList.Images.AddRange(images.ToArray())
    
        Debug.WriteLine("sec = " & sw.Elapsed.TotalSeconds.ToString)
        ListView1.Items.AddRange(items.ToArray())
    
        Debug.WriteLine("sec = " & sw.Elapsed.TotalSeconds.ToString) 
        sw.Stop()
      End Sub
    
[/code]

ListView.ListViewItemCollection.AddRangeの使い方などを教えていただきありがとうございました。


CC BY-SA 4.0
2022-02-17T13:32:56.287
86417
2022-02-18T06:07:51.127
2022-02-18T06:07:51.127
51020
51020
86414
answer


処理の開始時に[`ListView.BeginUpdate()`](https://docs.microsoft.com/ja-
jp/dotnet/api/system.windows.forms.listview.beginupdate?view=windowsdesktop-6.0)、終了後に[`ListView.EndUpdate()`](https://docs.microsoft.com/ja-
jp/dotnet/api/system.windows.forms.listview.endupdate?view=windowsdesktop-6.0)を呼ぶことで、項目操作毎の画面更新を抑止できるため、処理を高速化できます。

* * *

ただし、`ListView.BeginUpdate()`の項目でも説明されていますが、[`ListView.AddRange`](https://docs.microsoft.com/ja-
jp/dotnet/api/system.windows.forms.listview.listviewitemcollection.addrange?view=windowsdesktop-6.0)を使用した方が効果的です。しかも`ImageList`側には`BeginUpdate`
/ `EndUpdate`が存在せず[`AddRange()`](https://docs.microsoft.com/ja-
jp/dotnet/api/system.windows.forms.imagelist.imagecollection.addrange?view=windowsdesktop-6.0)のみですのでなおさらです。

C#でしか書けないのですが次のような流れになります。

[code]

    var sw = System.Diagnostics.Stopwatch.StartNew();
    
    var images = new List<Image>();
    var items = new List<ListViewItem>();
    
    foreach (var path in Directory.GetFiles(Application.StartupPath + "/cache")) {
        var fs = File.OpenRead(path);
        images.Add(Image.FromStream(fs, false, false));
        items.Add(new ListViewItem(path, images.Count));
    }
    
    imgList.Images.AddRange(images.ToArray());
    ListView1.Items.AddRange(items.ToArray());
    
    Debug.WriteLine("sec = {0}", sw.Elapsed.TotalSeconds);
    
    
[/code]


CC BY-SA 4.0
2022-02-18T04:27:53.710
86429
2022-02-18T04:27:53.710
4236
86414
answer