Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parallel processing strings Delphi full available CPU usage

The goal is to achieve full usage of the available cores, in converting floats to strings in a single Delphi application. I think this problem applies to the general processing of string. Yet in my example I am specifically using the FloatToStr method.

What I am doing (I've kept this very simple so there is little ambiguity around the implementation):

  • Using Delphi XE6
  • Create thread objects which inherit from TThread, and start them.
  • In the thread execute procedure it will convert a large amount of doubles into strings via the FloatToStr method.
  • To simplify, these doubles are just the same constant, so there is no shared or global memory resource required by the threads.

Although multiple cores are used, the CPU usage % always will max out on the amount of a single core. I understand this is an established issue. So I have some specific questions.

In a simple way the same operation could be done by multiple app instances, and thereby achieve more full usage of the available CPU. Is it possible to do this effectively within the same executable ? I.e. assign threads different process ids on the OS level or some equivalent division recognised by the OS ? Or is this simply not possible in out of the box Delphi ?

On scope : I know there are different memory managers available & other groups have tried changing some of the lower level asm lock usage http://synopse.info/forum/viewtopic.php?id=57 But, I am asking this question in the scope of not doing things at such a low level.

Thanks


Hi J. My code is deliberately very simple :

TTaskThread = class(TThread)
public
  procedure Execute; override;
end;

procedure TTaskThread.Execute;
var
  i: integer;
begin
  Self.FreeOnTerminate := True;
  for i := 0 to 1000000000 do
    FloatToStr(i*1.31234);
end;

procedure TfrmMain.Button1Click(Sender: TObject);
var
  t1, t2, t3: TTaskThread;
begin
  t1 := TTaskThread.Create(True);
  t2 := TTaskThread.Create(True);
  t3 := TTaskThread.Create(True);
  t1.Start;
  t2.Start;
  t3.Start;
end;

This is a 'test code', where the CPU (via performance monitor) maxes out at 25% (I have 4 cores). If the FloatToStr line is swapped for a non string operation, e.g. Power(i, 2), then the performance monitor shows the expected 75% usage. (Yes there are better ways to measure this, but I think this is sufficient for the scope of this question)

I have explored this issue fairly thoroughly. The purpose of the question was to put forth the crux of the issue in a very simple form.

I am asking about limitations when using the FloatToStr method. And asking is there an implementation incarnation which will permit better usage of available cores.

Thanks.

like image 531
Alex Avatar asked Jan 22 '15 00:01

Alex


2 Answers

If you can't change the memory manager (MM) the only thing to do is to avoid using it where MM could be a bottleneck.

As for float to string conversion (Disclamer: I tested the code below with Delphi XE) instead of

procedure Test1;
var
  i: integer;
  S: string;

begin
  for i := 0 to 10 do begin
    S:= FloatToStr(i*1.31234);
    Writeln(S);
  end;
end;

you can use

procedure Test2;
var
  i: integer;
  S: string;
  Value: Extended;

begin
  SetLength(S, 64);
  for i := 0 to 10 do begin
    Value:= i*1.31234;
    FillChar(PChar(S)^, 64, 0);
    FloatToText(PChar(S), Value, fvExtended, ffGeneral, 15, 0);
    Writeln(S);
  end;
end;

which produce the same result but does not allocate memory inside the loop.

like image 39
kludg Avatar answered Nov 03 '22 06:11

kludg


I second what everyone else has said in the comments. It is one of the dirty little secrets of Delphi that the FastMM memory manager is not scalable.

Since memory managers can be replaced you can simply replace FastMM with a scalable memory manager. This is a rapidly changing field. New scalable memory managers pop up every few months. The problem is that it is hard to write a correct scalable memory manager. What are you prepared to trust? One thing that can be said in FastMM's favour is that it is robust.

Rather than replacing the memory manager, it is better to replace the need to replace the memory manager. Simply avoid heap allocation. Find a way to do your work with need for repeated calls to allocate dynamic memory. Even if you had a scalable heap manager, heap allocation would still cost.

Once you decide to avoid heap allocation the next decision is what to use instead of FloatToStr. In my experience the Delphi runtime library does not offer much support. For example, I recently discovered that there is no good way to convert an integer to text using a caller supplied buffer. So, you may need to roll your own conversion functions. As a simple first step to prove the point, try calling sprintf from msvcrt.dll. This will provide a proof of concept.

like image 163
David Heffernan Avatar answered Nov 03 '22 06:11

David Heffernan