2009-06-05 13 views
39

Muszę podzielić ogromny plik na wiele mniejszych plików. Każdy z plików docelowych jest zdefiniowany przez przesunięcie i długość jako liczbę bajtów. Używam następujący kod:Jak napisać superszybki kod strumieniowania plików w języku C#?

private void copy(string srcFile, string dstFile, int offset, int length) 
{ 
    BinaryReader reader = new BinaryReader(File.OpenRead(srcFile)); 
    reader.BaseStream.Seek(offset, SeekOrigin.Begin); 
    byte[] buffer = reader.ReadBytes(length); 

    BinaryWriter writer = new BinaryWriter(File.OpenWrite(dstFile)); 
    writer.Write(buffer); 
} 

Biorąc pod uwagę, że mam zadzwonić Ta funkcja około 100 tysięcy razy, to jest niezwykle powolny.

  1. Czy istnieje sposób, aby program Writer został podłączony bezpośrednio do czytnika? (To znaczy, bez rzeczywistego załadowania zawartości do bufora w pamięci).
+0

File.OpenRead i File.OpenWrite 100000 będzie powolny porządku ... –

+0

Czy podział pliku idealnie, czyli można odbudować duży plik przez prostu łącząc wszystkie małe pliki razem? Jeśli tak, to są oszczędności. Jeśli nie, zakresy małych plików pokrywają się? Czy są sortowane w kolejności przesunięcia? – jamie

Odpowiedz

45

Nie wierzę, że coś wewnątrz .NET, aby umożliwić kopiowanie fragmentu pliku bez buforowania go w pamięci. Jednak wydaje mi się, że i tak jest to nieefektywne, ponieważ musi otworzyć plik wejściowy i szukać wiele razy. Jeśli jesteś tylko dzielenie się plik, dlaczego nie otworzyć pliku wejściowego raz, a potem po prostu napisać coś takiego:

public static void CopySection(Stream input, string targetFile, int length) 
{ 
    byte[] buffer = new byte[8192]; 

    using (Stream output = File.OpenWrite(targetFile)) 
    { 
     int bytesRead = 1; 
     // This will finish silently if we couldn't read "length" bytes. 
     // An alternative would be to throw an exception 
     while (length > 0 && bytesRead > 0) 
     { 
      bytesRead = input.Read(buffer, 0, Math.Min(length, buffer.Length)); 
      output.Write(buffer, 0, bytesRead); 
      length -= bytesRead; 
     } 
    } 
} 

Ma niewielką nieefektywność w tworzeniu bufora na każdym wywołaniu - może chcesz stworzyć bufor raz i przekazać, że do metody, a także:

public static void CopySection(Stream input, string targetFile, 
           int length, byte[] buffer) 
{ 
    using (Stream output = File.OpenWrite(targetFile)) 
    { 
     int bytesRead = 1; 
     // This will finish silently if we couldn't read "length" bytes. 
     // An alternative would be to throw an exception 
     while (length > 0 && bytesRead > 0) 
     { 
      bytesRead = input.Read(buffer, 0, Math.Min(length, buffer.Length)); 
      output.Write(buffer, 0, bytesRead); 
      length -= bytesRead; 
     } 
    } 
} 

Zauważ, że to zamyka również strumień wyjściowy (dzięki użyciu instrukcji), co oryginalny kod nie.

Ważne jest to, że będzie to bardziej efektywnie wykorzystywać buforowanie plików systemu operacyjnego, ponieważ ponownie używasz tego samego strumienia wejściowego zamiast ponownego otwierania pliku na początku, a następnie wyszukiwania.

myślę będzie to znacznie szybciej, ale oczywiście trzeba spróbować, aby zobaczyć ...

Zakłada sąsiadujących fragmentów, oczywiście. Jeśli chcesz pominąć bity pliku, możesz to zrobić spoza metody. Ponadto, jeśli piszesz bardzo małe pliki, możesz chcieć zoptymalizować dla tej sytuacji - najprostszym sposobem na to byłoby prawdopodobnie wprowadzenie strumienia wejściowego BufferedStream.

+0

Wiem, że to jest dwuletni post, tylko zastanawiałem się ... czy to nadal jest najszybszy sposób? (Nic nowego w .Net, aby wiedzieć o?). Ponadto, byłoby szybciej wykonać 'Math.Min' przed wejściem do pętli? Albo jeszcze lepiej, aby usunąć parametr długości, ponieważ można go obliczyć za pomocą bufora? Przepraszam, że wybieram to i nekroję! Z góry dziękuję. – Smudge202

