Laden...

Kopiervorgang Optimieren: File.Copy vs. FileStream.CopyTo

Erstellt von Rioma vor 9 Jahren Letzter Beitrag vor 9 Jahren 4.728 Views
R
Rioma Themenstarter:in
228 Beiträge seit 2013
vor 9 Jahren
Kopiervorgang Optimieren: File.Copy vs. FileStream.CopyTo

Hallo zusammen,

ich habe des öfteren gelesen, dass das Kopieren per Filestream schneller sein soll, als File.Copy().

Da ich in einigen Programmen Daten kopiere, wollte ich mir das mal genauer ansehen, aber bei mir ist genau das Gegenteil der Fall.

Mit vielen kleinen Dateien:
File.Copy --> 18s
Stream --> 50s

Mit einer großen Datei:
File.Copy --> 1.32 min
Stream --> 1.38min

Ich befürchte das es daran liegt, dass mein Code äußerst bescheiden ist.

Hier mal der Code zum Testen, den Methodenaufruf habe ich dann natürlich ausgetauscht.


using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;

namespace CopyTest
{
    class Program
    {
        private const string fromDirectory = @"M:\Anderes\Games\Mirrors Edge\";
        private const string toDirectory = @"F:\";

        private static DirectoryInfo Dir = new DirectoryInfo(fromDirectory);
        static void Main(string[] args)
        {
            Console.WriteLine("START");
            Stopwatch sp = new Stopwatch();
            sp.Start();
            StreamCopy();
            sp.Stop();

            Console.WriteLine(sp.Elapsed.TotalMinutes);
            Console.ReadKey();
        }

        private static void StreamCopy()
        {

            List<DirectoryInfo> directory = Dir.EnumerateDirectories("*", SearchOption.AllDirectories).ToList();

            foreach (DirectoryInfo directoryInfo in directory)
            {
                string newDir = GetFilePath(directoryInfo.FullName);
                Directory.CreateDirectory(newDir);
            }

            List<FileInfo> files = Dir.EnumerateFiles("*", SearchOption.AllDirectories).ToList();

            foreach (FileInfo file in files)
            {
                using (FileStream fsIn = new FileStream(file.FullName, FileMode.Open, FileAccess.Read))
                {
                    string filePath = GetFilePath(file.FullName);

                    using (FileStream fsOut = new FileStream(filePath, FileMode.Create, FileAccess.Write))
                    {
                        fsIn.CopyTo(fsOut);
                    }
                }
            }

        }

        private static void FileCopy()
        {
            List<DirectoryInfo> directory = Dir.EnumerateDirectories("*", SearchOption.AllDirectories).ToList();

            foreach (DirectoryInfo directoryInfo in directory)
            {
                string newDir = GetFilePath(directoryInfo.FullName);
                Directory.CreateDirectory(newDir);
            }

            List<FileInfo> files = Dir.EnumerateFiles("*", SearchOption.AllDirectories).ToList();

            foreach (FileInfo file in files)
            {
                string filePath = GetFilePath(file.FullName);
                File.Copy(file.FullName, filePath);
            }
        }

        private static string GetFilePath(string filepath)
        {
            string newPath = String.Concat(toDirectory, filepath.Substring(fromDirectory.Length, filepath.Length - fromDirectory.Length));
            return newPath;
        }

    }
}

Könnte vielleicht mal jemand auf meinen Test schauen und meinen Kopiervorgang optimieren?

Das wäre sehr nett.

Danke 😃

C
1.214 Beiträge seit 2006
vor 9 Jahren

Das geht evtl. wegen unbuffered I/O. Hier ist eine Diskussion drüber:

Fast file write with overlapped I/O

Der Titel ist falsch, weil da von overlapped I/O die Rede ist, habs aber so übernommen, wie es dransteht.

16.806 Beiträge seit 2008
vor 9 Jahren

File Copy von vielen kleinen Dateien ist immer langsamer als eine große Dateie aufgrund des Overheads, der beim Erstellen und Abschließen passiert.

