Jump to content
Mike Torrettinni

Micro optimization - effect of defined and not used local variables

Recommended Posts

During routine profiling I noticed a function that gets called 1mio+ times and I wanted to look into it, even though it's only 0.24% of total execution time. So, not a bottleneck, but still wanted to see is there anything need to be addressed.

 

Here is example that imitates a real function:

 

var flag: boolean = true;

function ProcessString(const aStr: string): string;
var s: string;
    i: integer;
    b: boolean;
begin
  if flag then
    Exit(aStr);
  
  // dummy code to use the local variables
  s := aStr;
  i := Length(s);
  b := i = 1;
  if b then
    Result := aStr
  else
    Result := s;
end;

As this function will always return string and exit, the same does my real function in 99.9% cases -  in only 0.01% cases executes the lower part of the function.

 

If I split functions to this:

function ProcessStringOLD(const aStr: string): string;
var s: string;
    i: integer;
    b: boolean;
begin
  s := aStr;
  i := Length(s);
  b := i = 1;
  if b then
    Result := aStr
  else
    Result := s;
end;

function ProcessStringNew(const aStr: string): string;
begin
  if flag then
    Exit(aStr)
  else
    Result := ProcessStringOLD(aStr);
end;

In this case, the new ProcessStringNew is 25% faster, because it never executes ProcessStringOLD - make sense.

 

But if I set the flag = false, then of course ProcessStringNew is slower vs original ProcessString, but only by 7%.

 

So new changes result in: 25% faster in 99.9% and 7% slower in 0.01%.

 

Does this micro optimization makes sense? I assume a few little changes like this across in multiple functions, and it could save some valuable execution time, >1%.

Share this post


Link to post

You could remove the flag, by the use of a PointerVariable as pointer to function.

In SetFlag, you can switch the PointerVariable to the desired, real function, so that the PointerVariable always willl be valid.

Then just calling the PointerVariable will not need any conditional check inside.

Edited by Rollo62
  • Thanks 1

Share this post


Link to post
21 minutes ago, Mike Torrettinni said:

In this case, the new ProcessStringNew is 25% faster, because it never executes ProcessStringOLD - make sense.

Wrong.

It's faster because you don't have a managed type as a local variable.

 

7 minutes ago, Rollo62 said:

a PointerVariable

a procedural type would be enough, no pointer needed

  • Like 1
  • Thanks 1

Share this post


Link to post
22 minutes ago, Mike Torrettinni said:

Does this micro optimization makes sense?

No I don't think so. You've just made your code more complex and harder to maintain and gained almost nothing.

  • Like 1
  • Thanks 1

Share this post


Link to post
19 minutes ago, Rollo62 said:

You could remove the flag, by the use of a PointerVariable as pointer to function.

In SetFlag, you can switch the PointerVariable to the desired, real function, so that the PointerVariable always willl be valid.

Then just calling the PointerVariable will not need any conditional check inside.

 

11 minutes ago, Attila Kovacs said:

a procedural type would be enough, no pointer needed

Are you guys suggesting that I set the function type at the time when I set the flag (SetFlag), something like:

 

flag := SetFlag; // flag is initialize on Project start
if flag then 
  ProcessString := ProcessStringWhenFlagIsTrue
else
  ProcessString := ProcessStringWhenFlagIsFalse;
  
// function type
type
TProcessStringType = function(const aStr: string): string;

ProcessString = TProcessStringType;
  
  
// usage of ProcessString function
  vStr := ProcessString('x');

?

  • Like 1

Share this post


Link to post
2 minutes ago, Attila Kovacs said:

Only if it make sense. (Not for every call)

We don't know how your code work.

OK, thanks, interesting suggestion. In this case this could work, because flag doesn't change while the project is running, set at the beginning for the whole runtime of project.

Share this post


Link to post
Guest
37 minutes ago, Mike Torrettinni said:

Does this micro optimization makes sense?

It is just ugly, and the benefit is limited per your timing.

 

There is a trick which is also ugly but more handsome !

 

The overhead of using string as local var in often called function might be small but can accumulate, so only if you want it, and for educational knowledge as trick and workaround, you can do the following:

 

function ProcessString(const aStr: string; var unused: string): string;
var
  i: integer;
  b: boolean;
begin
  if flag then
    Exit(aStr);

  // dummy code to use the local variables
  unused := aStr;
  i := Length(unused);
  b := i = 1;
  if b then
    Result := aStr
  else
    Result := unused;
end;

procedure TheOneThatCallProcessString;
var
  temp: string;
begin
  for ...
    ProcessString('12', temp)
end;

This should fix your gain from splitting the function in both flag cases, the idea is to remove local managed var from this function to a parent/caller local one, so no more try..finally then clearing, this only can be helpful when you have fairly small number of callers to that function.

Share this post


