I have a procedure my program calls tens of thousands of times that uses a generic structure like this:
procedure PrintIndiEntry(JumpID: string);
type
TPeopleIncluded = record
IndiPtr: pointer;
Relationship: string;
end;
var
PeopleIncluded: TList<TPeopleIncluded>;
PI: TPeopleIncluded;
begin { PrintIndiEntry }
PeopleIncluded := TList<TPeopleIncluded>.Create;
{ A loop here that determines a small number (up to 100) people to process }
while ... do begin
PI.IndiPtr := ...;
PI.Relationship := ...;
PeopleIncluded.Add(PI);
end;
DoSomeProcess(PeopleIncluded);
PeopleIncluded.Clear;
PeopleIncluded.Free;
end { PrintIndiEntry }
Alternatively, I can declare PeopleIncluded globally rather than locally as follows:
unit process;
interface
type
TPeopleIncluded = record
IndiPtr: pointer;
Relationship: string;
end;
var
PeopleIncluded: TList<TPeopleIncluded>;
PI: TPeopleIncluded;
procedure PrintIndiEntry(JumpID: string);
begin { PrintIndiEntry }
{ A loop here that determines a small number (up to 100) people to process }
while ... do begin
PI.IndiPtr := ...;
PI.Relationship := ...;
PeopleIncluded.Add(PI);
end;
DoSomeProcess(PeopleIncluded);
PeopleIncluded.Clear;
end { PrintIndiEntry }
procedure InitializeProcessing;
begin
PeopleIncluded := TList<TPeopleIncluded>.Create;
end;
procedure FinalizeProcessing;
begin
PeopleIncluded.Free;
end;
My question is whether in this situation it is better to declare PeopleIncluded globally rather than locally. I know the theory is to define locally whenever possible, but I would like to know if there are any issues to worry about with regards to doing tens of thousands of of "create"s and "free"s? Making them global will do only one create and one free.
What is the recommended method to use in this case?
If the recommended method is to still define it locally, then I'm wondering if there are any situations where it is better to define globally when defining locally is still an option.
About visibility of the process, what I would make is to create a class
holding all the data, one method for initalization and one destructor, then one method to call the process. Then optimize it for speed using a profiler. Never use globals, but encapsulate your process within small reusable and multi-thread-ready classes.
About process speed, in short: "Premature optimization is the root of all evil" — Donald Knuth, quoting C. A. R. Hoare. I'm sure that the bottleneck is not in the TList Create/Free
, but within your main process loop.
So use a profiler to see where the bottleneck is, before guessing what may be changed. See Profiler and Memory Analysis Tools for Delphi
You can pre-allocate variables and/or cache reusable data, use static arrays instead of a TList
(or allocate it and reuse it by using an external count
variable), and avoid allocating string
instances (and passing them without a const
parameter). But perhaps not a magic solution.
To make process faster, changing the algorithm is almost always better than some low-level implementation tricks like you tried. Use of pre-calculated look-up tables, memory pre-allocation, avoid creating temporary string
(e.g. use PosEx
instead of copy
of sub chains; or mix AnsiString
/UnicodeString
), avoid disk, API or DB calls, change memory structures, sort data then use binary search, use hashing or un-pipeline loops, make you process multi-thread and so on... it is impossible to guess what should be changed without having the whole source code of your process, and run a profiler on real data!
Your first code is better. It is good to hide any data structure from outside. The first code shows that PeopleIncluded is not used outside your procedure, so it is better to keep it inside. Exposing it outside (in the interface section) will make it visible from all other units that use this unit. By doing so, you risk it from being accessed and modified by other units. This can happened either intentionally or unintentionally by you or other people using your code, and can cause undesired results.
If you can't or still want to make it not local to your procedure, it is better to declare both the type and var in the implementation section, instead of interface section.
You don't have to worry about the performance. Operations in memory is fast and my suggestion is only to worry about it when performance bottleneck is REALLY happening. When it happens, you can profile your application to find the bottleneck, and optimize ONLY your code that cause bottleneck. Doing this will avoid unnecessary efforts to optimize codes that does not contribute (or minimum) to your application performance.
case # 3
unit process;
interface
type
TPeopleIncluded = record
IndiPtr: pointer;
Relationship: string;
end;
procedure PrintIndiEntry(JumpID: string);
var
PeopleIncluded: TList<TPeopleIncluded>;
PI: TPeopleIncluded;
begin { PrintIndiEntry }
{ A loop here that determines a small number (up to 100) people to process }
while ... do begin
PI.IndiPtr := ...;
PI.Relationship := ...;
PeopleIncluded.Add(PI);
end;
DoSomeProcess(PeopleIncluded);
PeopleIncluded.Clear;
end { PrintIndiEntry }
procedure InitializeProcessing;
Dosomeprocess((PeopleIncluded) has to know the record also. Declaring the same record on multiple places is kinda bad. That's why you want to make it available to Dosomeprocess. The variables however are better declared locally.
Creating a small class might be best though.