Recent

Author Topic: Tips on comparing programming languages' performance  (Read 17276 times)

lucamar

  • Hero Member
  • *****
  • Posts: 4219
Re: Tips on comparing programming languages' performance
« Reply #30 on: April 22, 2020, 01:07:39 pm »
Breaking a lance in favor of nested procedures, here is this snippet taken from on of my small tools:

Code: Pascal  [Select][+][-]
  1. procedure TMainForm.ListKeyDown(Sender: TObject; var Key: Word;
  2.   Shift: TShiftState);
  3. { Handling of various key-combos when on the list.
  4.   Maybe should use actions instead.}
  5. var
  6.   Max, NewIndex, NewCurrent: Integer;
  7.   AList: TStringList;
  8.  
  9.   function NoShift  : Boolean; begin Result := Shift = [];      end;
  10.   function WithAlt  : Boolean; begin Result := Shift = [ssAlt]; end;
  11.   function WithCtrl : Boolean; begin Result := Shift = [ssCtrl]; end;
  12.   function WithShift: Boolean; begin Result := Shift = [ssShift]; end;
  13.  
  14.   function IsCopyCombo: Boolean;
  15.   begin
  16.     Result := (Key in [VK_C, VK_INSERT]) and WithCtrl;
  17.   end;
  18.  
  19.   function IsCutCombo: Boolean;
  20.   begin
  21.     Result := ((Key = VK_X) and WithCtrl) or
  22.               ((Key = VK_DELETE) and WithShift)
  23.   end;
  24.  
  25.   function IsAndCanPaste: Boolean;
  26.   begin
  27.     Result := Clipboard.HasFormat(CF_Text) and (
  28.               ((Key = VK_V) and WithCtrl) or
  29.               ((Key = VK_INSERT) and WithShift));
  30.   end;
  31.  
  32.   procedure DeleteSelection;
  33.   var i: Integer;
  34.   begin
  35.     { Adjusts ItemIndex to keep it on the same or next file.}
  36.     NewIndex := FileList.ItemIndex;
  37.     for i := FileList.ItemIndex downto 0 do begin
  38.       if (FileList.Selected[i]) and (NewIndex >= 0) then
  39.         Dec(NewIndex);
  40.     end;
  41.     {Adjust Current too, else list-play will skip}
  42.     NewCurrent := Current;
  43.     for i := Current downto 0 do begin
  44.       if (FileList.Selected[i]) and (NewCurrent >= 0) then
  45.         Dec(NewCurrent);
  46.     end;
  47.     { Now do the deed }
  48.     FileList.DeleteSelected;
  49.     FileList.AdjustScrollWidth;
  50.     Current := NewCurrent;
  51.     {Must be after: SetCurrent also sets ItemIndex}
  52.     FileList.ItemIndex := NewIndex;
  53.   end;
  54.  
  55.   procedure DoCopy;
  56.   var i: Integer;
  57.   begin
  58.     AList := TStringList.Create;
  59.     try
  60.       if FileList.SelCount = 0 then
  61.         AList.AddStrings(FileList.Items)
  62.       else begin
  63.         for i := 0 to FileList.Count-1 do
  64.           if FileList.Selected[i] then
  65.             AList.Add(
  66.               TrimAndExpandFilename(FileList.Items[i],GetCurrentDir));
  67.       end;
  68.       if AList.Count > 0 then
  69.         Clipboard.AsText := AList.Text;
  70.     finally
  71.       AList.Free;
  72.     end;
  73.   end;
  74.  
  75.   procedure DoCutDel;
  76.   begin
  77.     if FileList.SelCount > 0 then
  78.       DeleteSelection
  79.     else
  80.       FileList.Clear;
  81.     FileList.AdjustScrollWidth;
  82.   end;
  83.  
  84.   procedure DoPaste;
  85.   begin
  86.     NewIndex := FileList.ItemIndex;
  87.     AList := TStringList.Create;
  88.     try
  89.       AList.Text := Clipboard.AsText;
  90.       if ParseList(AList, @NameParsed) then begin
  91.         CleanList(AList);
  92.         FileList.InsertItems(NewIndex, AList);
  93.       end;
  94.     finally
  95.       AList.Free;
  96.     end;
  97.   end;
  98.  
  99.   procedure ZeroKey; inline;
  100.   begin
  101.     Key := 0;
  102.   end;
  103.  
  104. begin
  105.   Max := FileList.Count - 1;
  106.   case Key of
  107.   { Enter as alternative to "Play" button }
  108.   VK_RETURN: begin
  109.       if NoShift then ListDblClick(Sender);
  110.       ZeroKey;
  111.     end;
  112.   { List management }
  113.   {Alt+Down/Up do nothing unless there is at least one item below/above}
  114.   VK_DOWN: {Moves an item one position down the list}
  115.     if WithAlt then
  116.     if (Max > 0) and InRange(FileList.ItemIndex, 0, Max-1) then begin
  117.       FileList.Items.Exchange(FileList.ItemIndex, FileList.ItemIndex+1);
  118.       FileList.ItemIndex := FileList.ItemIndex + 1;
  119.       FListSaved := False;
  120.       ZeroKey;
  121.     end;
  122.   VK_UP  : {Moves an item one position up the list}
  123.     if WithAlt then
  124.     if (Max > 0) and InRange(FileList.ItemIndex, 1, Max) then begin
  125.       FileList.Items.Exchange(FileList.ItemIndex, FileList.ItemIndex-1);
  126.       FileList.ItemIndex := FileList.ItemIndex - 1;
  127.       FListSaved := False;
  128.       ZeroKey;
  129.     end;
  130.   VK_DELETE: {Delete/cut selected item(s)}
  131.     begin
  132.       if NoShift and (FileList.SelCount > 0) then begin
  133.         DeleteSelection;
  134.         FListSaved := False;
  135.       end else if WithShift then begin {Shift+Del = Cut}
  136.         DoCopy;
  137.         DoCutDel;
  138.         FListSaved := False;
  139.       end;
  140.       { List might be empty now, so }
  141.       UpdateButtons();
  142.       ZeroKey;
  143.     end;
  144.   { Clipboard ops. }
  145.   VK_INSERT, VK_V:
  146.     if IsCopyCombo then begin
  147.       DoCopy;
  148.       ZeroKey;
  149.     end else if IsAndCanPaste then begin
  150.       DoPaste;
  151.       FListSaved := False;
  152.       ZeroKey;
  153.       UpdateButtons();{< list might have been empty.}
  154.     end;
  155.   VK_C, VK_X: {Copy to clipboard; if "X", do cut op.}
  156.     if IsCopyCombo or IsCutCombo then begin
  157.       DoCopy;
  158.       if IsCutCombo then begin
  159.         DoCutDel;
  160.         UpdateButtons();{< because list might be empty now.}
  161.         FListSaved := False;
  162.       end;
  163.       ZeroKey;
  164.     end;
  165.   end;{case Key of}
  166. end;

