Jump to content
SirPope

Redeclaration of variables and functions in loops

Recommended Posts

I'm mainly making this post to see if I have the right ideal. I've seen some of the stuff while browsing the source / diff.

Here's an example of something I made up but I'm sure the developers have already seen something similar... its overly crappy fake code btw

function idkRectangle(arrayOfRects) {
   for(let rect of arrayOfRects) {
      const x0 = someMath on rect; //All of these are recreated each loop
      const y0 = someMath on rect;
      const x1 = someMath on rect;
      const y1 = someMath on rect;
      const v = new Vector(centerPoint...).rotate(someDegree/Rad);
      const newArrowFunction = (...) => {}; //extra bit I've seen elsewhere
      
      yata yata
   }
};

//Isn't this faster and less data heavy?
function idkRectangle(arrayOfRects) {
   let x0; //just created once
   let y0; 
   let x1;
   let y1; //These could be moved to one line to save space.
   let v = new Vector(0,0);
   const newArrowFunction = (...) => {};
   for(let rect of arrayOfRects) {
      x0 = someMath on rect;
      y0 = someMath on rect;
      x1 = someMath on rect;
      y1 = someMath on rect;
      v.set(centerPoint...).rotate(someDegree/Rad);
      newArrowFunction(...);
      
      yata yata
   }
};

My guess is yes but: linting... and readability.

The last one is kinda confusing. But it is faster and less data heavy right? I've seen this in area's where "speed is key". I got confused.

  • Like 2

Share this post


Link to post
Share on other sites

Well it could be or not. Depending on how this is optimised it will or will not reuse the same address space. Which means instead of allocating and deallocating 1000000 times a 32 bits int it will only do it once. If you do that with objects it can be faster really quickly because allocation is costly. What's missing there is probably a comment saying it was tested inlining variables and it was way faster without doing so.

This could also be an old C programmer habit. Some school teaches one to do it that way. The reason was at the beginning compilers couldn't use variables that were not declared at the top of the function.

 

 

  • Like 1

Share this post


Link to post
Share on other sites

I just did a simple experiment to see gcc behavior on this.

int main()
{
    for (int i = 0; i < 10; ++i)
    {
        int j = 100;
    }
    return 0;
}

and
 

int main()
{

    int i, j;
    for (i = 0; i < 10; ++i)
    {
        j = 100;
    }
    return 0;
}

Compiled to the exact same thing as expected. No difference at all. Compilers have become a *lot* smarter.

This was a very simple test ofcourse and results might vary if for example, j was something like i * i (it does not vary). Not sure, I am still pretty inexperienced with these stuff.

 

Edited by (-_-)

Share this post


Link to post
Share on other sites
1 hour ago, (-_-) said:

I just did a simple experiment to see gcc behavior on this.


int main()
{
    for (int i = 0; i < 10; ++i)
    {
        int j = 100;
    }
    return 0;
}

and
 


int main()
{

    int i, j;
    for (i = 0; i < 10; ++i)
    {
        j = 100;
    }
    return 0;
}

Compiled to the exact same thing as expected. No difference at all. Compilers have become a *lot* smarter.

This was a very simple test ofcourse and results might vary if for example, j was something like i * i (it does not vary). Not sure, I am still pretty inexperienced with these stuff.

 

Worth noting that inline declaration was not allowed before c99

The real question is the impact on JS Code.

I also wonder if minifying it and obfuscating it would make it faster. Less bytes of code to read so faster loading in memory.

  • Like 1

Share this post


Link to post
Share on other sites

Yes the JS Code is what I was wondering about.

I'm pretty sure doing the below is faster as well. The array doesn't need to ask for/look up the value, it already has a reference to the data in memory.

function some2dArrayFunc(arr2d) {
   const numberOfRows = arr2d.length; //avoid look up each condition check
   const numberOfColumns = arr2d[0].length; //same
   let row, j, i = 0; //avoid recreation each loop(?).
   for(; i < numberOfRows; i++) {
      row = arr2d[i]; //only look up and retrieve index i once.
      for(j = 0; j < numberOfColumns; j++) {
         //doStuff with row[j];
      }
   }
};

Something about not having to travel the prototype chain each time (or do error/sanity checking - if there is any internally). It somehow makes it a bit faster (but that was from some site a time ago). The speed up is probably small anyways but 0.0001 can equal a quadrillion if you add it together enough times. Poor point... So this really only applies to large amounts of operations that don't need to be 'felt' by the viewer. I don't really know if 0 ad does this, so this post could be pointless. Only thing that might be noticeably improved is map generation.

