Simplifying code II

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

User avatar
mvanthoor
Posts: 1784
Joined: Wed Jul 03, 2019 4:42 pm
Location: Netherlands
Full name: Marcel Vanthoor

Re: Simplifying code II

Post by mvanthoor »

R. Tomasi wrote: Mon Sep 20, 2021 11:03 pm Well, yes and no. Of course the size of a pointer is typically 8 or 4 bytes, depending on your hardware. But passing arguments to a function does usually not involve copying memory, since the first n arguments will be passed in registers. The exact value of n and which registers are used depend on the calling convention. Only when too many arguments are needed they will be passed via the stack. And I guess that's the catch: the registers used for passing parameters are in use and the compiler can't use them for local variables when it is optimizing the function. Most calling conventions are designed to balance between using registers for parameters and for local variables. But if you have less parameters, there is more room for the compiler to optimize. That last sentence is a hunch of mine - I'm not exactly sure how big the impact is. That's why I am asking if someone has experimented with that. The drawback of passing a pointer/reference to a struct, is that this struct will most certainly live on the stack, which means memory accesses will be involved and those may be slow. I would however suspect that the local stack is cached.
Even _if_ passing the pointer to a struct is a tiny bit slower, I much prefer to just collect all the needed information into one struct and pass a reference to it, than having to pass 12 variables into a function.
Author of Rustic, an engine written in Rust.
Releases | Code | Docs | Progress | CCRL
R. Tomasi
Posts: 307
Joined: Wed Sep 01, 2021 4:08 pm
Location: Germany
Full name: Roland Tomasi

Re: Simplifying code II

Post by R. Tomasi »

mvanthoor wrote: Mon Sep 20, 2021 11:13 pm
R. Tomasi wrote: Mon Sep 20, 2021 11:03 pm Well, yes and no. Of course the size of a pointer is typically 8 or 4 bytes, depending on your hardware. But passing arguments to a function does usually not involve copying memory, since the first n arguments will be passed in registers. The exact value of n and which registers are used depend on the calling convention. Only when too many arguments are needed they will be passed via the stack. And I guess that's the catch: the registers used for passing parameters are in use and the compiler can't use them for local variables when it is optimizing the function. Most calling conventions are designed to balance between using registers for parameters and for local variables. But if you have less parameters, there is more room for the compiler to optimize. That last sentence is a hunch of mine - I'm not exactly sure how big the impact is. That's why I am asking if someone has experimented with that. The drawback of passing a pointer/reference to a struct, is that this struct will most certainly live on the stack, which means memory accesses will be involved and those may be slow. I would however suspect that the local stack is cached.
Even _if_ passing the pointer to a struct is a tiny bit slower, I much prefer to just collect all the needed information into one struct and pass a reference to it, than having to pass 12 variables into a function.
I am very much with you on that. Which is slower/faster really depends on many factors: how many arguments? what calling convention? are there so many local variables in the function that register load is even an issue? How big is the struct? (if it fits in a register passing it by value might be faster), etc. So leaving the maintainability arguments aside (which are indeed strong arguments), you will really have to measure what works best in each individual case.
R. Tomasi
Posts: 307
Joined: Wed Sep 01, 2021 4:08 pm
Location: Germany
Full name: Roland Tomasi

Re: Simplifying code II

Post by R. Tomasi »