While this is a rather extreme example and I should probably optimize it a little, the points are that the nested procs/functions allow me to code the key combo handlers in such a way that what they do can be inmediately understood, such as in:
Code: Pascal  [Select][+][-]
  1. case Key of
  2.   {...}
  3.   VK_C, VK_X: {Copy to clipboard; if "X", do cut op.}
  4.     if IsCopyCombo or IsCutCombo then begin
  5.       DoCopy;
  6.       if IsCutCombo then begin
  7.         DoCutDel;
  8.         UpdateButtons();{< because list might be empty now.}
  9.         FListSaved := False;
  10.       end;
  11.       ZeroKey;
  12.     end;
and allows the reuse of code which is common to various operations.

Of course, I could do the same with non-nested functions, but then I would have to pass all the state vars to them: the various indexes, key and shift state, etc without gaining nothing, since they are only used inside that event handler.

I tend to use nested functions rather heavily mainly for the motive outlined here in previous posts: to extract code which should by rights go in its own block (for example for easier debugging or adpatation) but is only ever used in an inner scope, and to simplify and make easier to follow the "main" procedure.

YMMV, though; everone is, of course, free to program as (s)he sees fit. ;)
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus/FPC 2.0.8/3.0.4 & 2.0.12/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11458
  • FPC developer.
