A simple PRNG using /dev/urandom

Discussion of chess software programming and technical issues.

Moderator: Ras

User avatar
sje
Posts: 4675
Joined: Mon Mar 13, 2006 7:43 pm

Re: Revised code

Post by sje »

matthewlai wrote:A little off topic here, but is there a reason for all those things to be pointers? It seems like they can just be regular members and there would be no need for heap allocation/deallocation (of course, in C++11 or Boost, smart pointers would be another safe option).

Also for "isthreadsafe", it may be faster to make it a template parameter instead, so compiler can optimize it out completely if you don't need it.
Pointers to member objects are quite useful for several reasons.

1. Source files which include header files may not need to also include header files for member objects as long as there is a forward declaration file included. In Symbolic, every class has a forward declaration as a class plus a declaration for a pointer to that class; this is all stuck in the header file Forward.h and every C++ file includes that. So, better encapsulation and less chance for unnecessary dependency.

2. When a thread is created, it usually gets only a moderately sized stack segment. If auto allocation objects are large. then there is the danger of undetected stack overflow when the thread runs. In fact, this would happen in the above code if more than a single PRNG instance (if it had a full megabyte buffer instead of a pointer to that buffer) were allocated on the thread stack. Using heap allocation avoids all such danger.

----

The PRNG class is too simple for template usage.
AlvaroBegue
Posts: 932
Joined: Tue Mar 09, 2010 3:46 pm
Location: New York
Full name: Álvaro Begué (RuyDos)

Re: Revised code

Post by AlvaroBegue »

The problem with having raw pointers as members is that your code is not exception safe (what happens if a constructor throws an exception?). To help with that you should probably use something like std::unique_ptr for individual objects and std::vector for arrays.
matthewlai
Posts: 793
Joined: Sun Aug 03, 2014 4:48 am
Location: London, UK

Re: Revised code

Post by matthewlai »

sje wrote: Pointers to member objects are quite useful for several reasons.

1. Source files which include header files may not need to also include header files for member objects as long as there is a forward declaration file included. In Symbolic, every class has a forward declaration as a class plus a declaration for a pointer to that class; this is all stuck in the header file Forward.h and every C++ file includes that. So, better encapsulation and less chance for unnecessary dependency.

2. When a thread is created, it usually gets only a moderately sized stack segment. If auto allocation objects are large. then there is the danger of undetected stack overflow when the thread runs. In fact, this would happen in the above code if more than a single PRNG instance (if it had a full megabyte buffer instead of a pointer to that buffer) were allocated on the thread stack. Using heap allocation avoids all such danger.

----
The buffer is the only thing that is really large. A std::vector can still be used for example, to automatically manage the buffer on the heap.

It's good to think about dependencies, but it significantly complicates the code in this case (especially if you want to do things like exceptions later), and can make it slower. Circular dependencies doesn't really happen that often when you have a well thought-out design.
The PRNG class is too simple for template usage.
What does complexity have to do with using templates or not? It just requires declaring the template parameter, and then you can use it just like a variable, except it won't actually take up space, and the compiler won't actually have to check it at run time.
Disclosure: I work for DeepMind on the AlphaZero project, but everything I say here is personal opinion and does not reflect the views of DeepMind / Alphabet.
User avatar
sje
Posts: 4675
Joined: Mon Mar 13, 2006 7:43 pm

Re: Revised code

Post by sje »

Many template classes from the STL introduce unneeded complexity plus inefficiency (like autosizing vectors). Symbolic uses templates (as inherited classes), but they're templates which I wrote myself.

Using pointers for potentially large objects works well because I (and the compiler) always know the size of a pointer regardless of what it points to. Therefore, thread stack space exhaustion becomes a non-issue.

If I can get by well without a language feature, then I don't use that feature. Less complexity = better code = easier debugging. In my book, that is. Your book may be different.
matthewlai
Posts: 793
Joined: Sun Aug 03, 2014 4:48 am
Location: London, UK

Re: Revised code

Post by matthewlai »

sje wrote:Many template classes from the STL introduce unneeded complexity plus inefficiency (like autosizing vectors). Symbolic uses templates (as inherited classes), but they're templates which I wrote myself.

Using pointers for potentially large objects works well because I (and the compiler) always know the size of a pointer regardless of what it points to. Therefore, thread stack space exhaustion becomes a non-issue.

If I can get by well without a language feature, then I don't use that feature. Less complexity = better code = easier debugging. In my book, that is. Your book may be different.
There is nothing inefficient about vectors if you just allocate all the memory you need in the beginning, like you are doing already -

Code: Select all

std::vector<u8> buffer(bufferSize);
The performance will be exactly the same as a raw pointer.

The size of a vector on the stack is also constant (and very small), regardless of how big the vector is.

In exchange, you get exception safety, no need to manage your own dynamic memory (with absolutely no performance hit), and less chance for bugs.

It depends on your definition of "complexity". I wouldn't say using a template parameter is more complex than adding a member variable, if you are familiar with both. It's about the same number of lines of code, and the code isn't any more complex. My book is different. My book is to use the most suitable language features for each task, even if I can do it without. In this case, a template parameter is more conceptually intuitive (because it essentially says you have 2 variants of the class, doing slightly different things), may be faster, and isn't more complex in code.

There are many things in C++ you can do without - smart pointers, standard library containers, exceptions, etc. Using them just makes the code simpler, more intuitive, and most importantly safer. I wouldn't say using a new language feature is always introducing complexity.
Disclosure: I work for DeepMind on the AlphaZero project, but everything I say here is personal opinion and does not reflect the views of DeepMind / Alphabet.
User avatar
sje
Posts: 4675
Joined: Mon Mar 13, 2006 7:43 pm

1e+9 random games

Post by sje »

Nineteen hours wall time for a billion random games:

Code: Select all

[] rg 1000000000
   checkmate 153044893 0.153045
  fiftymoves 193436632 0.193437
insufficient 567155101 0.567155
  repetition 25183422 0.0251834
   stalemate 61179952 0.06118
Average ply length: 334.358
Maximum ply length: 1076
PT: 3:22:05:01.228
WT: 18:52:34.097
The observed frequencies are a close match of those seen in earlier runs with good PRNGs in use.