I can almost say that the arrow function creation in a loop is slow. Sure, they've been sped up but you are still forcing a lot of stuff to happen each time.

--

I thought minifying just lessened the load time, not necessarily the execution. If it just does that then, yeah it would technically be faster. I never looked it up or tried it.

Edited by SirPope
stuff about mini

Share this post


Link to post
Share on other sites
4 hours ago, stanislas69 said:

This could also be an old C programmer habit. Some school teaches one to do it that way. The reason was at the beginning compilers couldn't use variables that were not declared at the top of the function.

 

 

Some JavaScript interpreters 'lift' variables before the code is ran.

Found a link. Seems like it doesn't apply here really. let and const are ignored so uh... 'hoist waiting' isn't an issue really.

https://www.w3schools.com/js/js_hoisting.asp

-- wasn't the old site I was talking about but apparently it is faster to do the .length outside the loop. I found that out at least.

https://www.w3schools.com/js/js_performance.asp

Edited by SirPope
another link addition

Share this post


Link to post
Share on other sites

Since 0ad svn came with the source of spidermonkey, I decided to have some fun and got a debug build of it (which allows disassembling bytecode).

The following js code:

Spoiler

function doStuff()
{
    for (var i = 0; i < 10; ++i)
    {
        var j = i*i;
    }
}

function doStuff()
{
    var i;
    var j;
    for (i = 0; i < 10; ++i)
    {
        j = i*i;
    }
}

 

Produced a somewhat different bytecode. (complete output from the js shell)

Spoiler

flags:
loc     op
-----   --
main:
00000:  zero
00001:  setlocal 0
00005:  pop
00006:  goto 38 (+32)
00011:  loophead
00012:  getlocal 0
00016:  getlocal 0
00020:  mul
00021:  setlocal 1
00025:  pop
00026:  getlocal 0
00030:  pos
00031:  one
00032:  add
00033:  setlocal 0
00037:  pop
00038:  loopentry 129
00040:  getlocal 0
00044:  int8 10
00046:  lt
00047:  ifne 11 (-36)
00052:  retrval

Source notes:
 ofs line    pc  delta desc     args
---- ---- ----- ------ -------- ------
  0:    1     0 [   0] newline
  1:    2     0 [   0] newline
  2:    3     0 [   0] colspan 5
  4:    3     0 [   0] colspan 4
  6:    3     5 [   5] for      cond 32 update 20 tail 41
 10:    3    11 [   6] setline  lineno 5
 12:    5    11 [   0] colspan 1
 14:    5    12 [   1] setline  lineno 4
 16:    4    12 [   0] newline
 17:    5    12 [   0] colspan 5
 19:    5    26 [  14] xdelta  
 20:    5    26 [   0] setline  lineno 3
 22:    3    26 [   0] colspan 24
 24:    3    38 [  12] xdelta  
 25:    3    38 [   0] setline  lineno 5
 27:    5    38 [   0] setline  lineno 3
 29:    3    38 [   0] colspan 16
 31:    3    52 [  14] xdelta  
 32:    3    52 [   0] setline  lineno 5
 34:    5    52 [   0] colspan 6

Exception table:
kind      stack    start      end
 loop         0       11       52


 


flags:
loc     op
-----   --
main:
00000:  getlocal 0
00004:  pop
00005:  getlocal 1
00009:  pop
00010:  zero
00011:  setlocal 0
00015:  pop
00016:  goto 48 (+32)
00021:  loophead
00022:  getlocal 0
00026:  getlocal 0
00030:  mul
00031:  setlocal 1
00035:  pop
00036:  getlocal 0
00040:  pos
00041:  one
00042:  add
00043:  setlocal 0
00047:  pop
00048:  loopentry 129
00050:  getlocal 0
00054:  int8 10
00056:  lt
00057:  ifne 21 (-36)
00062:  retrval

Source notes:
 ofs line    pc  delta desc     args
