Title: Shotgun Copy
Question: Is there a faster way to copy files across seperate physical drives?
e.g. HD-HD, CDROM-HD.
Using threads, we are able to issue a write at the same time as a read, effectively reading and writing files at the same time.
Answer:
unit Main;
{ ShotGun Copy
by Victor Kasenda
Free for any usage.
DESCRIPTION
ShotGun Copy will try to issue a read and write at the same time (like a
double barrel shotgun).
ShotGun Copy should work faster when copying files across seperate
physical drives (HD-HD, CDROM-HD), as one drive can be made to read and
the other drive writing data at the same time.
For Hard Disks, this is theoretically faster on drives with DMA controllers,
where the controller can be left alone to handle the read/write.
Across the same physical drive, then the Hard disk head will be made to seek more
and may have a detrimental effect on performance.
The copy engine is implemented in TMainForm.ShotGunCopy
2 standard components have been added to the form: the TButton and TProgressBar.
When the button is pressed, the copying is started.
The progress bar provides feedback
ALGORITHM
2 buffers- Buf1 and Buf2
Main Read Buf1
Main Wait for Buf2 to write finish (if Buf2 is still writing)
Thread Write Buf1
Main Read Buf2
Main Wait for Buf1 to write finish (if Buf1 is still writing)
Thread Write Buf2
Loop
Description:
Main Read - Main Thread does a read
Thread Write - A TWriteThread (see below)
is created to write the buffer in a tread.
VARIABLES
MainForm.DoingWrite
if true, the write thread is in the writing process.
TWriteThread will set it back to false after it has finished executing.
Buf1, Buf2
the buffers
BEFORE TESTING
Modify the IN file path and OUT file path in the procedure ShotgunCopy.
IDEAS
is this thing working as it is supposed to be (reading and writing at the
same time?)
does the call to Synchronize(SetDoingWrite); affect performance in
TWriteThread?
}
(**) interface (**)
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ComCtrls;
type
TMainForm = class(TForm)
Button1: TButton;
ProgressBar: TProgressBar;
procedure Button1Click(Sender: TObject);
private
procedure WriteThreadOnTerminate(Sender: TObject);
public
FromF, ToF: File;
DoingWrite: boolean;
procedure ShotgunCopy;
end;
var
MainForm: TMainForm;
(**) implementation (**)
{$R *.DFM}
const
BufSize = 512 * 1024; { 512 KByte buffer }
type
P64kBuf = ^T64kBuf;
T64kBuf = array[0..1024*64-1] of char;
PBigBuf = ^TBigBuf;
TBigBuf = array[0..MaxLongInt-1] of char;
{ Create suspended }
TWriteThread = class(TThread)
private
procedure SetDoingWrite;
public
BufToWrite: PBigBuf;
Count: integer; // how much to write?
procedure Execute; override;
end;
procedure TMainForm.ShotgunCopy;
var
WriteThread: TWriteThread;
Buf1, Buf2: PBigBuf;
NumRead, TotalRead, TotalSize: Integer;
P: TThreadPriority;
procedure UpdateProgressBar;
begin
ProgressBar.Position := (INT64(TotalRead) * 100) div INT64(TotalSize);
end;
begin
P := tpHighest; {Thread priority}
Screen.Cursor := crHourGlass;
WriteThread := nil;
GetMem(Buf1, BufSize+10);
GetMem(Buf2, BufSize+10);
{ Modify the following two lines to assign the IN file and OUT file }
AssignFile(FromF, 'e:\d2char.mpq'); {IN File}
AssignFile(ToF, 'd:\temp\out.CGTest'); {OUT File}
Reset(FromF, 1);
Rewrite(ToF, 1);
TotalRead := 0;
TotalSize := FileSize(FromF);
DoingWrite := false;
repeat
BlockRead(FromF, Buf1^, BufSize, NumRead);
if NumRead = 0 then break;
inc(TotalRead, NumRead);
UpdateProgressBar;
{Wait for Buf2 to write finish}
if DoingWrite then WriteThread.WaitFor;
FreeAndNil(WriteThread);
{Thread write Buf1}
WriteThread := TWriteThread.Create(true);
with WriteThread do
begin
BufToWrite := Buf1;
Count := NumRead;
OnTerminate := MainForm.WriteThreadOnTerminate;
Priority := P;
end;
WriteThread.Resume;
{Main Read Buf2}
BlockRead(FromF, Buf2^, BufSize, NumRead);
if NumRead = 0 then break;
inc(TotalRead, NumRead);
UpdateProgressBar;
{Wait for Buf2 to write finish}
if DoingWrite then WriteThread.WaitFor;
FreeAndNil(WriteThread);
{Thread Write Buf2}
WriteThread := TWriteThread.Create(true);
with WriteThread do
begin
BufToWrite := Buf2;
Count := NumRead;
OnTerminate := WriteThreadOnTerminate;
Priority := P;
Resume;
end;
until false;
if DoingWrite then WriteThread.WaitFor;
FreeAndNil(WriteThread);
Screen.Cursor := crDefault;
CloseFile(FromF);
CloseFile(ToF);
FreeMem(Buf1);
FreeMem(Buf2);
end;
procedure TMainForm.Button1Click(Sender: TObject);
begin
ShotgunCopy;
end;
{ TWriteThread }
procedure TWriteThread.Execute;
begin
// Write out the buffer
FreeOnTerminate := false;
Synchronize(SetDoingWrite);
BlockWrite(MainForm.ToF, BufToWrite^, Count);
end;
procedure TWriteThread.SetDoingWrite;
begin
MainForm.DoingWrite := true;
end;
procedure TMainForm.WriteThreadOnTerminate(Sender: TObject);
begin
DoingWrite := false;
end;
end.