pedrojdm2021 wrote: Mon Sep 20, 2021 9:42 pm you can also use "m_variableName" for local variables inside your class
I consider that a good practice when writing classes, too. Generally I somewhat try to adhere to hungarian notation (https://en.wikipedia.org/wiki/Hungarian_notation), but I am not pedantic about it, either - class members being the only exception: they always get prefixed by m_.
R. Tomasi
Posts: 307
Joined: Wed Sep 01, 2021 4:08 pm
Location: Germany
Full name: Roland Tomasi

Re: Simplifying code II

Post by R. Tomasi »

I tried passing the search arguments in a struct in Pygmalion, to see if it pays off. To my big surprise it seems to hurt performance by roughly 10%.

This is the version with individual arguments:

Code: Select all

0:            0 -
  1.00 N   in   32.9 mcs =>   30.4 kN/s

1:   +0.0527344 - e3
  24.0 N   in   61.6 mcs =>    390 kN/s

2:            0 - e3 e6
  78.0 N   in   85.7 mcs =>    910 kN/s

3:     +0.03125 - e3 d5 Qh5
   565 N   in    401 mcs =>   1.41 MN/s

4:            0 - Nc3 d5 d4 Nc6
  2.90 kN  in   1.97 ms  =>   1.47 MN/s

5:   +0.0214844 - Nc3 d5 d4 Nc6 f3
  7.26 kN  in   4.28 ms  =>   1.70 MN/s

6:            0 - Nc3 d5 d4 Nc6 Nf3 Nf6
  56.1 kN  in   35.2 ms  =>   1.59 MN/s

7:   +0.0195312 - Nc3 d5 d4 Nc6 Nf3 Nf6 Qd3
   185 kN  in    130 ms  =>   1.42 MN/s

8:            0 - Nc3 d5 d4 Nc6 Nf3 Nf6 Qd3 Qd6
  1.57 MN  in    986 ms  =>   1.59 MN/s

9:   +0.0117188 - Nc3 d5 d4 Nc6 f3 Nf6 Qd3 e5 xe5 Nxe5
  4.57 MN  in   3.05 s   =>   1.50 MN/s

10:  +0.00195312 - Nc3 Nc6 e4 e5 Nf3 Nf6 d4 xd4 Nxd4 Bd6
  57.2 MN  in   38.6 s   =>   1.48 MN/s
This is the version where the arguments are passed through a struct:

Code: Select all

0:            0 -
  1.00 N   in   38.7 mcs =>   25.8 kN/s

1:   +0.0527344 - e3
  24.0 N   in   66.8 mcs =>    359 kN/s

2:            0 - e3 e6
  78.0 N   in    128 mcs =>    607 kN/s

3:     +0.03125 - e3 d5 Qh5
   565 N   in    511 mcs =>   1.11 MN/s

4:            0 - Nc3 d5 d4 Nc6
  2.90 kN  in   2.13 ms  =>   1.36 MN/s

5:   +0.0214844 - Nc3 d5 d4 Nc6 f3
  7.26 kN  in   5.47 ms  =>   1.33 MN/s

6:            0 - Nc3 d5 d4 Nc6 Nf3 Nf6
  56.1 kN  in   38.2 ms  =>   1.47 MN/s

7:   +0.0195312 - Nc3 d5 d4 Nc6 Nf3 Nf6 Qd3
   185 kN  in    128 ms  =>   1.45 MN/s

8:            0 - Nc3 d5 d4 Nc6 Nf3 Nf6 Qd3 Qd6
  1.57 MN  in   1.10 s   =>   1.43 MN/s

9:   +0.0117188 - Nc3 d5 d4 Nc6 f3 Nf6 Qd3 e5 xe5 Nxe5
  4.57 MN  in   3.27 s   =>   1.40 MN/s

10:  +0.00195312 - Nc3 Nc6 e4 e5 Nf3 Nf6 d4 xd4 Nxd4 Bd6
  57.2 MN  in   41.2 s   =>   1.39 MN/s
I had to pass the struct as rvalue reference and not as lvalue reference, since the compiler wouldn't allow me to instantiate the struct directly in the function call, elsewise. I'm not sure what exactly the compiler is doing under the hood, but I find these results rather discouraging. I will change the code to use lvalue references, just to be sure that isn't the reason, but I'm not holding my breath.
R. Tomasi
Posts: 307
Joined: Wed Sep 01, 2021 4:08 pm
Location: Germany
Full name: Roland Tomasi

Re: Simplifying code II

Post by R. Tomasi »

Here are the results for lvalue references:

Code: Select all

0:            0 -
  1.00 N   in   35.2 mcs =>   28.4 kN/s

1:   +0.0527344 - e3
  24.0 N   in   66.1 mcs =>    363 kN/s

2:            0 - e3 e6
  78.0 N   in    130 mcs =>    601 kN/s

3:     +0.03125 - e3 d5 Qh5
   565 N   in    400 mcs =>   1.41 MN/s

4:            0 - Nc3 d5 d4 Nc6
  2.90 kN  in   2.12 ms  =>   1.37 MN/s

5:   +0.0214844 - Nc3 d5 d4 Nc6 f3
  7.26 kN  in   6.01 ms  =>   1.21 MN/s

6:            0 - Nc3 d5 d4 Nc6 Nf3 Nf6
  56.1 kN  in   42.6 ms  =>   1.32 MN/s

7:   +0.0195312 - Nc3 d5 d4 Nc6 Nf3 Nf6 Qd3
   185 kN  in    137 ms  =>   1.35 MN/s

8:            0 - Nc3 d5 d4 Nc6 Nf3 Nf6 Qd3 Qd6
  1.57 MN  in   1.11 s   =>   1.41 MN/s

9:   +0.0117188 - Nc3 d5 d4 Nc6 f3 Nf6 Qd3 e5 xe5 Nxe5
  4.57 MN  in   3.24 s   =>   1.41 MN/s

10:  +0.00195312 - Nc3 Nc6 e4 e5 Nf3 Nf6 d4 xd4 Nxd4 Bd6
  57.2 MN  in   41.3 s   =>   1.39 MN/s
That seems to be equivalent, if not a tiny tad worse...

I'm all for readability and maintainability... but honestly, 10% is too much of a price to pay. I will stick with individual arguments.
Henk
Posts: 7216
Joined: Mon May 27, 2013 10:31 am

Re: Simplifying code II

Post by Henk »

Method should contain no more than one return statement.
Git is my only friend.
O wait it is control z (undo in visual studio)
User avatar
mvanthoor
Posts: 1784
Joined: Wed Jul 03, 2019 4:42 pm
Location: Netherlands
Full name: Marcel Vanthoor

Re: Simplifying code II

Post by mvanthoor »

Henk wrote: Wed Sep 22, 2021 4:47 pm Method should contain no more than one return statement.
That, IMHO, is an old view.

If you are running through a function (alpha/beta being one of them) which can hit a point where you MUST return (time up, for example), it's much cleaner and more understandable to just return from that point, as opposed to saving the return value somewhere else, finishing the function without actually doing anything, and then return at the end.

When you have code like "if stuff_happened() { return 0 }", then others just know: "If this happens, we're done", and you don't need to take "stuff_happens()" into account anymore. If you save "true" in a temporary variable, it will haunt you the rest of the function, and you'll have to check on it every time the function needs to do something.

I had a teacher in university (beginning of 2000's) who was quite old at that time already, and he held this view. He'd actually deduct points for each extra return statement. Sometimes our code got VERY messy, trying to keep track of all the temp variables: "if x == 5 && !a && !b && !c {...}"

When you hit a point where you need to return, then just be done with it and return. It will greatly unmessify your code.
Author of Rustic, an engine written in Rust.
Releases | Code | Docs | Progress | CCRL
R. Tomasi
Posts: 307
Joined: Wed Sep 01, 2021 4:08 pm
Location: Germany
Full name: Roland Tomasi

Re: Simplifying code II

Post by R. Tomasi »

mvanthoor wrote: Wed Sep 22, 2021 6:26 pm
Henk wrote: Wed Sep 22, 2021 4:47 pm Method should contain no more than one return statement.
That, IMHO, is an old view.

If you are running through a function (alpha/beta being one of them) which can hit a point where you MUST return (time up, for example), it's much cleaner and more understandable to just return from that point, as opposed to saving the return value somewhere else, finishing the function without actually doing anything, and then return at the end.

When you have code like "if stuff_happened() { return 0 }", then others just know: "If this happens, we're done", and you don't need to take "stuff_happens()" into account anymore. If you save "true" in a temporary variable, it will haunt you the rest of the function, and you'll have to check on it every time the function needs to do something.

I had a teacher in university (beginning of 2000's) who was quite old at that time already, and he held this view. He'd actually deduct points for each extra return statement. Sometimes our code got VERY messy, trying to keep track of all the temp variables: "if x == 5 && !a && !b && !c {...}"

When you hit a point where you need to return, then just be done with it and return. It will greatly unmessify your code.
I totally agree. That's an outdated practice imho, too. There might have been performance reasons for doing so back in the days, but I hardly believe that is an issue at all with modern compilers. And checking flags abundantly doesn't help with performance, either, I would argue. There was a limitation of that kind for constexpr functions in older C++ standards, but that's gone, too.

EDIT: I just googled what the original reason for this was. Seems like "one return statement" never was actually meant. It was all about "one return adress" - which is quite a different thing:
https://softwareengineering.stackexchan ... -come-from
Henk
Posts: 7216
Joined: Mon May 27, 2013 10:31 am

Re: Simplifying code II

Post by Henk »

Maybe not use 'else'. Makes refactoring easier.
Nested if statements terrible to read.
Henk
Posts: 7216
Joined: Mon May 27, 2013 10:31 am

Re: Simplifying code II

Post by Henk »

I have a method DoSearchMoves with 14 parameters. Awfull. Don't know what to do about it.
Put some in an extra object?