---- ---- ----- ------ -------- ------
  0:    1     0 [   0] newline
  1:    2     0 [   0] newline
  2:    3     0 [   0] colspan 4
  4:    3     5 [   5] newline
  5:    4     5 [   0] colspan 4
  7:    4    10 [   5] newline
  8:    5    10 [   0] colspan 5
 10:    5    15 [   5] for      cond 32 update 20 tail 41
 14:    5    21 [   6] setline  lineno 7
 16:    7    21 [   0] colspan 1
 18:    7    22 [   1] setline  lineno 6
 20:    6    22 [   0] newline
 21:    7    22 [   0] colspan 1
 23:    7    36 [  14] xdelta  
 24:    7    36 [   0] setline  lineno 5
 26:    5    36 [   0] colspan 20
 28:    5    48 [  12] xdelta  
 29:    5    48 [   0] setline  lineno 7
 31:    7    48 [   0] setline  lineno 5
 33:    5    48 [   0] colspan 12
 35:    5    62 [  14] xdelta  
 36:    5    62 [   0] setline  lineno 7
 38:    7    62 [   0] colspan 9

Exception table:
kind      stack    start      end
 loop         0       21       62

 

Although none of these would lead to a significant performance gain or anything, it was interesting to see the difference.

Edited by (-_-)
  • Like 1

Share this post


Link to post
Share on other sites
1 hour ago, (-_-) said:

Since 0ad svn came with the source of spidermonkey, I decided to have some fun and got a debug build of it (which allows disassembling bytecode).

The following js code:

  Reveal hidden contents


function doStuff()
{
    for (var i = 0; i < 10; ++i)
    {
        var j = i*i;
    }
}


function doStuff()
{
    var i;
    var j;
    for (i = 0; i < 10; ++i)
    {
        j = i*i;
    }
}

 

Produced a somewhat different bytecode. (complete output from the js shell)

  Reveal hidden contents


flags:
loc     op
-----   --
main:
00000:  zero
00001:  setlocal 0
00005:  pop
00006:  goto 38 (+32)
00011:  loophead
00012:  getlocal 0
00016:  getlocal 0
00020:  mul
00021:  setlocal 1
00025:  pop
00026:  getlocal 0
00030:  pos
00031:  one
00032:  add
00033:  setlocal 0
00037:  pop
00038:  loopentry 129
00040:  getlocal 0
00044:  int8 10
00046:  lt
00047:  ifne 11 (-36)
00052:  retrval

Source notes:
 ofs line    pc  delta desc     args
---- ---- ----- ------ -------- ------
  0:    1     0 [   0] newline
  1:    2     0 [   0] newline
  2:    3     0 [   0] colspan 5
  4:    3     0 [   0] colspan 4
  6:    3     5 [   5] for      cond 32 update 20 tail 41
 10:    3    11 [   6] setline  lineno 5
 12:    5    11 [   0] colspan 1
 14:    5    12 [   1] setline  lineno 4
 16:    4    12 [   0] newline
 17:    5    12 [   0] colspan 5
 19:    5    26 [  14] xdelta  
 20:    5    26 [   0] setline  lineno 3
 22:    3    26 [   0] colspan 24
 24:    3    38 [  12] xdelta  
 25:    3    38 [   0] setline  lineno 5
 27:    5    38 [   0] setline  lineno 3
 29:    3    38 [   0] colspan 16
 31:    3    52 [  14] xdelta  
 32:    3    52 [   0] setline  lineno 5
 34:    5    52 [   0] colspan 6

Exception table:
kind      stack    start      end
 loop         0       11       52


 



flags:
loc     op
-----   --
main:
00000:  getlocal 0
00004:  pop
00005:  getlocal 1
00009:  pop
00010:  zero
00011:  setlocal 0
00015:  pop
00016:  goto 48 (+32)
00021:  loophead
00022:  getlocal 0
00026:  getlocal 0
00030:  mul
00031:  setlocal 1
00035:  pop
00036:  getlocal 0
00040:  pos
00041:  one
00042:  add
00043:  setlocal 0
00047:  pop
00048:  loopentry 129
00050:  getlocal 0
00054:  int8 10
00056:  lt
00057:  ifne 21 (-36)
00062:  retrval

Source notes:
 ofs line    pc  delta desc     args
---- ---- ----- ------ -------- ------
  0:    1     0 [   0] newline
  1:    2     0 [   0] newline
  2:    3     0 [   0] colspan 4
  4:    3     5 [   5] newline
  5:    4     5 [   0] colspan 4
  7:    4    10 [   5] newline
  8:    5    10 [   0] colspan 5
 10:    5    15 [   5] for      cond 32 update 20 tail 41
 14:    5    21 [   6] setline  lineno 7
 16:    7    21 [   0] colspan 1
 18:    7    22 [   1] setline  lineno 6
 20:    6    22 [   0] newline
 21:    7    22 [   0] colspan 1
 23:    7    36 [  14] xdelta  
 24:    7    36 [   0] setline  lineno 5
 26:    5    36 [   0] colspan 20
 28:    5    48 [  12] xdelta  
 29:    5    48 [   0] setline  lineno 7
 31:    7    48 [   0] setline  lineno 5
 33:    5    48 [   0] colspan 12
 35:    5    62 [  14] xdelta  
 36:    5    62 [   0] setline  lineno 7
 38:    7    62 [   0] colspan 9