Re: Tips on comparing programming languages' performance
« Reply #31 on: April 22, 2020, 01:13:50 pm »
Of course, I could do the same with non-nested functions, but then I would have to pass all the state vars to them: the various indexes, key and shift state, etc without gaining nothing, since they are only used inside that event handler.

Or unnecessarily add them to global class state.  Adding them as full method means that they are possible accessible from outside the unit (if only using the class cracker hack), hampering optimization.

I pretty much work the same, though I would declare some of the small functions inline.

howardpc

  • Hero Member
  • *****
  • Posts: 4144
Re: Tips on comparing programming languages' performance
« Reply #32 on: April 22, 2020, 01:14:44 pm »
It's much simpler and much easier when the function/procedure is nested because then you _know_ for a fact that it is used in _only_ that function/procedure.
There is really nothing to be gained from that knowledge, because it is non-essential how often a procedure is used, not counting recursion of course. This is primarily about code aesthetics and programmer preference. From a machine language point of view, higher language structuring is often being elevated to a form of art that overshoots its primary goal.

In addition, the more complex a compiler becomes, the harder it will be to maintain and extend it without introducing bugs.
You seem to have missed 440bx's point.
He was not referring to the frequency of called routines but their location.

The improvement in readability and debug-ability arises with nested routines (as compared to spaghetti alternatives) because the author (and reader) of the code have a clear, immediate visual sense of the flow of control, which becomes part of the nested structure designed into the paradigm.

440bx

  • Hero Member
  • *****
  • Posts: 4064
Re: Tips on comparing programming languages' performance
« Reply #33 on: April 22, 2020, 01:44:56 pm »
There is really nothing to be gained from that knowledge, because it is non-essential how often a procedure is used,
Nice attempt to avoid the real point.  Yes, how often a function/procedure is used is not really important as long as it's always used in the same place but, if the function/procedure is used in a lot of different places then the story is very different.

You cannot claim it is non-essential because when a change is made to that function/procedure which is called often and by many different functions/procedures, it is more than essential, it is _required_, to ascertain the change being made does not conflict with the code in _any_ of those functions/procedures.  That means _you_, not the compiler, has to determine all the places where the function/procedure is used.

If the function is nested, you know upfront, it is used there and nowhere else.

This is primarily about code aesthetics
It has nothing to do with aesthetics, it has everything to do with maintainability and reliability.

and programmer preference.
some programmers prefer good code... others prefer their preferences.

In addition, the more complex a compiler becomes, the harder it will be to maintain and extend it without introducing bugs.
That's true of any piece of software but, I for one, think that if a compiler is well designed from the start, extensions will be few and far between.  IOW, more "polish" than extension.

The compiler's job is to deal with complexity.  It isn't the programmer's job to make the compiler's job a cushy one.  The compiler works for the programmer and, it better work hard.



@Lucamar,

Nicely done. :)



You seem to have missed 440bx's point.
He was not referring to the frequency of called routines but their location.
I have to say, I harbor doubts that he really missed the point.  ;)

The improvement in readability and debug-ability arises with nested routines (as compared to spaghetti alternatives) because the author (and reader) of the code have a clear, immediate visual sense of the flow of control, which becomes part of the nested structure designed into the paradigm.
Exactly.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

munair

  • Hero Member
  • *****
  • Posts: 798
  • compiler developer @SharpBASIC
    • SharpBASIC