Link to post
9 minutes ago, Kas Ob. said:

This should fix your gain from splitting the function in both flag cases, the idea is to remove local managed var from this function to a parent/caller local one, so no more try..finally then clearing, this only can be helpful when you have fairly small number of callers to that function.

Aha, interesting. Now I understand what @Attila Kovacs meant in his comment:

 

37 minutes ago, Attila Kovacs said:

Wrong.

It's faster because you don't have a managed type as a local variable.

 

I was still focused on local unused variables vs no-local variables. Wasn't focused on string variable.

Share this post


Link to post
Guest

I forgot to mention when it is not ugly but appreciated for speed with minimal cosmetic effect.

When your function is a method in a class/record then move these local managed types vars to be private fields even when each one of them is not used outside one method, here you can recycle them, just remember they are initialized with some value from previous usage so be careful, like don't assume a string being used like that an empty one.

Share this post


Link to post
21 minutes ago, Kas Ob. said:

I forgot to mention when it is not ugly but appreciated for speed with minimal cosmetic effect.

When your function is a method in a class/record then move these local managed types vars to be private fields even when each one of them is not used outside one method, here you can recycle them, just remember they are initialized with some value from previous usage so be careful, like don't assume a string being used like that an empty one.

OK, thanks. Good suggestion on class encapsulation, if possible.

 

I'm not worried about ugly code, if I add comments and meaningful names, I'm OK with it, if it performs good. Of course such functions are rarely changed, so maintenance is also not really a deciding factor.

Share this post


Link to post
9 minutes ago, Mike Torrettinni said:

I'm not worried about ugly code, if I add comments and meaningful names, I'm OK with it, if it performs good. Of course such functions are rarely changed, so maintenance is also not really a deciding factor.

That's a very short sighted attitude. Technical debt accumulates and your memory will not get better with age, or so I've heard :classic_wink:

  • Like 1

Share this post


Link to post
3 minutes ago, Anders Melander said:

That's a very short sighted attitude. Technical debt accumulates and your memory will not get better with age, or so I've heard :classic_wink:

My memory is already bad, just ask my wife 🙂

  • Haha 1

Share this post


Link to post
Guest
Quote

"Code you wrote 6 months ago could as well have been written by someone else".

 