Exception table:
kind      stack    start      end
 loop         0       21       62

 

Although none of these would lead to a significant performance gain or anything, it was interesting to see the difference.

How about making a mini profiler for JavaScript mag gen? Knowing functions times and number of calls would allow to know where are the bottle necks. Some functions are called millions times for each map gen.:banana:

Share this post


Link to post
Share on other sites
1 hour ago, nani said:

How about making a mini profiler for JavaScript mag gen? Knowing functions times and number of calls would allow to know where are the bottle necks. Some functions are called millions times for each map gen.:banana:

Briefly discussed with elexis and decided to have it some time ago.

25 minutes ago, stanislas69 said:

I thought there were timers in RMGen. @elexis

I guess it has it's limitations. Plus, it's not as precise as using JS_Now (GetMicroSeconds as exposed).

Share this post


Link to post
Share on other sites
On 9/27/2018 at 5:16 PM, (-_-) said:

Briefly discussed with elexis and decided to have it some time ago. 

You either refer to some ticket that floats around somewhere on trac space or to the plot example over there somewhere. It allows finding the greatest bottlenecks in a specific mapgen too, (besides the textual / numeric stdout log).

Share this post


Link to post
Share on other sites
10 minutes ago, elexis said:

You either refer to some ticket that floats around somewhere on trac space or to the plot example over there somewhere. It allows finding the greatest bottlenecks in a specific mapgen too, (besides the textual / numeric stdout log).

It's somewhere in the IRC logs. But you might very well be right.

Regarding the support for seeing function calls and things, all that's needed would be to enable tracelogging in SM. Which can be done by editing some environment variables.

More specifically (I think):

TLLOG=Default TLOPTIONS = EnableActiveThread, EnableOffThread

Can read the log and make a fancy graph after that.

Edit:: Apparently, someone else here did the research a long time ago. https://trac.wildfiregames.com/wiki/EngineProfiling#SpiderMonkeyTracelogger

(Trac page came up in a google search about Spidermonkey tracelogging)

Edited by (-_-)

Share this post


Link to post
Share on other sites

Off topic but on topic(?)

I thinking: Initializing variables with values (that they are going to hold) could speed up the code. var x = 0.0; Only using x to store decimals.

The JS Code is converted from untyped to typed values right? var x to float x and then to assembly.

I was looking at that link and poking around on google. I found a comment about SpiderMonkeies inner workings. (misspelled but i like the way it's pronounced: Spider Monk-Easy)

https://wiki.mozilla.org/IonMonkey/Overview

Quote

For example, consider an Ion-generated method having a 32-bit addition. If the addition operation overflows, or say it relies on an object access which happens to return a double, the compiled code is no longer valid. Guards check these assumptions, and when they fail, the method's execution resumes in the interpreter (and may later be recompiled).

Not sure what it means but interpreters are usually slow. I'm not tech savvy. I'm just good at misspelligns.

Share this post


Link to post
Share on other sites

Well technically what could speed up things is using integer arrays as opposed to object arrays. Unfortunately though I believe that needs at least SM45 support ;)

Share this post


Link to post
Share on other sites

I was a bit worried about that SM45 diff as I have an xp. It won't break the game for me. Greater than I think 52 will...

Finding the 'best way to JavaScript' is kinda pointless. One browser (or version) might handle something better but the other not so much.

I guess find places that would benefit having what is known to be faster and make a note of it. Attack types, maybe resources, and some other stuff comes to mind.

 

About the byte code: Related to the typedarrays

I think what's happening in the byte code is a bit of optimization.

for(var i = 0; yata yate; i++) // i is an integer. I don't need to guess.

Share this post


Link to post
Share on other sites
2 hours ago, stanislas69 said:

Well technically what could speed up things is using integer arrays as opposed to object arrays. Unfortunately though I believe that needs at least SM45 support ;)

The version 0AD uses support it.

  • Like 1

Share this post


Link to post
Share on other sites

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

×