Re: Tips on comparing programming languages' performance
« Reply #34 on: April 22, 2020, 01:52:27 pm »
It's much simpler and much easier when the function/procedure is nested because then you _know_ for a fact that it is used in _only_ that function/procedure.
There is really nothing to be gained from that knowledge, because it is non-essential how often a procedure is used, not counting recursion of course. This is primarily about code aesthetics and programmer preference. From a machine language point of view, higher language structuring is often being elevated to a form of art that overshoots its primary goal.

In addition, the more complex a compiler becomes, the harder it will be to maintain and extend it without introducing bugs.
You seem to have missed 440bx's point.
He was not referring to the frequency of called routines but their location.

I don't think I did. He specifically stated *when* and where:
[..] _you_ have to keep track of when and where functions/procedures are called.

The improvement in readability and debug-ability arises with nested routines (as compared to spaghetti alternatives) because the author (and reader) of the code have a clear, immediate visual sense of the flow of control, which becomes part of the nested structure designed into the paradigm.

There are many ways to improve code readability. Having become used to nested procedures, I understand that Pascal programmers see the benefit and make frequent use of it. But from a compiler point of view, it is a complicated and IMO superfluous 'feature'. Without it programs can be just as easily developed without having to resort to 'spaghetti' code.
keep it simple

munair

  • Hero Member
  • *****
  • Posts: 798
  • compiler developer @SharpBASIC
    • SharpBASIC
Re: Tips on comparing programming languages' performance
« Reply #35 on: April 22, 2020, 02:19:07 pm »
You cannot claim it is non-essential because when a change is made to that function/procedure which is called often and by many different functions/procedures, it is more than essential, it is _required_, to ascertain the change being made does not conflict with the code in _any_ of those functions/procedures.  That means _you_, not the compiler, has to determine all the places where the function/procedure is used.

The job of a specific procedure should be well defined and well known within a program/project. Your argument is no excuse to resort to nested procedures because it easily leads to code duplication that - if the programmer tries a bit harder - can and *should* be avoided.

Programmers frequently face the situation that they need to assess what effect a specific change has on different parts of a project. Nested procedures doesn't take that job away. At best, it limits it somewhat.

Speaking of which, some languages, such as Go, chose not to implement classes and in particular class inheritance because it easily introduces errors elsewhere in the inheritance chain, which can be hard to trace. They made the wise decision to only implement objects, which is my preferred way to structure a program. If procedures are part of an object, it is crystal clear where they belong to and easy to understand what their function is.
keep it simple

munair

  • Hero Member
  • *****
  • Posts: 798
  • compiler developer @SharpBASIC
    • SharpBASIC
Re: Tips on comparing programming languages' performance
« Reply #36 on: April 22, 2020, 02:30:17 pm »
Of course, I could do the same with non-nested functions, but then I would have to pass all the state vars to them: the various indexes, key and shift state, etc without gaining nothing, since they are only used inside that event handler.

In that particular case I would create an object containing those members, which provides better structuring and code maintainability; at one point you could choose to use a now nested procedure at more than one place within the same object.
« Last Edit: April 22, 2020, 02:34:32 pm by Munair »
keep it simple

440bx

  • Hero Member
  • *****
  • Posts: 4064
Re: Tips on comparing programming languages' performance
« Reply #37 on: April 22, 2020, 02:34:35 pm »
The job of a specific procedure should be well defined and well known within a program/project.
I'm pleased we have something we can agree on.

Your argument is no excuse to resort to nested procedures because it easily leads to code duplication...
Programmers are supposed to pay attention to what they are doing and, if a particular sequence of code is used by more than one function/procedure then that function/procedure should definitely not be nested.

if the programmer tries a bit harder - can and *should* be avoided.
I agree.  The programmer should try "a bit harder" to write good code, which among other things, includes using the facilities the language provides to inform the programmer who may have to maintain the code that a particular sequence of code is used in one and only one place by nesting that sequence of code in the function/procedure that uses it.

