Szybkie kopiowanie plików

2010-12-06 22:21
0

Witam,
Mój program ma za zadanie otworzyć plik, na kilkudziesięciu pierwszych bajtach coś pozmieniać, a później zapisać plik z pozmienianymi bajtami w nowe miejsce. Kod wygląda mniej więcej tak:

while i<100 do
  begin
        BlockRead(plik, bajt, SizeOf(bajt));
        {coś tu rób na bajcie bajt}
        BlockWrite(plikNowy, bajt, SizeOf(bajt)); //i go zapisz
  end;

  {przepisywanie reszty pliku}

  while not eof(plik) do
  begin
    BlockRead(plik, bajt, SizeOf(bajt));
    BlockWrite(plikNowy, bajt, SizeOf(bajt));
  end;

Problem w tym, że działa to bardzo wolno. Domyślam się że pierwszej pętli zbytnio nie mogę pozmieniać, ale czy dało by się skrócić czasowo działanie drugiej pętli? Próbując funkcją CopyFile plik kopiował się bardzo szybko.
Jeśli chodzi o to, jakie operacje wykonywane są w pierwszej pętli, to jest to kilka porównań, które na pewno nie mają większego wpływu na czas wykonywania programu.
Wina leży pewnie po stronie BlockRead() BlockWrite(), ale czy można jakoś inaczej dokopiować resztę pliku?
Z góry dzięki za odpowiedź
Pozdrawiam


Pozostało 580 znaków

2010-12-06 22:47
0

a nie mozesz po prostu skopiowac pliku w nowe miejsce a potem tylko wprowadzic zmian w tym nowym pliku?
po za tym... blockread odczytuje caly blok - im wiekszy tym szyciej sie plik skopiuje.

edytowany 1x, ostatnio: cimak, 2010-12-06 22:47

Pozostało 580 znaków

2010-12-06 22:48
0

Nie piszesz czy to koniecznie musi byc Pascal, bo jeżeli
może być i Delphi to skorzystaj z klas TMemoryStream
albo TFileStream i zapisuj już po modyfikacji pozostale
dane po kawałku, ale używając bufora. I może ten kod
coś Tobie pomoże, napiałem to kiedyś, bo zdarzalo się
że plik rar pobrany z rapidshare.com miał przed swoim
właściwym nagłowkiem jakieś "śmieci", przez co się w
ogóle nie dało rozpakowac, a naprawa WinRAR'em też
nie zawsze się udawała. Program robi mniej więcej to
co chcesz osiągnąć, zapisuje tylko właściwe dane, ale
z nagłowkiem "Rar!" i tym co po nim nastepuje. A oto
link: http://www.mediafire.com/?vhjuuuv4mc907q7 .

EDIT: cimak mnie ubiegł, ale nawet jeżeli chodzi o kod
w Pascalu, to generalnie zasada jest taka, jak napisał
cimak i należy dane zapisywać po większym "kawałku".


edytowany 1x, ostatnio: olesio, 2010-12-06 22:50

Pozostało 580 znaków

2010-12-07 00:01
0

nie memory stream tylko file stream


- Ciemna druga strona jest.
- Nie marudź Yoda, tylko jedz tego tosta.
Google NIE GRYZIE!
Pomogłem - kliknij

Pozostało 580 znaków

2010-12-07 00:16
0

hmm, napisałem tak:

while i<100 do
  begin
        BlockRead(plik, bajt, SizeOf(bajt));
        {coś tu rób na bajcie bajt}
        BlockWrite(plikNowy, bajt, SizeOf(bajt)); //i go zapisz
  end;

  {przepisywanie reszty pliku
}
x:=FileSize(plik)-FilePos(plik);
  SetLength(tab, x);

    BlockRead(plik, tab[0], x);
    BlockWrite(plikNowy, tab[0], x);

czy to poprawnie jeśli chodzi o bezpieczeństwo?


powinno być BlockRead(plik, tab, sizeof(tab)); - Azarien 2010-12-07 07:31

Pozostało 580 znaków

2010-12-07 10:37
0
Misiekd napisał(a)

nie memory stream tylko file stream

TFileStream w przeciwieństwie do TMemoryStream nie ma żadnej metody aby zapisać plik pod inną nazwą.
Na strumieniach powinno to wyglądać jakoś tak:

const
  ILE_BAJTOW = 100;
var
  ms: TMemoryStream;
  wsk: ^Byte;
  b: Byte;
  i, maxbytes: integer;