So true and you will notice it even more when you have to "jump" between completely different projects (like a huge server project, a VCL based client, a JS/TypeScript project and so on and so forth... after a while you start to REALLY appreciate OPs strictness (well, at least most times). No, milliseconds that evolve to minutes per moth, in these cases both we and our clients might well be better served by maintainable code!!

Edited by Guest
Added an "oxford comma"

Share this post


Link to post
11 minutes ago, Dany Marmur said:

 

So true and you will notice it even more when you have to "jump" between completely different projects (like a huge server project, a VCL based client, a JS/TypeScript project and so on and so forth... after a while you start to REALLY appreciate OPs strictness (well, at least most times). No, milliseconds that evolve to minutes per moth, in these cases both we and our clients might well be better served by maintainable code!!

Thank you, but I think we have different definition of maintainable code. Until a few years ago I didn't use classes, I had 1000s of global variables, long methods, so nothing refactored...

And now you guys describing splitting this function into 2 functions for performance (or whatever reason) as ugly, non-maintainable code... we are so far apart on this, we are not even in the same zip code (or same country, for European friends) 🙂

 

I do appreciate all suggestions and comments! It would be nice to know if someone had similar experience, did similar thing, and how such micro optimization came back causing issues later.

Share this post


Link to post
Guest
15 minutes ago, Mike Torrettinni said:

you guys describing splitting this function into 2 functions for performance (or whatever reason) as ugly, non-maintainable code...

I did NOT write that. Most definitely not. I did not get close to the details above.

15 minutes ago, Mike Torrettinni said:

It would be nice to know if someone had similar experience, did similar thing, and how such micro optimization came back causing issues later

Where to start... but it takes an effort and time to produce a viable MVP for such a case (even if publishing is "just" here).

When it hits you, you fix the problem, swear (on yourself) and move on.

I seldom feel the need to document my own f¤ck&ps 🙂 i just move on. Experience will not sit better if i over-use notepads.

 

Sorry for being a "best practices" - "everything else is folly" kind of poster. There is two of us here now. Sorry, truly.

Share this post


Link to post
1 minute ago, Dany Marmur said:

I did NOT write that. Most definitely not. I did not get close to the details above.

Where to start... but it takes an effort and time to produce a viable MVP for such a case (even if publishing is "just" here).

When it hits you, you fix the problem, swear (on yourself) and move on.

I seldom feel the need to document my own f¤ck&ps 🙂 i just move on. Experience will not sit better if i over-use notepads.

 

Sorry for being a "best practices" - "everything else is folly" kind of poster. There is two of us here now. Sorry, truly.

No need for sorry, at all, really. When I was compared to a boy from 'Boy who cried wolf' story, it took me a while to realize how different personalities we have on this forum and to not take it personally, sometimes is just the language or wording 🙂

 

9 minutes ago, Dany Marmur said:

There is two of us here now

The usual trio of one-liners is not complete, in this topic, yet 🙂

 

Back to topic:

I didn't implement this change, yet, but will have it on the list to evaluate in the future. The Hex2Bin topic, from the other day, gave me a boost to try and see if little tricks make big improvements, but I rarely touch functions below 1% of total runtime, this one is more exception than the rule.

 

Share this post


Link to post

And as for one-liners:
"Premature optimization is the root of all evil in programming. "
- C. A. R. Hoare - often misattributed to D. E. Knuth,who was himself quoting Hoare

Share this post


Link to post
21 minutes ago, Bill Meyer said:

And as for one-liners:
"Premature optimization is the root of all evil in programming. "
- C. A. R. Hoare - often misattributed to D. E. Knuth,who was himself quoting Hoare

Nice try! 🙂

Share this post


Link to post
1 minute ago, Mike Torrettinni said:

Nice try! 🙂

There is another one out there, which hasn't turned up yet. Something to the effect that 90% of programmers are wrong 90% of the  time about where to optimize. 

  • Like 1

Share this post


Link to post
5 minutes ago, Bill Meyer said:

There is another one out there, which hasn't turned up yet. Something to the effect that 90% of programmers are wrong 90% of the  time about where to optimize. 

I wasn't actually referring to these one-liners... 🙂

I would say I often try to optimize things that are 'fun', something I know I can do. If it's something very new to me, I hesitate but eventually go at it. So, 90% of the time is fun, I guess.

Share this post


Link to post

"Premature optimization" is a phrase used to describe a situation where a programmer lets performance considerations affect the design of a piece of code. This can result in a design that is not as clean as it could have been or code that is incorrect, because the code is complicated by the optimization and the programmer is distracted by optimizing.

 

"Premature optimization is the root of all evil." December 1974

 

Test-driven development is related to the test-first programming concepts of extreme programming, begun in 1999, but more recently has created more general interest in its own right.

 

Just move on.

 

 

  • Like 1

Share this post


Link to post

As a guideline: try to remove overhead from prologues and epilogues caused by variables of managed types (explicit or implicit) such as strings or interfaces that are only there for the uncommon path.

Another example was the error raising code in the hextobin thread that can be put into a subroutine that gets called only when the rase case of a invalid char occurs.

 

Eric Grange wrote a nice article about this some years ago that I like to recommend: https://www.delphitools.info/2009/05/06/code-optimization-go-for-the-jugular/

  • Like 4
  • Thanks 1

Share this post


Link to post
7 hours ago, Mike Torrettinni said:

During routine profiling I noticed a function that gets called 1mio+ times and I wanted to look into it, even though it's only 0.24% of total execution time. So, not a bottleneck, but still wanted to see is there anything need to be addressed.

 

Here is example that imitates a real function:

 


var flag: boolean = true;

function ProcessString(const aStr: string): string;
var s: string;
    i: integer;
    b: boolean;
begin
  if flag then
    Exit(aStr);
  
  // dummy code to use the local variables
  s := aStr;
  i := Length(s);
  b := i = 1;
  if b then
    Result := aStr
  else
    Result := s;
end;

As this function will always return string and exit, the same does my real function in 99.9% cases -  in only 0.01% cases executes the lower part of the function.

 

If I split functions to this:


function ProcessStringOLD(const aStr: string): string;
var s: string;
    i: integer;
    b: boolean;
begin
  s := aStr;
  i := Length(s);
  b := i = 1;
  if b then
    Result := aStr
  else
    Result := s;
end;

function ProcessStringNew(const aStr: string): string;
begin
  if flag then
    Exit(aStr)
  else
    Result := ProcessStringOLD(aStr);
end;

In this case, the new ProcessStringNew is 25% faster, because it never executes ProcessStringOLD - make sense.

 

But if I set the flag = false, then of course ProcessStringNew is slower vs original ProcessString, but only by 7%.

 

So new changes result in: 25% faster in 99.9% and 7% slower in 0.01%.

 

Does this micro optimization makes sense? I assume a few little changes like this across in multiple functions, and it could save some valuable execution time, >1%.

Is this code a bottleneck in your program? If not write it in the way that is easiest to read. How many times have I said that to you? Why would you choose to make your code hard to read for no benefit? 

Share this post


Link to post
28 minutes ago, David Heffernan said:

Is this code a bottleneck in your program? If not write it in the way that is easiest to read. How many times have I said that to you? Why would you choose to make your code hard to read for no benefit? 

Or as a friend advised me when I started learning to code, on my wood-burning CPU: First make it work, then worry about performance.

  • Like 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×