Programmers frequently face the situation that they need to assess what effect a specific change has on different parts of a project. Nested procedures doesn't take that job away.
Yes, they do because, if the function/procedure that is being changed is nested, the programmer knows the owning function is the only function he/she has to concern him/herself with.

At best, it limits it somewhat.
On the contrary my dear Watson, as explained in the previous paragraph, it makes it easier and safer.

Speaking of which, some languages, such as Go, chose not to implement classes and in particular class inheritance because it easily introduces errors elsewhere in the inheritance chain, which can be hard to trace. They made the wise decision to only implement objects, which is my preferred way to structure a program. If procedures are part of an object, it is crystal clear where they belong to and easy to understand what their function is.
My opinion about OOP is probably well known by now.  I'm going to leave that one at that alone.

« Last Edit: April 22, 2020, 02:38:24 pm by 440bx »
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

munair

  • Hero Member
  • *****
  • Posts: 798
  • compiler developer @SharpBASIC
    • SharpBASIC
Re: Tips on comparing programming languages' performance
« Reply #38 on: April 22, 2020, 03:00:23 pm »
Programmers are supposed to pay attention to what they are doing and, if a particular sequence of code is used by more than one function/procedure then that function/procedure should definitely not be nested.

And *that* is my whole point with nested procedures (not counting compiler technicality). You don't see them unless you actually look at a procedure's code. It is simply not doable nor reasonable in large projects with hundreds of procedures; every programmer will at one point loose track of possible code duplication by nested procedures. If on the other hand procedures are part of a main module or object, they are instantly visible in the member list, giving the programmer an excellent overview of what is there (and ease of mind that he did not miss anything). I'm sure this seems trivial to some, but is there any programmer here who never pulled a nested procedure out and/or made it multi-functional? It should be part of the Power of 10 rules to at least limit the use of nested procedures if a language supports it IMO.  ;D

I think my point is clear, so I will leave it at that.
« Last Edit: April 22, 2020, 03:02:19 pm by Munair »
keep it simple

440bx

  • Hero Member
  • *****
  • Posts: 4064
Re: Tips on comparing programming languages' performance
« Reply #39 on: April 22, 2020, 03:20:04 pm »
And *that* is my whole point with nested procedures (not counting compiler technicality). You don't see them unless you actually look at a procedure's code.
And you shouldn't see them unless you are dealing with the owning function/procedure.

A nested function/procedure is conceptually the same as a local variable. If someone were to buy your argument against nested procedures then local variables would be "bad" and they should be made global. Your duplication argument is not very solid, a local variable is often, either a duplicate or a derivative of some other variable in the program.   The whole point of nested functions/procedures is to make it clear they are local, just like local variables but, instead of variables, it is code.  Local variables, local code = clearer, simpler and much easier to visualize their use than global anything.

It is simply not doable nor reasonable in large projects with hundreds of procedures; every programmer will at one point loose track of possible code duplication by nested procedures.
Let's say that does occasionally happen.  It is certainly a lot better to have that than countless functions/procedures scattered around in the name of "uniqueness" that the programmer is burdened with having to figure out where they are used.

If on the other hand procedures are part of a main module or object, they are instantly visible in the member list, giving the programmer an excellent overview of what is there (and ease of mind that he did not miss anything).
I don't buy that at all.  In a 50,000+ line program the only thing that is instantly visible is that there is a whole bunch of code to inspect.    I seriously doubt most programmers looking at thousands of lines of code are going to "instantly" see that two functions or procedures are duplicates of each other.

I'm sure this seems trivial to some, but is there any programmer here who never pulled a nested procedure out and/or made it multi-functional?
I've done that in the course of writing a program.  It's part of the programming process but, it doesn't mean that every piece of code that is used in only one function/procedure should be placed in the global scope, which would significantly increase the burden on the programmer to visualize the program's logic and dependencies among functions/procedures.