begin
  ms:= TMemoryStream.Create;
  try
  ms.LoadFromFile('e:\a.rar');
  try
  //ustawiamy wskaznik na miejsce od ktorego czytamy plik
  //w tym przypadku poczatek pliku
  ms.Seek(0, soFromBeginning);

  maxbytes:= ms.Size - ms.Position; //zabezpieczenie gdyby plik był mniejszy
  if ILE_BAJTOW < max then
    maxbytes:=  ILE_BAJTOW;

  wsk:= ms.Memory;
  for i:=1 to maxbytes do
  begin
    b:= wsk^; //pobierz bajt

    //tu jakies operacje na bajcie

    wsk^:= b; //zapisz bajt;
    Inc(wsk); //przejscie do kolejnego bajtu
  end;

  ms.SaveToFile('e:\b.rar');
  except
    on E: Exception do
      ShowMessage(E.Message);
  end;
  finally
  ms.Free;
  end;
end;

Tylko z doświadczenia wiem, że metoda ze strumieniami (zarówno TFileStream jak i TMemoryStream) nie nadaje się do dużych plików ponieważ jest bardzo zasobożerna dlatego bo ładuje cały plik do pamięci a to na słabszych kompach może trochę potrwać dlatego czasem teoretycznie wolniejsze wolniejsze rozwiązanie czyli tradycyjne CreateFile (ew. FileOpen z Delphi) może okazać się lepsze.


Nie odpowiadam na PW w sprawie pomocy programistycznej.
Pytania zadawaj na forum, bo:
od tego ono jest ;) | celowo nie zawracasz gitary | przeczyta to więcej osób a więc większe szanse że ktoś pomoże.
edytowany 1x, ostatnio: kAzek, 2010-12-07 10:40

Pozostało 580 znaków

2010-12-07 10:52
0

Ja pierdole [sciana]

A potem się ludzie dziwią, że najprostszy program potrzebuje procesora 4-rdzeniowego i min 2 GB ramu...

Otwierasz plik istniejący do odczytu, plik docelowy do zapisu, odczytujesz (ReadBuffer), zmieniasz w buforze co masz zmienić i zapisujesz (WriteBuffer)


- Ciemna druga strona jest.
- Nie marudź Yoda, tylko jedz tego tosta.
Google NIE GRYZIE!
Pomogłem - kliknij

Pozostało 580 znaków

2010-12-07 14:54
0

Taki mądry jesteś? To napisz to tak aby uzyskać ludzki czas czyli max kilka sekund. W komentarzach kodu masz wyniki ile u mnie zajmuje zwykłe skopiowanie pliku 1400MB.

function MSecToTime(mSec: Int64): TTime;
begin
  result := mSec / MSecsPerSec / SecsPerDay;
end;

procedure TForm1.btnTestClick(Sender: TObject);
const
  BUFSIZE = 50 * 1048576; //n * 1 MB
  //Przy pliku ~1400 MB
  //rozmiar buffera 1 MB 00:01:30, 10 MB 00:01:00, 50 MB 00:00:40
  //100 MB 00:00:40 (tak jak 50), 200 MB 00:00:52
var
  fs, fd: TFileStream;
  buf: PChar;
  count: Cardinal;
  sTime, eTime: Cardinal;
begin
  sTime:= GetTickCount;
  buf:= AllocMem(BUFSIZE);
  try
    fs:= TFileStream.Create('e:\_a.avi', fmOpenRead
      or fmShareDenyWrite);
    try
      fd:= TFileStream.Create('e:\_b.avi', fmCreate or fmOpenWrite
        or fmShareExclusive);
      try
      //fd.CopyFrom(fs, fs.Size); //zamiast petli ta metoda kopiuje 00:01:22
      repeat
        count:= fs.Read(buf^, BUFSIZE);
        fd.Write(buf^, count);
      until (count = 0);

      finally
      fd.Free;
      end;
    finally
    fs.Free;
    end;
  finally
  FreeMem(buf);
  end;
  eTime:= GetTickCount - sTime;
  ShowMessage(TimeToStr(MSecToTime(eTime)));
end; 

Na pewno maksymalny wynik można by było uzyskać jakoś obliczając wielkość buffera w zależności od rozmiaru pliku i ilości wolnej pamięci ram.


Nie odpowiadam na PW w sprawie pomocy programistycznej.
Pytania zadawaj na forum, bo:
od tego ono jest ;) | celowo nie zawracasz gitary | przeczyta to więcej osób a więc większe szanse że ktoś pomoże.
edytowany 2x, ostatnio: kAzek, 2010-12-07 14:58

