Pertanyaan File.Copy vs Manual FileStream.Write Untuk Menyalin File


Masalah saya dalam hal menyalin file kinerja. Kami memiliki sistem manajemen media yang membutuhkan banyak file bergerak di sekitar pada sistem file ke lokasi yang berbeda termasuk berbagi windows di jaringan yang sama, situs FTP, AmazonS3, dll. Ketika kita semua berada di satu jaringan windows, kita bisa lolos menggunakan System.IO.File.Copy (sumber, tujuan) untuk menyalin file. Karena berkali-kali semua yang kita miliki adalah input Stream (seperti MemoryStream), kami mencoba mengabstraksi operasi Copy untuk mengambil input Stream dan Output Stream tetapi kami melihat penurunan performa yang sangat besar. Di bawah ini adalah beberapa kode untuk menyalin file untuk digunakan sebagai titik diskusi.

public void Copy(System.IO.Stream inStream, string outputFilePath)
{
    int bufferSize = 1024 * 64;

    using (FileStream fileStream = new FileStream(outputFilePath, FileMode.OpenOrCreate, FileAccess.Write))
    {

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

        while ((bytesRead = inStream.Read(bytes, 0, bufferSize)) > 0)
        {
            fileStream.Write(bytes, 0, bytesRead);
            fileStream.Flush();
        }
    }
}

Apakah ada yang tahu mengapa ini melakukan jauh lebih lambat daripada File.Copy? Apakah ada yang bisa saya lakukan untuk meningkatkan kinerja? Apakah saya hanya harus menempatkan logika khusus untuk melihat apakah saya menyalin dari satu lokasi jendela ke yang lain - dalam hal ini saya hanya akan menggunakan File.Copy dan dalam kasus lain saya akan menggunakan aliran?

Tolong beri tahu saya apa yang Anda pikirkan dan apakah Anda memerlukan informasi tambahan. Saya telah mencoba berbagai ukuran buffer dan sepertinya ukuran buffer 64k adalah optimal untuk file "kecil" kami dan 256k + adalah ukuran buffer yang lebih baik untuk file "besar" kami - tetapi dalam kedua kasus itu melakukan jauh lebih buruk daripada File.Copy ( ). Terima kasih sebelumnya!


32
2017-08-07 20:42


asal


Jawaban:


File.Copy dibangun di sekitar CopyFile Fungsi Win32 dan fungsi ini membutuhkan banyak perhatian dari kru MS (ingat ini terkait thread Vista tentang kinerja copy lambat).

Beberapa petunjuk untuk meningkatkan kinerja metode Anda:

  1. Seperti banyak yang mengatakan sebelumnya, hapus metode Flush dari siklus Anda. Anda tidak membutuhkannya sama sekali.
  2. Meningkatkan buffer dapat membantu, tetapi hanya pada operasi file-ke-file, untuk share jaringan, atau server ftp, ini akan memperlambat. 60 * 1024 sangat ideal untuk berbagi jaringan, setidaknya sebelum vista. untuk ftp 32k akan cukup dalam banyak kasus.
  3. Bantu os dengan menyediakan strategi caching Anda (dalam kasus Anda membaca dan menulis sekuensial), gunakan overkonstruksi FileStream dengan FileOptions parameter (SequentalScan).
  4. Anda dapat mempercepat penyalinan dengan menggunakan pola asynchronous (terutama berguna untuk kasus jaringan ke file), tetapi tidak menggunakan utas untuk ini, sebagai gantinya gunakan tumpang tindih io (BeginRead, EndRead, beginWrite, EndWrite in .net), dan jangan lupa atur opsi Asynchronous di konstruktor FileStream (lihat FileOptions)

Contoh pola salin asynchronous:

int Readed = 0;
IAsyncResult ReadResult;
IAsyncResult WriteResult;

ReadResult = sourceStream.BeginRead(ActiveBuffer, 0, ActiveBuffer.Length, null, null);
do
{
    Readed = sourceStream.EndRead(ReadResult);

    WriteResult = destStream.BeginWrite(ActiveBuffer, 0, Readed, null, null);
    WriteBuffer = ActiveBuffer;

    if (Readed > 0)
    {
      ReadResult = sourceStream.BeginRead(BackBuffer, 0, BackBuffer.Length, null, null);
      BackBuffer = Interlocked.Exchange(ref ActiveBuffer, BackBuffer);
    }

    destStream.EndWrite(WriteResult);
  }
  while (Readed > 0);