(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

lucamar

  • Hero Member
  • *****
  • Posts: 4219
Re: Tips on comparing programming languages' performance
« Reply #40 on: April 22, 2020, 03:29:31 pm »
Of course, I could do the same with non-nested functions, but then I would have to pass all the state vars to them: the various indexes, key and shift state, etc without gaining nothing, since they are only used inside that event handler.

In that particular case I would create an object containing those members, which provides better structuring and code maintainability; at one point you could choose to use a now nested procedure at more than one place within the same object.

You're probably right about that because I'm already using a subclassed TListBox (I already said it was an extreme example) so it would cost relatively little to include most of those nested functions as methods. This was a small "utility" program that, as often happens, grew out of any control ;)

Note, though, that that is true only in this particular example (and not even for all of it); in most other cases programing a new class and building a new object would complicate the program and reduce its fiability and maintainability.

I'll confess myself culprit of (maybe) overusing this kind of nesting: as soon as I've to add initialization code (say, in a relatively complex OnCreate or OnActivate handler) such as:
Code: Pascal  [Select][+][-]
  1. SomeObject := TSomeClass.Create;
  2. with SomeObject do begin
  3.   {... set lots of properties}
  4. end;
I almost automatically do it as a nested function, so that I can easily change it, if needed, without touching the main handler. That's probably overkill most of the times ... or you'd maybe call it an abomination? :D
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus/FPC 2.0.8/3.0.4 & 2.0.12/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

munair

  • Hero Member
  • *****
  • Posts: 798
  • compiler developer @SharpBASIC
    • SharpBASIC
Re: Tips on comparing programming languages' performance
« Reply #41 on: April 22, 2020, 03:36:03 pm »
A nested function/procedure is conceptually the same as a local variable. If someone were to buy your argument against nested procedures then local variables would be "bad" and they should be made global.

I don't think you believe that yourself. Going by your own words:

[..]if a nested function/procedure accesses a variable that is in one of its containing functions/procedures then the access to the variable is indirect.  The compiler has a pointer to the parent's function/procedure stack frame, it uses that pointer to get to the stack frame, then the variable's offset from the parent's stack frame to access the variable.  If a procedure/function is nested n-deep, that means the compiler has to "walk" through those stack frame pointers until it reaches the desired stack frame.

Variables and procedures are conceptually the same in that they are both identifiers, but the comparison ends there.
keep it simple

Thaddy

  • Hero Member
  • *****
  • Posts: 14382
  • Sensorship about opinions does not belong here.
Re: Tips on comparing programming languages' performance
« Reply #42 on: April 22, 2020, 03:38:50 pm »
 :D and correct.
440bx focusses on optimizations and not at any language
« Last Edit: April 22, 2020, 03:40:33 pm by Thaddy »
Object Pascal programmers should get rid of their "component fetish" especially with the non-visuals.

munair

  • Hero Member
  • *****
  • Posts: 798
  • compiler developer @SharpBASIC
    • SharpBASIC
Re: Tips on comparing programming languages' performance
« Reply #43 on: April 22, 2020, 03:40:04 pm »
I almost automatically do it as a nested function, so that I can easily change it, if needed, without touching the main handler. That's probably overkill most of the times

That's the danger with any 'advanced' feature, using it as a shortcut instead of doing it the proper way. The 'goto' statement was once accused of baring the same potential.
keep it simple

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11458
  • FPC developer.
Re: Tips on comparing programming languages' performance
« Reply #44 on: April 22, 2020, 03:43:11 pm »
I almost automatically do it as a nested function, so that I can easily change it, if needed, without touching the main handler. That's probably overkill most of the times

That's the danger with any 'advanced' feature, using it as a shortcut instead of doing it the proper way. The 'goto' statement was once accused of baring the same potential.

The point is there is no alternative more proper way. Adding heaps of parameters is not proper, specially if the nested procedure only uses some parameters in some cases, and adding global or class state is also not proper, if it is only used there. That would widen the visibility scope significantly, ugh.

 

TinyPortal © 2005-2018