+2

@ Smudge202: Biorąc pod uwagę, że wykonuje to IO, wywołanie Math.Min z pewnością * nie * będzie istotne pod względem wydajności. Istotą zarówno parametru długości, jak i długości bufora jest umożliwienie ponownego wykorzystania potencjalnie zbyt dużego bufora. –

+0

Mam cię i dziękuję za skontaktowanie się ze mną. Nie chciałbym zaczynać nowego pytania, gdy istnieje odpowiednia wystarczająca odpowiedź tutaj, ale czy powiedziałbyś, że gdybyś chciał przeczytać pierwsze * x * bajtów dużej liczby plików (w celu złapania Metadane XMP z dużej liczby plików), powyższe podejście (z pewnymi zmianami) byłoby nadal zalecane? – Smudge202

6

Jak duży jest length? Lepiej możesz ponownie użyć bufora o stałym rozmiarze (umiarkowanie dużego, ale nieprzyzwoitego) i zapomnieć o BinaryReader ... po prostu użyj Stream.Read i Stream.Write.

(edit) coś takiego:

private static void copy(string srcFile, string dstFile, int offset, 
    int length, byte[] buffer) 
{ 
    using(Stream inStream = File.OpenRead(srcFile)) 
    using (Stream outStream = File.OpenWrite(dstFile)) 
    { 
     inStream.Seek(offset, SeekOrigin.Begin); 
     int bufferLength = buffer.Length, bytesRead; 
     while (length > bufferLength && 
      (bytesRead = inStream.Read(buffer, 0, bufferLength)) > 0) 
     { 
      outStream.Write(buffer, 0, bytesRead); 
      length -= bytesRead; 
     } 
     while (length > 0 && 
      (bytesRead = inStream.Read(buffer, 0, length)) > 0) 
     { 
      outStream.Write(buffer, 0, bytesRead); 
      length -= bytesRead; 
     } 
    }   
} 
+1

Jaki jest powód koloru na końcu? Zamknięcie powinno to zrobić. Ponadto, myślę, że chcesz odjąć od długości w pierwszej pętli :) –

+0

Dobre oczy Jon! Kolor był siłą przyzwyczajenia; od mnóstwa kodu, kiedy przekazuję strumienie zamiast otwierać/zamykać je w metodzie - wygodnie jest (jeśli pisze się nietrywialną ilość danych), aby wypróżnić je przed powrotem. –

3

Nie powinieneś ponownie otwierać pliku źródłowego za każdym razem, gdy robisz kopię, lepiej otwórz ją raz i przekaż wynikowy BinaryReader do funkcji kopiowania. Ponadto może to pomóc, jeśli zamówisz swoje poszukiwania, więc nie robisz dużych skoków wewnątrz pliku.

Jeśli długości nie są zbyt duże, można także spróbować kilku połączeń grupowych kopii grupując przesunięć, które są blisko siebie i czytając cały blok potrzeba do nich na przykład:

offset = 1234, length = 34 
offset = 1300, length = 40 
offset = 1350, length = 1000 

można pogrupować do jednego odczytu:

offset = 1234, length = 1074 

Potem trzeba tylko „szukać” w buforze i może napisać trzy nowe pliki stamtąd bez konieczności ponownego odczytania.

1

Pierwszą rzeczą, którą polecam jest wykonanie pomiarów. Gdzie tracisz czas? Czy to w czytaniu, czy w piśmie?

Ponad 100 000 wejść (suma czasów): Ile czasu zajmuje przydzielanie tablicy buforów? Ile czasu zajmuje otwieranie pliku do odczytu (czy za każdym razem jest to ten sam plik?) Ile czasu poświęca się na operacje odczytu i zapisu?

Jeśli nie dokonujesz żadnego przekształcenia pliku, potrzebujesz BinaryWriter, czy możesz użyć strumienia plików do zapisu? (Spróbuj go, czy można uzyskać identyczną moc? To zaoszczędzić czas?)

-1

(do wglądu.)

Prawdopodobnie najszybszy sposób to zrobić byłoby użyć pamięci mapowane pliki (tak głównie kopiowania pamięci , a system operacyjny obsługujący plik odczytuje/zapisuje poprzez zarządzanie stronicowaniem/pamięcią).

