I have 2 Tbytes var:
A: Tbytes;
B: Tbytes;
Now i want to swap then like this
tmp := A;
A := B;
B := tmp;
But I not sure if this is the most efficient way, especially with the copy-on-write (if it's the same as with String)
maybe something like this :
Tmp := Pointer(a);
pointer(a) := pointer(b);
pointer(b) := Tmp ;
There is no copy-on-write for dynamic arrays, but if there were, it would not matter, because nothing is written (to the contents of the arrays).
Your way is the most efficient: only references are copied, and a few reference counts are updated.
The way using pointers would be slightly more efficient (no refcounting), but also a bit more risky. You can do this because in the end, the reference counts of both arrays should be the same as they were before. If nothing can access the (local) references during the swap, it should not matter.
Update
And if you do what David recommended, i.e. put this code in a separate procedure, then it doesn't matter a lot if you use a local Tempvariable or an external one. But the swap using Pointer casts is 10x (ten times) as fast as the normal swap using TBytes!
See my comment to the other answer: it doesn't matter if you use an external or a local Temp variable: they are almost equally fast. I measured the one with a local Temp variable at an average of 6512 milliseconds, the one with the external Temp variable at 6729 milliseconds and the one using pointers at 589 milliseconds. I did several tests in different orders to eradicate any timing errors. There are timing differences when swapping empty (nil) arrays, but I assume these don't matter a lot
As it was already answered your code of swapping two TBytes between each other is the most efficient.
So my post here isn't an answer to your question but instead with it I'm trying just to warn you about how you can possibly screw up performance by using this code impropriety where performance loss will be actually caused by the code that is calling this code from your question.
Now based on the fact that you are even thinking about performance of such small piece of code I'm guessing you are probably planning on executing of this code in one large loop where slightest gain in performance of this code might have big consequences on overall performance of your application. If you would have called this code a few times I bet you wouldn't worry about its performance at all since it would be negligible to the performance of your entire application.
So if you follow the David's suggestion of putting this code into a procedure I'm guessing you might write something like this:
procedure SwapBytes(var A,B: TBytes);
var Temp: Tbytes;
begin
Temp := A;
A := B;
B := Temp;
end;
Nothing fancy. But the problem with this would be that every time you would call this procedure in your loop your application will have to initialize (allocate memory for it) that local variable upon entering the procedure and then finalize (release its memory) it on exiting the above procedure. Now why is this so bad? Because allocating od deallocating memory is much slower than actually writing to or reading from already allocated memory.
So how can you avoid this problem? You do so by initializing the Temp variable outside of your procedure and pass it to the procedure as additional parameter instead. Performance gain can be significant this way can be significant.
Here is my test example where I used both approaches and measure their performance.
//Basic procedure for swapping two TBytes values between each other
//It has local variable Temp of TBytes type which is automatically created when
//entering the procedure and released when exiting the procedure
procedure SwapBytesLocalTempVariable(var A,B: TBytes);
var Temp: TBytes;
begin
Temp := A;
A := B;
B := Temp;
end;
//Same as above bit this procedure does not contain any local variable so you
//need to pas the Temp variable as an additional input parameter
procedure SwapBytesExternalTempVariable(var A,B,Temp: TBytes);
begin
Temp := A;
A := B;
B := Temp;
end;
//Quick procedure for testing
procedure TForm1.Button1Click(Sender: TObject);
var A,B: TBytes;
I: Integer;
SW: TStopWatch;
Temp: TBytes;
begin
//Calling first procedure with local temp variable in a loop many times can be
//quite slow because your program needs to initialize and release that local
//variable in each loop cycle.
SW := TStopWatch.Create;
SW.Start;
for I := 0 to 100000000 do
begin
SwapBytesLocalTempVariable(A,B);
end;
SW.Stop;
Memo1.Lines.Add(Format('Swap bytes with local variable: %f',[SW.Elapsed.TotalMilliseconds]));
//Calling second procedure which does not have local temp variable and passing
//the temp variable as additional parameter is much quicker because this way
//the Temp variable isn't initialized and then released in each loop cycle but
//instead we created (initialized) it outside the loop (out OnClick method of
//TButton and is therefore being reused in each loop cycle.
SW := TStopWatch.Create;
SW.Start;
for I := 0 to 100000000 do
begin
SwapBytesExternalTempVariable(A,B,Temp);
end;
SW.Stop;
Memo1.Lines.Add(Format('Swap bytes with external variable: %f',[SW.Elapsed.TotalMilliseconds]));
end;
Now as you can see the performance difference of these two approaches is quite significant. During my testing calling first procedure with local variable took about 1800 millisecond (almost two seconds) while calling second procedure where I pas Temp variable as additional parameter to it only took about 800 milliseconds. Now that is one second performance gain between the two mentioned approaches.
Any way the general advice is to try and reduce the number of memory allocations as much as possible and try to reuse variables where possible.
Related
I use Delphi 10.1 Berlin in Windows 10.
I have two records of different sizes. I wrote code to loop through two TList<T> of these records to test elapsed times. Looping through the list of the larger record runs much slower.
Can anyone explain the reason, and provide a solution to make the loop run faster?
type
tTestRecord1 = record
Field1: array[0..4] of Integer;
Field2: array[0..4] of Extended;
Field3: string;
end;
tTestRecord2 = record
Field1: array[0..4999] of Integer;
Field2: array[0..4999] of Extended;
Field3: string;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
_List: TList<tTestRecord1>;
_Record: tTestRecord1;
_Time: TTime;
i: Integer;
begin
_List := TList<tTestRecord1>.Create;
for i := 0 to 4999 do
begin
_List.Add(_Record);
end;
_Time := Time;
for i := 0 to 4999 do
begin
if _List[i].Field3 = 'abcde' then
begin
Break;
end;
end;
Button1.Caption := FormatDateTime('s.zzz', Time - _Time); // 0.000
_List.Free;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
_List: TList<tTestRecord2>;
_Record: tTestRecord2;
_Time: TTime;
i: Integer;
begin
_List := TList<tTestRecord2>.Create;
for i := 0 to 4999 do
begin
_List.Add(_Record);
end;
_Time := Time;
for i := 0 to 4999 do
begin
if _List[i].Field3 = 'abcde' then
begin
Break;
end;
end;
Button2.Caption := FormatDateTime('s.zzz', Time - _Time); // 0.045
_List.Free;
end;
First of all, I want to consider the entire code, even the code that populates the list which I do realise you have not timed. Because the second record is larger in size more memory needs to be copied when you make an assignment of that record type. Further when you read from the list the larger record is less cache friendly than the smaller record which impacts performance. This latter effect is likely less significant than the former.
Related to this is that as you add items the list's internal array of records has to be resized. Sometimes the resizing leads to a reallocation that cannot be performed in-place. When that happens a new block of memory is allocated and the previous content is copied to this new block. That copy is clearly ore expensive for the larger record. You can mitigate this by allocating the array once up front if you know it's length. The list Capacity is the mechanism to use. Of course, not always will you know the length ahead of time.
Your program does very little beyond memory allocation and memory access. Hence the performance of these memory operations dominates.
Now, your timing is only of the code that reads from the lists. So the memory copy performance difference on population is not part of the benchmarking that you performed. Your timing differences are mainly down to excessive memory copy when reading, as I will explain below.
Consider this code:
if _List[i].Field3 = 'abcde' then
Because _List[i] is a record, a value type, the entire record is copied to an implicit hidden local variable. The code is actually equivalent to:
var
tmp: tTestRecord2;
...
tmp := _List[i]; // copy of entire record
if tmp.Field3 = 'abcde' then
There are a few ways to avoid this copy:
Change the underlying type to be a reference type. This changes the memory management requirements. And you may have good reason to want to use a value type.
Use a container class that can return the address of an item rather than a copy of an item.
Switch from TList<T> to dynamic array TArray<T>. That simple change will allow the compiler to access individual fields directly without copying entire records.
Use the TList<T>.List to obtain access to the list object's underlying array holding the data. That would have the same effect as the previous item.
Item 4 above is the simplest change you could make to see a large difference. You would replace
if _List[i].Field3 = 'abcde' then
with
if _List.List[i].Field3 = 'abcde' then
and that should yield a very significant change in performance.
Consider this program:
{$APPTYPE CONSOLE}
uses
System.Diagnostics,
System.Generics.Collections;
type
tTestRecord2 = record
Field1: array[0..4999] of Integer;
Field2: array[0..4999] of Extended;
Field3: string;
end;
procedure Main;
const
N = 100000;
var
i: Integer;
Stopwatch: TStopwatch;
List: TList<tTestRecord2>;
Rec: tTestRecord2;
begin
List := TList<tTestRecord2>.Create;
List.Capacity := N;
for i := 0 to N-1 do
begin
List.Add(Rec);
end;
Stopwatch := TStopwatch.StartNew;
for i := 0 to N-1 do
begin
if List[i].Field3 = 'abcde' then
begin
Break;
end;
end;
Writeln(Stopwatch.ElapsedMilliseconds);
end;
begin
Main;
Readln;
end.
I had to compile it for 64 bit to avoid an out of memory condition. The output on my machine is around 700. Change List[i].Field3 to List.List[i].Field3 and the output is in single figures. The timing is rather crude, but I think this demonstrates the point.
The issue of the large record not being cache friendly remains. That is more complicated to deal with and would require a detailed analysis of how the real world code operated on its data.
As an aside, if you care about performance then you won't use Extended. Because it has size 10, not a power of two, memory access is frequently mis-aligned. Use Double or Real which is an alias to Double.
I'm having a weird issue on using Delphi's TMemoryStream (or TFileStream for that matter). While reading a part of the stream into a byte array. Here's some code as an example.
procedure readfromstream();
var
ms : TMemoryStream;
buffer : array of byte;
recordSize : Integer;
begin
try
begin
ms := TMemeoryStream.Create();
ms.LoadFromFile(<some_path_to_a_binary_file>);
while ms.Position < ms.Size do
begin
buffer := nil;
SetLength(buffer, 4);
ms.ReadBuffer(buffer, 4);
move(buffer[0], recordSize, 4);
SetLength(buffer, recordSize);
ms.Position := ms.Position - 4; // Because I was having issues trying to read the rest of the record into a specific point in the buffer
FillChar(buffer, recordSize, ' ');
ms.ReadBuffer(buffer, recordSize); // Issue line ???
// Create the record from the buffer
end;
finally
begin
ms.Free();
end;
end;
procedure is called as,
// Some stuff happens before it
readfromstream();
// Some stuff happens after it
on debugging, I can see that it reads the stream into the buffer and the record is stored in memory appropriately. The procedure then exits normally and the debugger steps out of the procedure, but I end up straight back into the procedure and it repeats.
By forcing the procedure to exit prematurely I believe the issue involves the ms.ReadBuffer(buffer, recordSize); but I don't see why it would cause the issue.
This procedure is called only once. My test data has only one entry/data.
Any help would be greatly appreciated.
FillChar(buffer, recordSize, ' ');
Here you are overwriting the dynamic array variable, a pointer, rather than writing to the content of the array. That causes a memory corruption. Pretty much anything goes at that point.
The call to FillChar is needless anyway. You are going to read into the entire array anyway. Remove the call to FillChar.
For future reference, to do that call correctly, you write it like this:
FillChar(Pointer(buffer)^, ...);
or
FillChar(buffer[0], ...);
I prefer the former since the latter is subject to range errors when the array length is zero.
And then
ms.ReadBuffer(buffer, recordSize);
makes the exact same mistake, writing to the array variable rather than the array, and thus corrupting memory.
That should be
ms.ReadBuffer(Pointer(buffer)^, recordSize);
or
ms.ReadBuffer(buffer[0], recordSize);
The first 4 lines inside the loop are clumsy. Read directly into the variable:
ms.ReadBuffer(recordSize, SizeOf(recordSize));
I recommend that you perform some sanity checks on the value of recordSize that you read. For instance, any value less than 4 is clearly an error.
There's not a lot of point in moving the stream pointer back and reading again. You can copy recordSize into the first 4 bytes and the array and then read the rest.
Move(recordSize, buffer[0], SizeOf(recordSize));
ms.ReadBuffer(buffer[SizeOf(recordSize)], recordSize - SizeOf(recordSize));
A memory stream also seems wasteful. Why read the entire file into memory? That's going to place stress on your address space for large files. Use a buffered file stream.
Letting the caller allocate the stream would give more flexibility to the caller. They could then read from any type of stream and not be constrained to use a disk file.
Your try/finally block is wrong. You must acquire the resource immediately before the try. As you have it, an exception in the constructor leads to you calling Free on an uninitialized variable.
A better version might be:
procedure ReadFromStream(Stream: TStream);
var
buffer: TArray<byte>;
recordSize: Integer;
begin
while Stream.Position < Stream.Size do
begin
Stream.ReadBuffer(recordSize, SizeOf(recordSize));
if recordSize < SizeOf(recordSize) then
raise ...;
SetLength(buffer, recordSize);
Move(recordSize, buffer[0], SizeOf(recordSize));
if recordSize > SizeOf(recordSize) then
Stream.ReadBuffer(buffer[SizeOf(recordSize)],
recordSize - SizeOf(recordSize));
// process record
end;
end;
Sorry I can't add a comment, being a newb and all :) This reply is based on my understanding of Clayton's code in light of his comment with the recordSize values.
The reason David's code is looping is probably that you are interpreting each four byte "block" is a number. I'll assume your first Stream.Readbuffer is correct and that the first four bytes in the file is a length.
Now, unless I'm mistaken, I expect the recordSize will usually be greater than SizeOf(recordSize), which I think should be 4 (the size of an int). Nevertheless, this line is meaningless here.
The SetLength is correct, given my previous assumption.
Now your Move is where the story hits a snag: you haven't read anything since you read the length! So before the move, you should have:
bytesRead := Stream.Readbuffer(Pointer(buffer)^, recordSize);
Now you can check for EOF:
if bytesRead <> recordSize then
raise...;
...and move the buffer somewhere (if you wish):
Move(buffer[0], dest[0], recordSize);
And you are positioned to read the next recordSize value.
Repeat until EOF.
I have a procedure which calls several functions:
procedure TForm1.Button1Click(Sender: TObject);
var
rawData: TRawData;
rawInts: TRawInts;
processedData: TProcessedData;
begin
rawData := getRawData();
rawInts := getRawInts(rawData);
processedData := getProcessedData(rawInts);
end;
The data types are defined like this:
TRawData = array[0..131069] of Byte;
TRawInts = array[0..65534] of LongInt;
TProcessedData = array[0..65534] of Double;
running the program with just:
rawData := getRawData();
rawInts := getRawInts(rawData);
Works totally fine. However, when I try to run:
getProcessedData(rawInts)
I get a stackoverflow error. I don't see why this is. The function code for getProcessedData is very simple:
function getProcessedData(rawInts : TRawInts) : TProcessedData;
var
i: Integer;
tempData: TProcessedData;
scaleFactor: Double;
begin
scaleFactor := 0.01;
for i := 0 to 65534 do
tempData[i] := rawInts[i] * scaleFactor;
Result := tempData;
end;
Why is this causing an error ?
The default maximum stack size for a thread is 1 MB. The three local variables of Button1Click total 131,070 + 65,535 * 4 + 65,535 * 8 = 917,490 bytes. When you call getProcessedData, you pass the parameter by value, which means that the function makes a local copy of the parameter on the stack. That adds SizeOf(TRawInts) = 262,140 bytes to bring the stack to at least 1,179,630 bytes, or about 1.1 MB. There's your stack overflow.
You can reduce the stack use by passing the TRawInts array by reference instead. Then the function won't make its own copy. Zdravko's answer suggests using var, but since the function has no need to modify the passed-in array, you should use const instead.
function getProcessedData(const rawInts: TRawInts): TProcessedData;
Naively, we might expect the tempData and Result variables in getProcessedData to occupy additional stack space, but in reality, they probably won't. First, large return types typically result in the compiler changing the function signature, so it would act more like your function were declared with a var parameter instead of a return value:
procedure getProcessedData(rawInts: TRawInts; var Result: TProcessedData);
Then the call is transformed accordingly:
getProcessedData(rawInts, processedData);
Thus, Result doesn't take up any more stack space because it's really just an alias for the variable in the caller's frame.
Furthermore, sometimes the compiler recognizes that an assignment at the end of your function, like Result := tempData, means that tempData doesn't really need any space of its own. Instead, the compiler may treat your function as though you had been writing directly into Result all along:
begin
scaleFactor := 0.01;
for i := 0 to 65534 do
Result[i] := rawInts[i] * scaleFactor;
end;
However, it's best not to count on the compiler to make those sorts of memory-saving changes. Instead, it's better not to lean so heavily on the stack in the first place. To do that, you can use dynamic arrays. Those will move the large amounts of memory out of the stack and into the heap, which is the part of memory used for dynamic allocation. Start by changing the definitions of your array types:
type
TRawData = array of Byte;
TRawInts = array of Integer;
TProcessedData = array of Double;
Then, in your functions that return those types, use SetLength to assign the length of each array. For example, the function we've seen already might go like this:
function getProcessedData(const rawInts: TRawInts): TProcessedData;
var
i: Integer;
scaleFactor: Double;
begin
Assert(Length(rawInts) = 65535);
SetLength(Result, Length(rawInts));
scaleFactor := 0.01;
for i := 0 to High(rawInts) do
Result[i] := rawInts[i] * scaleFactor;
end;
These objects are all very large. And you appear to be allocating them as local variables. They will reside on the stack which has an fixed size, by default 1MB on Windows. You have allocated enough of these large objects in various parts of the call stack to exceed the 1MB limit. Hence a stack overflow.
Another problem in your code is the way you pass these objects as parameters. Passing large objects as value parameters results in copies being made. Copying an integer or two is nothing to worry about. Copying 65 thousand doubles is wasteful. It hurts performance. Don't do that. Pass references to large objects. Passing as const parameters achieves that.
The stack is well suited for small objects. It is not suited to these large objects. Allocate these objects on the heap. Use dynamic arrays: array of Integer, TArray<Integer> etc.
Do not increase the default stack size for your process. Especially in modern times of multi-core machines this is a recipe for out of memory errors.
Do not use magic constants. Use low() and high() to obtain array bounds.
Do pass input parameters by const. This allows the compiler to make optimisations that are significantly beneficial.
The key issue here is the size of your arrays.
If you use SizeOf you will see that they are probably larger than you think:
program Project3;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TRawData = array[0..131069] of Byte;
TRawInts = array[0..65534] of Longint;
TProcessedData = array[0..65534] of Double;
begin
try
writeln('TProcessedData:':20, SizeOf(TProcessedData):8);
writeln('TRawData:':20, SizeOf(TRawData):8);
writeln('TRawInts:':20, SizeOf(TRawInts):8);
writeln('Total:':20, SizeOf(TRawInts) + SizeOf(TProcessedData) + SizeOf(TRawData):8);
readln;
except
on E:Exception do
Writeln(E.Classname, ': ', E.Message);
end;
end.
Output:
TProcessedData: 524280
TRawData: 131070
TRawInts: 262140
Total: 917490
So most of the 1MB stack is consumed by the arrays. As some of the stack will already be allocated, you get a stack overflow.
This can be avoid by using dynamic arrays, which have their memory allocated from the heap.
TRawData = array of Byte;
TRawInts = array of Longint;
TProcessedData = array of Double;
...
SetLength(TProcessedData, 65535);
...
I have a function which creates Pointer to a data from a Stream.
function StreamToByteArray(Stream: TStream): Pointer;
var
ByteArr: array of Byte;
begin
if Assigned(Stream) then
begin
Stream.Position := 0;
SetLength(ByteArr, Stream.Size);
Stream.Read(ByteArr[0], Stream.Size);
end
else
SetLength(ByteArr, 0);
result := #ByteArr[0];
end;
How can I convert it back, from a Pointer to dynamic byte array and
then save the content to a stream. Or maybe it is possible to load stream directly from
a Pointer?
Thanks for help.
Ouch, this code is (unfortunately) very bad. Your function returns a pointer to the ByteArr array, but unfortunately that array runs out of scope when the function exists: you're essentially returning an Invalid Pointer! Even if the error doesn't immediately pop up, you've got a latent Access Violation in there.
Longer explanation
A Pointer is a dangerous structure: it doesn't contain data, it simply says where that data exists. Your example of an untyped Pointer is the most difficult kind of Pointer, it says nothing about the data that exists at the given address. It might point towards some bytes you read from a stream, might point to a string or even a picture of some sorts. You can't even know the amount of data that's at the given address.
The Pointer concept is closely related to the concept of allocating memory. We use many different techniques for allocation memory, using local variables, global variables, objects, dynamic arrays etc. In your sample function you're using a dynamic array, the array of Byte. The compiler does a very nice job of shielding you from the internals of allocating and reallocation memory, you can simply use SetLength() to say how big the array should be. Things work pretty well because the dynamic array is a managed data structure in Delphi: the compiler keeps track of how you're using the dynamic array and will free the associated memory as soon as the dynamic array is no longer needed. As far as the compiler is concerned, the associated memory is no longer required when your function exists.
When you're doing:
Result := #ByteArr[0];
You're essentially taking the address for the compiler-allocated memory block. Since you're using a very low level structure to do that (the Pointer), the compiler can't possibly keep track of your usage of the memory, so it will free the memory when the function exists. That leaves you with a Pointer to un-allocated memory.
How to properly return a Pointer from a function
First of all you should avoid Pointers if possible: they're low-level, the compiler can't help with type-safety or deallocation, they're simply too easy to get wrong. And when you do get Pointers wrong, the errors are usually Access Violations, and they're difficult to track.
That said, if you really want to return a pointer, you should return a pointer to explicitly allocated memory, so you know the compiler doesn't free it for you. When you do that, make sure the receiving code knows it's responsible for the memory (should free the memory when it's no longer needed). For example, your function could be re-written like this:
function StreamToByteArray(Stream: TStream): Pointer;
begin
if Assigned(Stream) then
begin
Result := AllocMem(Stream.Size);
Stream.Position := 0;
Stream.Read(Result^, Stream.Size);
end
else
Result := nil;
end;
How to change a Pointer back to array of byte or TStream
The answer is, there's no way to change it back. A pointer is just that, a pointer to some random data. An array of byte is more then the data it contains. A TStream is even more abstract: it's an interface that tells you how to retrieve data, it doesn't necessarily hold any data. For example, a TFileStream (and that is a TStream) doesn't hold any bytes of data: all the data is in the file on disk.
If you need a pointer to memory to pass to e.g. a function in a DLL, you should make that call while the buffer is still allocated. There are numerous ways to refactor the code below, but the same principle applies regardless of how your code ends up: You must not pass your pointer around after buffer has already been deallocated.
var
ByteArr: array of Byte;
begin
if Assigned(Stream) then
begin
Stream.Position := 0;
SetLength(ByteArr, Stream.Size);
Stream.Read(ByteArr[0], Stream.Size);
end
else
SetLength(ByteArr, 0);
Test(Pointer(ByteArray),Length(ByteArray));
end;
In your Test procedure you can do this:
procedure Test(aData: Pointer; aCount: Integer);
var
ByteArr: array of Byte;
begin
SetLength(ByteArr,aCount);
Move(aData^,Pointer(ByteArr)^,aCount);
Possible solution:
type
TBytes = array of byte;
function StreamToByteArray(Stream: TStream): TBytes;
begin
if Assigned(Stream) then
begin
Stream.Position := 0;
SetLength(result, Stream.Size);
Stream.Read(pointer(result)^, Stream.Size);
end
else
SetLength(result, 0);
end;
procedure Test;
var P: pointer;
begin
P := pointer(StreamToByteArray(aStream)); // returns an allocated TBytes
// ... use P
end; // here the hidden TBytes will be released
You can use pointer() around the result to get the memory location.
And your code won't leak any memory nor trigger any access violation, since an implicit try...finally block will be added by the compiler:
procedure Test;
var P: pointer;
tmp: TBytes; // created by the compiler
begin
tmp := StreamToByteArray(aStream)); // returns an allocated TBytes
try
P := pointer(tmp);
// ... use P
finally // here the hidden TBytes will be released
Finalize(tmp);
end;
end;
You can use RawByteString instead of TBytes if you wish.
Cosmin is right your are returing a pointer to an array that will become out of scope, the pointer will point to an area of memory that was on the stack and may get overwriten, It may appear as though the function works if you use the resust immediatly.
You need to pass the array to be filled into the function as well, or as I usually do (depending upon the data type) simple return a string and use that as a byte array (if you intend to move to a newer Delphi you need to be careful which string type your use).
Also dynamic arrays store the length and data type before the data (8 bytes of) and passing pointers to the 1st element losses the fact its a dynamic array and becomes just a memory buffer making freeing of the array dangerous.
To answer your question, a pointer (+ length) can be put back into a stream with the TStream.WriteBuffer. You may need to clear the stream first as this, as most stream write operations do, will append from the current stream position.
Hope that helps
i have a simple program with one procedure.
Procedure TForm1.btnKeywrdTransClick(Sender: TObject);
Var
i, ii : integer;
ch_word, zword, uy_word: widestring;
Begin
TntListBox1.items.LoadFromFile('d:\new folder\chh.txt'); //Chinese
TntListBox2.items.LoadFromFile('d:\new folder\uyy.txt'); //Uyword
TntListBox4.items.LoadFromFile(Edit3.text); //list of poi files
For I := 0 To TntListBox4.items.Count - 1 do
Begin
TntListBox3.items.LoadFromFile(TntListBox4.Items[i]);
zword := tntlistbox3.Items.Text; //Poi
For ii := 0 To TntListBox1.Items.count - 1 Do
Begin
loopz;
ch_word := tntlistbox1.Items[ii];
uy_word := ' ' + TntListBox2.items[ii] + ' ';
zword := wideFastReplace(zword, ch_word, uy_word, [rfReplaceAll]); //fastest, and better for large text
End;
TntListBox3.Items.text := zword;
TntListBox3.items.SaveToFile(TntListBox4.Items[i]);
end;
end;
now my new computer has 4cores, is making this program multithreading will make it run faster (if i uses 4 thread, a thread per core) ?
i have no experience with multithreading, i need your help
thanks.
ps : this is Loopz procedure
Procedure loopz;
Var
msg : tmsg;
Begin
While PeekMessage(Msg, 0, 0, 0, pm_Remove) Do
Begin
If Msg.Message = wm_Quit Then Halt(Msg.WParam);
TranslateMessage(Msg);
DispatchMessage(Msg);
End;
End;
update 1 :
from the answers, im gonna do
1 - use a profiler to find the most time consuming code
2 - try eliminate gui related things if possible
3 - use threads.
i'll report back . thanks all.
First of all make the algorithm as effective as it can be in it's current incarnation: Stop using TListBox to store your data!!! (sorry for shouting) Replace them with TStringList and you'll get a HUGE performance improvement. That's an required first step any way, because you can't use GUI objects from multiple threads (in fact you may only use them from the "main" thread). While you're changing TListBox to TStringList please give your variable meaningful names. I don't know how many people around here figured out that you're storing a list of file names in ListBox4, loading each file in ListBox3, using ListBox1 as a "keyword list" and ListBox2 as a "value list"... really, it's a big mess! Here's how it would look like with TStringList and proper names:
Procedure TForm1.btnKeywrdTransClick(Sender: TObject);
Var
i, ii : integer;
ch_word, zword, uy_word: widestring;
PoiFilesList:TStringList; // This is the list of files that need work
PoiFile:TStringList; // This is the file I'm working on right now
KeywordList, ValueList:TStringList; // I'll replace all keywords with corresponding values
Begin
PoiFilesList := TStringList.Create;
PoiFile := TStringList.Create;
KeywordList := TStringList.Create;
ValueList := TStringList.Create;
try
PoiFilesList.LoadFromFile(Edit3.text); //list of poi files
KeywordList.LoadFromFile('d:\new folder\chh.txt'); //Chinese
ValueList.LoadFromFile('d:\new folder\uyy.txt'); //Uyword
For I := 0 To PoiFilesList.Count - 1 do
Begin
PoiFile.LoadFromFile(PoiFilesList[i]);
zword := PoiFile.Text; //Poi
For ii := 0 To KeywordList.count - 1 Do
Begin
ch_word := KeywordList[ii];
uy_word := ' ' + ValueList[ii] + ' ';
zword := wideFastReplace(zword, ch_word, uy_word, [rfReplaceAll]);
End;
PoiFile.text := zword;
PoiFile.SaveToFile(PoiFilesList[i]);
end;
finally
PoiFilesList.Free;
PoiFile.Free;
KeywordList.Free;
ValueList.Free;
end;
end;
If you look at the code now, it's obvious what it does, and it's obvious how to multi-thread-it. You've got a text file containing names of files. You open up each one of those files and replace all Keywords with the corresponding Values. You save the file back to disk. It's easy! Load the KeywordList and ValueList to memory once, split the list of files into 4 smaller lists, start up 4 threads each working with it's own smaller files list.
I don't want to writhe the whole multi-threaded variant of the code because if I'll write it myself you might not understand how it works. Give it a chance and ask for help if you get into trouble.
First you should profile your code to see if reading from TntListBox is slowing you down or if it is WideFastReplace. But even before that, remove the 'loopz' call - it is slowing you the most! Why are you processing messages inside this loop at all?
To find the bottleneck, simply time your loop twice, but the second time comment out the WideFastReplace call. (And make sure you are timing only the loop, not the assignment to the TntListBox3 or saving into file or loading from file.)
When you will know what's slowing you down, report back ...
BTW, calling WideFastReplace in parallel would be almost impossible as it is always operating on the same source. I don't see any good way to parallelize your code.
A possible parallelization approach:
Split zword on an appropriate word delimiter (I'm assuming here you are only replacing words, not phrases) into N strings where N is the number of cores.
Do the full replacement (all search/replacement pairs) for each of those N strings in parallel. Of course, you would have to read search/replacement pairs first from the TntListBoxes into some internal structure (TStringList would suffice) and then use this structure in all N threads.
Concatenate those partial strings back together.
Of course, there's no point in doing that if WideFastReplace is not the time-consuming part of the code. Do the profiling first!
It looks like you are interfacing with GUI elements.
99% of all GUI code must be interfaced from one and only one thread.
If you refactor your code to perform the text replacements in a series of threads, dividing the text amongst them, and then have the GUI thread place it into your list box, you could improve performance.
Note that creating and synchronizing threads is not cheap. Unless you have thousands of entries to work on, you will likely slow down your program by adding threads.
You should gain quite a bit of improvement by using only one thread for the whole thing. With this you can omit the loopz call completely.
Be aware that you should replace the TntListboxes with local TWideStringList instances in your routine.
When you have gotten somewhat familiar with multithreading, you can go and split the work into multiple threads. This can be done for instance by splitting the list of poi files (listbox4) in multiple (say 3-4) lists, one for each thread.
Operations that could be run in parallel benefit from multitasking - those that have to be run one after another can't. The larger the operation, the larger the benefit. In your procedure you could parallelize the file loadings (although I guess they hold not so many elements) and you could parallelize the replace operation having multiple threads operating each on different list elements. How much faster it will run depends of the files size.
I guess you have more speed penality in using GUI elements to store data instead of working directly on in-memory structure, because you that means redrawing the controls often, which is an expensive operation.
Here is your answer
1. If you can, do not wait until user click to react to the action. Do it before hand like on formcreate by
Put them into wrapper object
Run it under a thread; once finish, mark it to be ready to be used
When user click on the action, check for marker. If it is not done
yet do a while loop and wait something like
btnKeywrdTrans.Enabled := False;
while not wrapper.done do
begin
Sleep(500);
Application.Processmessages;
end;
..... your further logic
btnKeywrdTrans.Enabled := True;
Replace it with TStringList or TWideStringList
Cheers
Pham