Viele große Dateien Kopieren (Schnell)

  • VB.NET

Es gibt 12 Antworten in diesem Thema. Der letzte Beitrag () ist von picoflop.

    Viele große Dateien Kopieren (Schnell)

    Hallo alle zusammen,

    Ich arbeite seit ein Paar tagen an einer Methode mit der man große Dateien schnell kopieren kann sprich schneller als mit der Windows Kopierfunktion, vor allem wenn man mehrere Dateien auf einmal Kopiert bricht bei Windows die Geschwindigkeit zusammen.

    Die Dateien haben etwa 7-16GB/Datei und es gibt ca. 40 Dateien.

    Mir stellen sich hierbei einige Fragen zur Optimierung des Codes, vor allem würde es mich interessieren ob es eine Mathematische Formel gibt mit der man die Optimale Größe des Puffers ermitteln kann.
    Denn wir mir bei Tests aufgefallen ist hat die Größe ja eine große Auswirkung auf die Geschwindigkeit.

    Mein Code arbeitet derzeit eine Datei Synchron ab, die frage ist ob es sich lohnen würde auf Asynch E/A zu wechseln und wenn wäre ich für einige Tutorials dankbar denn bei Google hab ich zwar manches gefunden doch das hat mich an dem Sinn von Asynch E/A bei reinen Kopieraktionen zweifeln lassen.
    Außerdem stellt sich mir generell die frage was besser ist, das starten mehrerer Kopiervorgänge in verschiedenen Threads oder das Abarbeiten der Dateien nacheinander.

    Mein Code sieht bisher so aus:


    VB.NET-Quellcode

    1. Function copy(ByVal scr As String, ByVal out As String, Optional ByVal buffersize As Long = 8192) As Boolean
    2. Dim scrf As New IO.FileStream(scr, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
    3. Dim outf As New IO.FileStream(out, FileMode.Create, FileAccess.ReadWrite, FileShare.None)
    4. Dim data(buffersize) As Byte
    5. Dim buffer As Long = 0
    6. Dim buffercount As Long = 0
    7. Dim filesize As Long = scrf.Length
    8. While True
    9. If buffercount >= filesize - buffersize Then
    10. ReDim data(filesize - buffercount)
    11. buffer = scrf.Read(data, 0, filesize - buffercount)
    12. outf.Write(data, 0, buffer)
    13. Exit While
    14. End If
    15. buffer = scrf.Read(data, 0, buffersize)
    16. outf.Write(data, 0, buffersize)
    17. buffercount += buffer
    18. End While
    19. Dim MD5 As New MD5CryptoServiceProvider
    20. Dim Hash As Byte()
    21. Dim res1 = "", res2 As String = ""
    22. Dim Tmp As String = ""
    23. MD5.ComputeHash(scrf)
    24. Hash = MD5.Hash
    25. scrf.Close()
    26. For i As Integer = 0 To Hash.Length - 1
    27. Tmp = Hex(Hash(i))
    28. If Len(Tmp) = 1 Then Tmp = "0" & Tmp
    29. res1 += Tmp
    30. Next
    31. MD5.ComputeHash(outf)
    32. Hash = MD5.Hash
    33. outf.Close()
    34. For i As Integer = 0 To Hash.Length - 1
    35. Tmp = Hex(Hash(i))
    36. If Len(Tmp) = 1 Then Tmp = "0" & Tmp
    37. res2 += Tmp
    38. Next
    39. If res1 = res2 Then
    40. Return True
    41. Else
    42. Return False
    43. End If
    44. End Function


    Edit: Sorry hatte mich schon gewundert.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „d3luxee“ ()

    @Dodo Sorry hatte mich schon gewundert wieso das nicht funktioniert, bin phpbb Boards gewohnt :D

    @petaod Danke für den Tipp, werde versuchen das einfließen zu lassen.

    Edit: Seht ihr in dem Code irgendwo ein Memoryleak? Weil beim Kopieren füllen sich alle 8GB Ram und werden auch nach beenden des Programms nicht frei.
    Das Problem trat auch schon bei anderen .Net Programmen auf.
    Windows 7 64bit alles aktuell.
    Einschränkung:
    Das mit dem Komprimieren bringt natürlich nur etwas, wenn du es von einem schnellen auf ein langsames Medium schiebst (lokal nach Netzwerk) und die Dekomprimierung auf dem Zielrechner durchführst.


    Vielleicht kannst du dir von diesem Projekt Anregungen holen.
    Ist allerdings C++
    --
    If Not Program.isWorking Then Code.Debug Else Code.DoNotTouch
    --
    Außerdem stellt sich mir generell die frage was besser ist, das starten mehrerer Kopiervorgänge in verschiedenen Threads oder das Abarbeiten der Dateien nacheinander.

    Ist bei Festplattenaktionen nicht der Knaller. Erstens tut die CPU optimalerweise sowieso kaum was als warten (async wäre also schon mal gut) und zweitens jagd man den SchreibLesekopf ggf unnötig durch die Gegend. Daraus folgt dann auch, dass sehr große Buffer dann was bringen, wenn die Platte nicht zu stark defragmentiert ist.

    btw: msdn.microsoft.com/de-de/library/dd783870.aspx !
    Seht ihr in dem Code irgendwo ein Memoryleak?
    du mußt bei allem, was du verwendest, herausfinden, ob es IDisposable implementiert, und es dann disposen.

    Anderenfalls, können MemoryLeaks und andere Resourcen-Engpässe auftreten (oder auch nicht, kommt aufs Dingsbums an, welches IDisposable implementiert)

    Meist empfiehlt sich sogar die Verwendung des Using-Blocks

    HintergrundInfo: dieses Buch Lesen, Stichwort "Resourcenbereinigung"

    Achso: Sieht so aus, als alloziiertest du ein Array, wo die ganze GigaByte-Datei rein soll. Das ist natürlich irre, und um sowas zu Vermeiden wurden Streams ja erst erfunden - gugge Stream-Konzepte
    @petaod
    Der Kopiervorgang findet auf einem Computer statt also von einer Festplatte zur anderen.
    Also lass ich das Komprimieren besser.

    @pico
    Ich hab auch schon mit großen Puffern experimentiert jedoch waren die schreibraten eher mäßig.
    Das Problem das ich bei einem großen Puffer bemerke ist das er erst die Daten in den Puffer einliest und danach schreibt er sie aber im selben Moment liest er nicht wieder ein.
    Deshalb sind kleinere Caches schneller beim Synchronen schreiben.
    Beim Asynchronen E/A wäre das sicher besser und würde sicher schneller gehen allerdings ist das wesentlich schwerer.


    @ErfinderDesRades
    Ich hab bisher eher das Gefühl das der Fehler im Framework liegt denn früher hatte ich das Problem nicht, denn der Fehler tritt auch bei Programmen auf die früher noch ohne Leak funktioniert haben.
    Edit: Wo speichert denn ein Array die ganze Datei?
    Ich lese doch immer nur ein Teil der Datei in das Array "data" ein und das Array ist nur 8192 Bytes groß.. einlesen und den Puffer wieder schreiben und dann wieder neu einlesen.
    Oder habe ich da einen mir nicht bewussten Fehler gemacht?
    Bisher war meine Theorie das es die Bytes im data Array nicht überschreibt sondern wieder ein neues anlegt bzw. die alten Daten nicht wieder frei gibt.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „d3luxee“ ()

    d3luxee schrieb:

    Deshalb sind kleinere Caches schneller beim Synchronen schreiben.
    Beim Asynchronen E/A wäre das sicher besser und würde sicher schneller gehen allerdings ist das wesentlich schwerer.

    Wenn du auf der gleichen physikalischen Platte kopierst, bringt paralleles Lesen/Schreiben wohl eher nix. Ganz im Gegenteil.

    Und "Async" ist super-einfach:
    Async CTP runterladen, installieren, verstehen (easy wenn man Erfahrung mit VB hat) und machen ;)
    microsoft.com/download/en/details.aspx?id=9983.
    Wäre auch ne nette Aufgabe für die "Dataflow" Erweiterung im CTP. Einen ActionBlock fürs Lesen, einen fürs Schreiben und den einfach an den Leser anhängen. Lustig. Leider keine Zeit zum ausprobieren im Moment ...
    Also ich kopiere von einer Physikalischen Platte auf eine andere, somit sollte paralleles Lesen/Schreiben doch vorteile haben, da die eine Platte nur liest und die andere nur schreibt dann ist es doch besser wenn beide Platten arbeiten und nicht eine auf die andere Wartet.

    Danke übrigens für den tipp mit Async CTP bin gerade am herunterladen, ich glaub schon das ich es verstehen werde.
    Wie oft kopierst Du denn die 40 ateien?
    Nimm den Explorer.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Falls es jemanden interessiert, habe ich mal ein bißchen rumgespielt:

    Laaaaaang ;)
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Imports AsyncCtpExtensions
    2. Imports System.Threading.Tasks
    3. Public Class Form1
    4. Private files As New List(Of IO.FileInfo)
    5. Private outfolder As String = String.Empty
    6. Private Class FileBuffer
    7. Public buf() As Byte
    8. Public contentlength As Integer = 0
    9. Public Sub New(ByVal size As Integer)
    10. Dim b(size - 1) As Byte
    11. buf = b
    12. End Sub
    13. End Class
    14. Private Class CopyResult
    15. Public Property Millis As Long
    16. Public Property Bytes As Integer
    17. Public ReadOnly Property BytesPerSecond As String
    18. Get
    19. Return New NiceBytes(CLng(Bytes) * 1000 \ Millis).ToString & "/sec"
    20. End Get
    21. End Property
    22. Public Sub New(ByVal millis As Long, bytes As Integer)
    23. Me.Millis = millis
    24. Me.Bytes = bytes
    25. End Sub
    26. End Class
    27. Private Class NiceBytes
    28. Public Property Bytes As Long
    29. Public Sub New(ByVal b As Long)
    30. Bytes = b
    31. End Sub
    32. Public Overrides Function ToString() As String
    33. Static units() As String = {"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"}
    34. Dim counter As Integer = 0
    35. Dim tmp As Long = Bytes
    36. While tmp > 10 * 1024
    37. tmp >>= 10
    38. counter += 1
    39. End While
    40. Return tmp.ToString & " " & units(counter) & "B"
    41. End Function
    42. End Class
    43. Private Async Function DoCopy(ByVal fnames() As String, outpath As String, ByVal bufsize As Integer) As Task(Of CopyResult)
    44. Dim CopyDic As New SortedDictionary(Of String, String)
    45. For Each s As String In fnames
    46. CopyDic.Add(s, IO.Path.Combine(outpath, IO.Path.GetFileName(s)))
    47. Next
    48. Dim buf() = {New FileBuffer(bufsize), New FileBuffer(bufsize)}
    49. Dim readertask As Task(Of Integer)
    50. Dim writertask As Task
    51. Dim total As Integer = 0
    52. 'Dim toggle As Integer = 0
    53. Dim stp As Stopwatch
    54. GC.Collect()
    55. GC.WaitForPendingFinalizers()
    56. GC.Collect()
    57. stp = Stopwatch.StartNew
    58. For Each kvp As KeyValuePair(Of String, String) In CopyDic
    59. Debug.Print(String.Format("From {0} to {1}", kvp.Key, kvp.Value))
    60. Using instrm As New IO.FileStream(kvp.Key, IO.FileMode.Open, IO.FileAccess.Read, IO.FileShare.None)
    61. Using outstrm As New IO.FileStream(kvp.Value, IO.FileMode.Create, IO.FileAccess.Write, IO.FileShare.None)
    62. Dim toggle As Integer = 0
    63. Dim bytesread As Integer = 0
    64. Do
    65. If toggle = 0 Then
    66. ' first run, just read
    67. Debug.Print(String.Format("First Read"))
    68. bytesread = Await instrm.ReadAsync(buf(0).buf, 0, bufsize)
    69. Else
    70. ' read and write
    71. readertask = instrm.ReadAsync(buf(toggle Mod 2).buf, 0, bufsize)
    72. writertask = outstrm.WriteAsync(buf((toggle - 1) Mod 2).buf, 0, buf((toggle - 1) Mod 2).contentlength)
    73. Await TaskEx.WhenAll(readertask, writertask)
    74. 'Debug.Print(String.Format("Current {0} Total {1}", bytesread, total))
    75. bytesread = readertask.Result
    76. End If
    77. buf(toggle Mod 2).contentlength = bytesread
    78. total += bytesread
    79. If bytesread < bufsize Then Exit Do
    80. toggle += 1
    81. Loop
    82. Debug.Print("Loop done for " & kvp.Value)
    83. If buf(toggle Mod 2).contentlength > 0 Then
    84. Await outstrm.WriteAsync(buf(toggle Mod 2).buf, 0, buf(toggle Mod 2).contentlength)
    85. End If
    86. End Using
    87. End Using
    88. Next
    89. stp.Stop()
    90. Return New CopyResult(stp.ElapsedMilliseconds, total \ 2)
    91. End Function
    92. Private Sub btnSelectFiles_Click(sender As System.Object, e As System.EventArgs) Handles btnSelectFiles.Click
    93. Dim ofd As New OpenFileDialog With {.Multiselect = True}
    94. If ofd.ShowDialog = Windows.Forms.DialogResult.OK AndAlso ofd.FileNames.Count > 0 Then
    95. files.Clear()
    96. files.AddRange((From s As String In ofd.FileNames Select New IO.FileInfo(s)).ToArray)
    97. End If
    98. End Sub
    99. Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
    100. With cmbBufferSize.Items
    101. .Add(New NiceBytes(8 * 1024))
    102. .Add(New NiceBytes(8 * 8 * 1024))
    103. .Add(New NiceBytes(8 * 8 * 8 * 1024))
    104. .Add(New NiceBytes(8 * 8 * 8 * 8 * 1024))
    105. .Add(New NiceBytes(8 * 8 * 8 * 8 * 8 * 1024))
    106. End With
    107. cmbBufferSize.SelectedIndex = 0
    108. End Sub
    109. Private Async Sub btnStart_Click(sender As System.Object, e As System.EventArgs) Handles btnStart.Click
    110. Static running As Boolean = False
    111. If running Then Exit Sub
    112. If files.Count = 0 OrElse outfolder = String.Empty OrElse cmbBufferSize.SelectedItem Is Nothing Then Exit Sub
    113. running = True
    114. btnStart.Text = "running"
    115. CopyProgress.Style = ProgressBarStyle.Marquee
    116. Dim fl = From f As IO.FileInfo In files Select f.FullName
    117. Dim r As CopyResult = Await DoCopy(fl.ToArray, outfolder, CInt(DirectCast(cmbBufferSize.SelectedItem, NiceBytes).Bytes))
    118. Dim mi = New NiceBytes((From f As IO.FileInfo In files Select f.Length).Min)
    119. Dim ma = New NiceBytes((From f As IO.FileInfo In files Select f.Length).Max)
    120. Dim av = New NiceBytes(CLng((From f As IO.FileInfo In files Select f.Length).Average))
    121. Dim tt = New NiceBytes((From f As IO.FileInfo In files Select f.Length).Sum)
    122. Dim s As String = String.Format("# {0} Min {1} Max {2} Avg {3} Tot {4}", files.Count, mi, ma, av, tt)
    123. lstResults.Items.Add(s)
    124. s = String.Format("Buf {0} Spd {1}", cmbBufferSize.SelectedItem.ToString, r.BytesPerSecond.ToString)
    125. lstResults.Items.Add(s)
    126. CopyProgress.Style = ProgressBarStyle.Blocks
    127. btnStart.Text = "Start!"
    128. running = False
    129. End Sub
    130. Private Sub btnSelectFolder_Click(sender As System.Object, e As System.EventArgs) Handles btnSelectFolder.Click
    131. Dim fbd As New FolderBrowserDialog
    132. If fbd.ShowDialog = Windows.Forms.DialogResult.OK AndAlso fbd.SelectedPath.Length > 0 Then
    133. outfolder = fbd.SelectedPath
    134. End If
    135. End Sub
    136. End Class


    Und wers mal ausprobieren will: Projekt hab ich mal als 7z Archiv (92 kB) angehängt. Hoffe das läuft.
    Speed scheint der Hammer zu sein ;) Hoffentlich sind die Dateien ok, sieht aber so aus.
    Lesen von SSD, Schreiben auf Raid0 aus 3 Platten: Max rund 700 MiB/s ... :thumbup:
    Und größere Caches sind deutlich besser als sehr kleine (8kb, 64kb, 512kb, 4MB und 32MB können ausgwählt werden)

    EDIT: Es gibt noch eine Optimierungsmöglichkeit, die das arbeiten mit großen Buffers noch beschleunigen würde (Tip: Lesen/Schreiben/Abschließen), aber das ist nicht GANZ einfach umzusetzen. Wer die optimierungsmöglichkeit findet kriegt 10 Gummipunkte. Wer sie implementiert bekommt 100 ;)

    EDIT2: Mal als Vorschlag: Seht es als Programmierwettbewerb. Wer schreibt die schnellste Kopierfunktion? Erlaubt ist, was gefällt. Gemessen wird die Zeit die die Kopierfunktion braucht, die als Input nur die Namen der Quelldateien als Array und den Zielpfad als String erwartet. Geschwindigkeit ist Anzahl der geschriebenen Bytes pro Sekunde. Testen muss das natürlich jeder selbst, es sei denn, wir machen es "offiziell" und ein Schiedsrichter findet sich, der jedes "Programm" mit definierten Szenarien testet.
    Wäre doch mal ein angenehmer "Ersatz" für die unsäglichen Community-Projekte. Kurz, knapp (KEINE Grafikorgien, sondern nur das was nötig ist, damit man die eigentliche Funktion testen kann und ein Ergebnis gezeigt bekommt).
    Meinungen?
    Dateien
    • TheQuickMover.7z

      (92,21 kB, 234 mal heruntergeladen, zuletzt: )

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „picoflop“ ()