Pliki mapowane w pamięci są obsługiwane w kodzie zarządzanym w .NET 4.0.

Należy jednak pamiętać, że należy profilować i oczekiwać przełączenia na kod natywny w celu uzyskania maksymalnej wydajności.

+1

Pliki odwzorowane w pamięci są wyrównane na stronie, dzięki czemu są niedostępne. Problem tutaj jest bardziej prawdopodobny czas dostępu do dysku, a pliki mapowane w pamięci i tak by nie pomogły. System operacyjny będzie zarządzał buforowaniem plików niezależnie od tego, czy są one mapowane czy nie. – jamie

0

Nikt nie sugeruje wątkowania? Pisanie mniejszych plików wygląda jak przykładowy tekst, w którym wątki są przydatne. Ustaw kilka wątków, aby utworzyć mniejsze pliki. w ten sposób możesz tworzyć je wszystkie równolegle i nie musisz czekać na zakończenie każdej z nich. Zakładam, że tworzenie plików (obsługa dysków) zajmie WAY więcej czasu niż podział danych. i oczywiście powinieneś najpierw sprawdzić, czy podejście sekwencyjne nie jest odpowiednie.

+0

Nawlekanie może pomóc, ale jego wąskie gardło jest z pewnością na I/O - procesor prawdopodobnie spędza dużo czasu czekając na dysku. Nie oznacza to, że wątki nie będą miały znaczenia (na przykład, jeśli zapisy dotyczą różnych wrzecion, może on uzyskać lepszy wzrost wydajności niż gdyby był na jednym dysku) – JMarsch

3

Czy rozważałeś użycie CCR, ponieważ piszesz do oddzielnych plików, możesz robić wszystko równolegle (odczyt i zapis), a CCR bardzo ułatwia to zadanie.

Ten kod przesyła posty do portu CCR, co powoduje utworzenie wątku w celu wykonania kodu w metodzie Split. Powoduje to wielokrotne otwieranie pliku, ale eliminuje potrzebę synchronizacji. Możesz zwiększyć wydajność pamięci, ale będziesz musiał poświęcić szybkość.

+1

Pamiętaj o tym (lub dowolnym rozwiązanie do gwintowania) możesz trafić w fazę, w której maksymalnie zwiększysz swoje IO: osiągniesz najlepszą wydajność (np. przy próbie zapisu setek/tysięcy małych plików w tym samym czasie, kilku dużych plików itp.).Zawsze stwierdziłem, że jeśli uda mi się sprawnie wykonać jeden plik do odczytu/zapisu, niewiele mogę zrobić, aby poprawić to poprzez równoległe (Zgromadzenie może dużo pomóc, zrobić odczyt/zapis w asemblerze i może być spektakularne, aż do IO Ograniczenia, jednak pisanie może być trudne i musisz mieć pewność, że chcesz uzyskać bezpośredni dostęp do sprzętu na poziomie sprzętu lub systemu BIOS. – GMasucci

1

Korzystanie z FileStream + StreamWriter Wiem, że możliwe jest tworzenie ogromnych plików w krótkim czasie (mniej niż 1 min 30 sekund). Generuję trzy pliki w sumie 700+ megabajtów z jednego pliku przy użyciu tej techniki.

Głównym problemem używanego kodu jest otwieranie pliku za każdym razem. To tworzy narzut plików we/wy.

Jeśli znasz nazwy plików, które tworzysz z wyprzedzeniem, możesz wyodrębnić File.OpenWrite w osobnej metodzie; zwiększy to prędkość.Nie widząc kodu, który określa, w jaki sposób dzielisz pliki, nie sądzę, że możesz uzyskać znacznie szybciej.

21

Najszybszym sposobem na utworzenie pliku I/O z C# jest użycie funkcji Windows ReadFile i WriteFile. Napisałem klasę C#, która hermetyzuje tę funkcję, a także program testów porównawczych, który analizuje różne metody I/O, w tym BinaryReader i BinaryWriter. Zobacz mój blogu w:

http://designingefficientsoftware.wordpress.com/2011/03/03/efficient-file-io-from-csharp/

+0

Dzięki za szczegółowe informacje na blogu. Odznacz się plakietką "Nice Answer"! – ouflak

Powiązane problemy