Pozostało 580 znaków

2010-12-07 21:34
0

Ano taki mądry jestem. Co więcej potrafię myśleć i nawet podstawowe operacje matematyczne nie są mi obce.
Ja u siebie mam średnią prędkość podczas kopiowania plików na poziomie 65MB/s. Przy 1400MB pliku daje to 1400 / 65 = 21 sekund
plik 1 458 346 369 bytes windows kopiował ~28s

procedure TForm1.Button1Click(Sender: TObject);
var
  infs, outfs: TFileStream;
  s: TDateTime;
begin
  infs := TFileStream.Create('f:\a\in.mp4', fmOpenRead);
  outfs := TFileStream.Create('f:\a\out.mp4', fmCreate);
  try
    s := Now;
    outfs.CopyFrom(infs, infs.Size);
    ShowMessage(FormatDateTime('nn:ss:zzz', Now - s));
  finally
    outfs.Free;
    infs.Free;
  end;
end;

kopiowało ~27sek

procedure TForm1.Button2Click(Sender: TObject);
const
  bs = 1024*1024*10;
var
  infs, outfs: TFileStream;
  bufs: Integer;
  buf: PChar;
  s: TDateTime;
  count: Integer;
begin
  infs := TFileStream.Create('f:\a\in.mp4', fmOpenRead);
  outfs := TFileStream.Create('f:\a\out.mp4', fmCreate);
  GetMem(buf, bs);
  try
    count := infs.Size;

    s := Now;
    while count > 0 do
    begin
      if count > bs then bufs := bs else bufs := count;
      infs.ReadBuffer(buf^, bufs);
      outfs.WriteBuffer(buf^, bufs);
      Dec(count, bufs);
    end;

    ShowMessage(FormatDateTime('nn:ss:zzz', Now - s));
  finally
    outfs.Free;
    infs.Free;
    FreeMem(buf);
  end;
end;

kopiowało ~28 sek

procedure TForm1.Button3Click(Sender: TObject);
const
  BUFSIZE = 10 * 1048576; //n * 1 MB
  //Przy pliku ~1400 MB
  //rozmiar buffera 1 MB 00:01:30, 10 MB 00:01:00, 50 MB 00:00:40
  //100 MB 00:00:40 (tak jak 50), 200 MB 00:00:52
var
  fs, fd: TFileStream;
  buf: PChar;
  count: Cardinal;
  s: TDateTime;
begin

  buf:= AllocMem(BUFSIZE);
  try
    fs:= TFileStream.Create('f:\a\in.mp4', fmOpenRead
      or fmShareDenyWrite);
    try
      fd:= TFileStream.Create('f:\a\out.mp4', fmCreate or fmOpenWrite
        or fmShareExclusive);
      try
      s := Now;
      repeat
        count:= fs.Read(buf^, BUFSIZE);
        fd.Write(buf^, count);
      until (count = 0);
      ShowMessage(FormatDateTime('nn:ss:zzz', Now - s));
      finally
      fd.Free;
      end;
    finally
    fs.Free;
    end;
  finally
  FreeMem(buf);
  end;
end;

kopiowało ~28 sek

gdzie tu masz jakieś różnice, pomijając +-1 sek jeśli system coś akurat robił. To, że u ciebie coś chujowa działa nie oznacza wcale, że działa tak u wszystkich! Co więcej czy bufor będzie 1MB, 10MB czy 100MB to czasy się praktycznie nie różnią!


- Ciemna druga strona jest.
- Nie marudź Yoda, tylko jedz tego tosta.
Google NIE GRYZIE!
Pomogłem - kliknij
edytowany 1x, ostatnio: Misiekd, 2010-12-07 21:38

Pozostało 580 znaków

2010-12-08 10:35
0

No i po twojemu u mnie to samo. Ja wiem że u mnie muli (i sporej ilości użytkowników) i dlatego od początku pisałem o słabych kompach, ja mam tylko 1GB RAM i to pewnie dlatego takie zmuły.


Nie odpowiadam na PW w sprawie pomocy programistycznej.
Pytania zadawaj na forum, bo:
od tego ono jest ;) | celowo nie zawracasz gitary | przeczyta to więcej osób a więc większe szanse że ktoś pomoże.

Pozostało 580 znaków

Liczba odpowiedzi na stronę

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0