Declare Locally or Globally in Delphi?

2019-08-31 19:27发布

问题:

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.

回答1:

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!



回答2:

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.



回答3:

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.