23
2017-08-07 21:11



Membersihkan reflektor kita dapat melihat bahwa File.Copy benar-benar memanggil Win32 API:

if (!Win32Native.CopyFile(fullPathInternal, dst, !overwrite))

Yang memutuskan untuk

[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
internal static extern bool CopyFile(string src, string dst, bool failIfExists);

Dan di sini adalah dokumentasi untuk CopyFile


7
2017-08-07 21:24



Anda tidak akan pernah bisa mengalahkan sistem operasi dalam melakukan sesuatu yang sangat penting dengan kode Anda sendiri, bahkan jika Anda membuatnya dengan hati-hati dalam assembler.

Jika Anda perlu memastikan bahwa operasi Anda terjadi dengan kinerja terbaik DAN Anda ingin mencampur dan mencocokkan berbagai sumber, maka Anda perlu membuat jenis yang menggambarkan lokasi sumber daya. Anda kemudian membuat API yang memiliki fungsi seperti Copyyang mengambil dua jenis dan memeriksa deskripsi keduanya memilih mekanisme penyalinan berkinerja terbaik. Misalnya, setelah menetapkan bahwa kedua lokasi tersebut adalah lokasi file windows Anda akan memilih File.Copy ATAU jika sumbernya adalah file windows tetapi tujuannya adalah menjadi HTTP POST, ia menggunakan WebRequest.


6
2017-08-07 22:12



Tiga perubahan secara dramatis akan meningkatkan kinerja:

  1. Tingkatkan ukuran buffer Anda, cobalah 1 MB (eksperimen yang dilakukan dengan baik)
  2. Setelah Anda membuka fileStream, panggil fileStream.SetLength (inStream.Length) untuk mengalokasikan seluruh blok pada disk di depan (hanya berfungsi jika inStream is seekable)
  3. Hapus fileStream.Flush () - itu berlebihan dan mungkin memiliki dampak terbesar pada kinerja karena akan memblokir sampai flush selesai. Aliran akan dibuang pada pembuangan.

Ini terasa sekitar 3-4 kali lebih cepat dalam eksperimen yang saya coba:

   public static void Copy(System.IO.Stream inStream, string outputFilePath)
    {
        int bufferSize = 1024 * 1024;

        using (FileStream fileStream = new FileStream(outputFilePath, FileMode.OpenOrCreate, FileAccess.Write))
        {
            fileStream.SetLength(inStream.Length);
            int bytesRead = -1;
            byte[] bytes = new byte[bufferSize];

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

4
2017-08-07 20:58



Satu hal yang menonjol adalah Anda membaca sepotong, menulis potongan itu, membaca bongkahan lain, dan sebagainya.

Operasi streaming adalah kandidat yang bagus untuk multithreading. Tebakan saya adalah bahwa File.Copy mengimplementasikan multithreading.

Coba baca dalam satu utas dan tulis di utas lain. Anda perlu mengoordinasikan untaian sehingga utas penulisan tidak mulai menulis buffer sampai thread baca selesai mengisinya. Anda dapat menyelesaikan ini dengan memiliki dua buffer, satu yang sedang dibaca sementara yang lain sedang ditulis, dan sebuah bendera yang mengatakan buffer yang saat ini digunakan untuk tujuan itu.


1
2017-08-07 20:48



Cobalah untuk menghapus panggilan Flush, dan pindahkan ke luar loop.

Terkadang OS tahu yang terbaik ketika menyiram IO .. Ini memungkinkannya untuk lebih baik menggunakan buffer internalnya.


1
2017-08-07 20:54



Berikut jawaban yang serupa

Bagaimana cara menyalin konten dari satu aliran ke lainnya?

Masalah utama Anda adalah panggilan untuk Flush (), yang akan mengikat kinerja Anda ke kecepatan I / O.


1
2017-08-07 20:54



Mark Russinovich akan menjadi otoritas dalam hal ini.

Dia menulis padanya blog sebuah entri Di dalam Perbaikan Copy File Vista SP1 yang meringkas keadaan seni Windows melalui Vista SP1.

Tebakan semi-terdidik saya adalah bahwa File.Copy akan menjadi yang paling kuat atas sejumlah besar situasi. Tentu saja, itu tidak berarti di beberapa kasus sudut tertentu, kode Anda sendiri mungkin mengalahkannya ...


1
2017-08-07 21:23