Bei Dir ist es schon langsam, da Du FileInfo verwendest und hier eine Menge Overhead geladen wird.
Ansonsten kannst Dir gern mal QuickIO.NET - Performante Dateioperationen anschauen.
Die meisten Dinge, die in Coder's Link stehen, habe ich hier beachtet (direkte API Calls, pre-allocate der Ziel-Datei, etc....). Unsafe-Copy ist ein Mythos. Kaum Vorteil nur riesen Aufwand für fast nichts.
Vor allem ist das Dokument 10 Jahre alt. Da war noch nichts von den jetzigen Controllern / Festplatte ansatzweise zu erahnen.

Was bei Dir im Code suboptimal ist, ist das CopyTo der Streams.
Wenn Du nicht direkt die Zielgröße Datei angibst, was Du hier nicht tust, passt ers bei jedem Copy eines Chunks selbstständig an. Das kostet jedes mal 2 IO Calls.

R
Rioma Themenstarter:in
228 Beiträge seit 2013
vor 9 Jahren

Danke erstmal für eure Hilfe. Ich habe jetzt mal versucht die Length des Outstream fest zu setzen, dies hat aber nicht wirklich etwas gebracht. Außerdem habe ich zum Testen mal die CopyTo Methode durch manuelles Kopieren per Schleife versucht und dies hat auch keine änderung hervor gerufen.

Hier der Code:


 private static void StreamCopy()
        {

            List<DirectoryInfo> directory = Dir.EnumerateDirectories("*", SearchOption.AllDirectories).ToList();

            foreach (DirectoryInfo directoryInfo in directory)
            {
                string newDir = GetFilePath(directoryInfo.FullName);
                Directory.CreateDirectory(newDir);
            }

            List<FileInfo> files = Dir.EnumerateFiles("*", SearchOption.AllDirectories).ToList();

            int bufferSize = 1024 * 1024;
            int bytesRead = -1;
            byte[] bytes = new byte[bufferSize];

            foreach (FileInfo file in files)
            {
                using (FileStream fsIn = new FileStream(file.FullName, FileMode.Open, FileAccess.Read))
                {
                    string filePath = GetFilePath(file.FullName);

                    using (FileStream fsOut = new FileStream(filePath, FileMode.Create, FileAccess.Write))
                    {
                        fsOut.SetLength(fsIn.Length);
                       // fsIn.CopyTo(fsOut);

                        while ((bytesRead = fsIn.Read(bytes, 0, bufferSize)) > 0)
                        {
                            fsOut.Write(bytes, 0, bytesRead);
                        }
                    }
                }
            }

        }

@Abt Deine Klasse sieht wirklich interessant aus! Danke.

Könntest du mich noch kurz aufklären, warum das CopyTo Suboptimal ist?
Ich habe es mal Manuell versucht, aber es kam zu keiner änderung. Oder sollte es noch ganz anders gehen?

Edit: Sorry ich habe irgendwie deinen letzten Satz überlesen. War des setzen der Streamlänge den so gedacht? Und anstatt FileInfo , was sollte ich denn verwenden?

16.806 Beiträge seit 2008
vor 9 Jahren

Wenn Du QuickIO nicht verwenden willst: die Standardklassen in .NET erzeugen ungemein Overhead (GetFiles / EnumerateFiles). Das tut eben QuickIO im optimalsten Fall (EnumerateFilesPaths) nicht und lädt nur das aller aller nötigste; aber selbst die normale Methode EnumerateFiles ist um ein vielfaches schneller, da auf vieles, normal unnötiges verzichtet wird.
Du misst ja nicht nur die Übertragunszeit sondern auch die Suche der Dateien. Und die ist beim ersten mal halt langsamer. Beim zweiten Mal ist (ein Teil des) Suchergebnisses noch im Cache bzw. für einige Momente. Ist so nicht imemr 100%tig identisch.
Du misst also Mist.
Mess richtig. So ist das Äpfel und Birnen.

PS: QuickIO.NET ist "etwas" mehr als eine Klasse 😉

R
Rioma Themenstarter:in
228 Beiträge seit 2013
vor 9 Jahren

Doch ich werde wohl auf QuickIO umsteigen, aber es hat mich interessiert, was ich falsch gemacht habe. Danke für eure Hilfe 😃