So, RND is not thread safe then.

Windows specific questions.
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

So, RND is not thread safe then.

Post by deltarho[1859] »

The following assumes a multi-core system and uses '-gen gcc -Wc -O2'

Code1.bas simply calculates an estimate of the speed of FreeBASIC's default random number generator, the Mersenne Twister(MT).

The eagle-eyed amongst you will see that I am forcing the code to run on a single core which may seem a bit daft for just a primary thread but more of that shortly.

Running the code as is gives me: 87 mill/sec.

Nothing unusual there.

If we now remove the comment from the line 'hThread = ...' and swap the Prints to display two speeds instead of just one, I now get: 45 mill/sec 45 mill/sec.

Nothing unusual there. Although we are using a secondary thread of execution because of a single core the 'bandwidth' of MT gets halved.

If we now comment the three line single core code starting with 'Dim hProcess As Handle' I get this: 16 mill/sec 16 mill/sec.

Clearly, something decidedly untoward has occurred.

There is only one instance of RND and the two cores are 'fighting over it' - we have memory collisions. RND is not thread safe. Windows will do some very fancy caching but we have made its life difficult. The faster the generator the greater the likelihood of collisions and the greater the 'throttling'.

The bad news is that we have the same problem with all the generators that I have published here and any that you may have written.

The good news is that all mine can be made thread safe and yours as well. There is a simple solution: Code duplication.

Taking my PCG32, for example, all instances of PCG32 was firstly replaced with PCG32A and then with PCG32B. Code2.bas sees the primary thread using PCG32A and the secondary thread using PCG32B.

As is, Code2.bas has 'hThread = ...' commented and just one timing Print giving me: 208 mill/sec.

If we now remove the comment from the line 'hThread = ...' and swap the Prints to display two speeds instead of just one, I now get: 208 mill/sec 205 mill/sec.

Look ma, no collisions. <smile>

There is a bonus in using PCG - the state vector has two Ulongints: state and inc. If we ensure that inc is not the same for each generator not only do we get independent generators but different sequences as well.

If more than two threads require random numbers it may be worthwhile to examine the code duplication with a view to condensing to the original number of functions with some parameter passing but in the above example I don't think it worth the effort and, of course, any parameter passing will slow the generators.

So, how many applications do I have requiring random numbers in two or more threads?

I don't have any - but what has that got to do with it. <laugh>

Code1.bas

Code: Select all

#include once "windows.bi"

Dim As Ulong i
Dim As Double t1, y
Dim Shared As Double t2
Dim Shared As Ulong ltime
Dim As Any Ptr hThread, x

Sub SecondThread( x As Any Ptr)
  Dim As Ulong i
  Dim As Double y
  t2 = Timer
  For i = 1 To ltime
    y = Rnd
  Next
  t2 = Timer - t2
  
End Sub

Dim hProcess As Handle
hProcess = GetCurrentProcess()
SetProcessAffinityMask( hProcess, 1) ' ie CPU 0

ltime = 100000000

'hThread = ThreadCreate( @SecondThread, 0 )

t1 = Timer
For i = 1 To ltime
  y = Rnd
Next
t1 = Timer - t1

Threadwait( hThread )

Print Int(100/t1);" mill/sec"
'Print Int(100/t1);" miil/sec", int(100/t2);" mill/sec"

Sleep
Code2.bas

Code: Select all

' Generator PCG32A
Type pcg32A_random_t
  As Ulongint state, inc
End Type

Dim Shared pcg32A As pcg32A_random_t

Function pcg32A_random_r(Byval rng As pcg32A_random_t Ptr) As Ulong
  Dim As Ulongint oldstate = rng->state
  ' Advance internal state
  rng->state = oldstate * 6364136223846793005ULL + (rng->inc Or 1)
  '' Calculate output function (XSH RR), uses old state for max ILP
  ' rotate32((state ^ (state >> 18)) >> 27, state >> 59)
  Dim As Ulong xorshifted = ((oldstate Shr 18u) xor oldstate) Shr 27u
  Dim As Ulong rot = oldstate Shr 59u
  Return (xorshifted Shr rot) Or (xorshifted Shl ((-rot) And 31))
End Function

Sub Randomizepcg32A( Seed As Ulongint = 0 )
  Dim i As Integer
  If Seed = 0 Then
    Randomize , 5
    pcg32A.state = Rnd*(2^64)
    pcg32A.inc = Rnd*(2^63)
  Else
    pcg32A.state = Seed
  End If
  For i As Integer = 1 To 200
    pcg32A_random_r(@pcg32A)
  Next i
End Sub

Function pcg32AS() As Single
  Dim TempVar As Ulong
  Dim rng As pcg32A_random_t Ptr = @pcg32A
  Dim As Ulongint oldstate = rng->state
  '' Advance internal state
  rng->state = oldstate * 6364136223846793005ULL + (rng->inc Or 1)
  '' Calculate output function (XSH RR), uses old state for max ILP
  Dim As Ulong xorshifted = ((oldstate Shr 18u) xor oldstate) Shr 27u
  Dim As Ulong rot = oldstate Shr 59u
  Tempvar =  (xorshifted Shr rot) Or (xorshifted Shl ((-rot) And 31))
  Asm
    mov eax, dword Ptr [TempVar]
    movd xmm0, eax
    psrlq xmm0, 9
    mov eax, 1
    cvtsi2ss xmm1, eax
    por xmm0, xmm1
    subss xmm0, xmm1
    movd [Function], xmm0
  End Asm
End Function

' Generator PCG32B
Type pcg32B_random_t
  As Ulongint state, inc
End Type

Dim Shared pcg32B As pcg32B_random_t

Function pcg32B_random_r(Byval rng As pcg32B_random_t Ptr) As Ulong
  Dim As Ulongint oldstate = rng->state
  ' Advance internal state
  rng->state = oldstate * 6364136223846793005ULL + (rng->inc Or 1)
  '' Calculate output function (XSH RR), uses old state for max ILP
  ' rotate32((state ^ (state >> 18)) >> 27, state >> 59)
  Dim As Ulong xorshifted = ((oldstate Shr 18u) xor oldstate) Shr 27u
  Dim As Ulong rot = oldstate Shr 59u
  Return (xorshifted Shr rot) Or (xorshifted Shl ((-rot) And 31))
End Function

Sub Randomizepcg32B( Seed As Ulongint = 0 )
  Dim i As Integer
  If Seed = 0 Then
    Randomize , 5
    pcg32B.state = Rnd*(2^64)
    pcg32B.inc = Rnd*(2^63)
  Else
    pcg32B.state = Seed
  End If
  For i As Integer = 1 To 200
    pcg32B_random_r(@pcg32B)
  Next i
End Sub

Function pcg32BS() As Single
  Dim TempVar As Ulong
  Dim rng As pcg32B_random_t Ptr = @pcg32B
  Dim As Ulongint oldstate = rng->state
  '' Advance internal state
  rng->state = oldstate * 6364136223846793005ULL + (rng->inc Or 1)
  '' Calculate output function (XSH RR), uses old state for max ILP
  Dim As Ulong xorshifted = ((oldstate Shr 18u) xor oldstate) Shr 27u
  Dim As Ulong rot = oldstate Shr 59u
  Tempvar =  (xorshifted Shr rot) Or (xorshifted Shl ((-rot) And 31))
  Asm
    mov eax, dword Ptr [TempVar]
    movd xmm0, eax
    psrlq xmm0, 9
    mov eax, 1
    cvtsi2ss xmm1, eax
    por xmm0, xmm1
    subss xmm0, xmm1
    movd [Function], xmm0
  End Asm
End Function

'--------------------------------------------

Dim As Ulong i
Dim As Double t1, y
Dim Shared As Double t2
Dim Shared As Ulong ltime
Dim As Any Ptr hThread, x

Sub SecondThread( x As Any Ptr )
  Dim As Ulong i
  Dim As Double y
  
  t2 = Timer
  For i = 1 To ltime
    y = PCG32BS
  Next
  t2 = Timer - t2
  
End Sub

Randomizepcg32A
Randomizepcg32B

ltime = 100000000

'hThread = ThreadCreate( @SecondThread, 0 )

t1 = Timer
For i = 1 To ltime
  y = PCG32AS
Next
t1 = Timer - t1

Threadwait( hThread )

Print Int(100/t1);" mill/sec"
'Print Int(100/t1);" miil/sec", int(100/t2);" mill/sec"

Sleep
St_W
Posts: 1619
Joined: Feb 11, 2009 14:24
Location: Austria
Contact:

Re: So, RND is not thread safe then.

Post by St_W »

deltarho[1859] wrote:There is only one instance of RND and the two cores are 'fighting over it' - we have memory collisions. RND is not thread safe.
Yes, it looks like the RND implementation does not use any locks despite accessing a shared state array. So it is clearly not thread safe.
After having followed all your forum threads about RNG I think you would be a perfect candidate for improving the RND implementation in FreeBasic's runtime library :-P
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: So, RND is not thread safe then.

Post by deltarho[1859] »

I think you would be a perfect candidate for improving the RND implementation in FreeBasic's runtime library.
Thank you for that, St_W, but unfortunately it is not true. That requires discipline and I don't have any. In Jung's typology I am an INTP, aka Logicians. They have weaknesses.
Logicians remain so open to new information that they often never commit to a decision at all. This applies to their own skills as well – Logician personalities know that as they practice, they improve, and any work they do is second-best to what they could do. Unable to settle for this, Logicians sometimes delay their output indefinitely with constant revisions, sometimes even quitting before they ever begin.
Oh dear, absolutely spot on.

FreeBASIC's Mersenne Twister is begging to be improved upon. I did that with RndMT. It is very Windows based but that can be corrected. What we need is someone who can take RndMT and 'go to market' with it. 'Going to market' is something that INTPs are totally clueless at. An idea bears fruit and we move on to the next idea.

There are some very talented programmers on this forum and some of them will have the discipline to 'go to market'.

Don't read this as a 'cop out'. I really do know where I am coming from.
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: So, RND is not thread safe then.

Post by deltarho[1859] »

If more than two threads require random numbers it may be worthwhile to examine the code duplication with a view to condensing to the original number of functions with some parameter passing
Maybe not. I looked at a thread index and dimensioned the shared variables. By dimension I mean in the classical sense, that is we replace foo, for example, with foo(0 To 7). A primary thread could use foo(0) and secondary threads could use foo(1) to foo(7). Variables are no longer shared so we have eliminated memory variable collisions or race conditions as Microsoft calls them. We have then thread safety. Considering a primary thread only found that the speed of the generator suffered badly.

However, if our shared function uses inline assembler then we will have register variable collisions as well. We cannot dimension registers. This had not occurred to me before but using 'straight forward' inline assembler is not thread safe. There may exist ways to eliminate register variable collisions but that and eliminating memory variable collisions will undoubtedly see a further reduction in the generator's speed..

The easiest method, I reckon, is the one described above - 100% code duplication - that will guarantee thread safety without further ado and leave the speed of the generator in tact.

There is another way and that is to use classes but my understanding is that FreeBASIC has not implemented that functionality yet.
St_W
Posts: 1619
Joined: Feb 11, 2009 14:24
Location: Austria
Contact:

Re: So, RND is not thread safe then.

Post by St_W »

deltarho[1859] wrote:Maybe not. I looked at a thread index and dimensioned the shared variables. By dimension I mean in the classical sense, that is we replace foo, for example, with foo(0 To 7). A primary thread could use foo(0) and secondary threads could use foo(1) to foo(7). Variables are no longer shared so we have eliminated memory variable collisions or race conditions as Microsoft calls them. We have then thread safety. Considering a primary thread only found that the speed of the generator suffered badly.
Yes, the state variables must not be shared among the different threads. What you would do to make it threadsafe is very similar to your proposal: One would typically pack the state variables into a structure or class and allocate such a set of state variables (let's call this "context") for each thread. Whether you'd instantiate a class, allocate a block of memory or choose a unused index from a preallocated set of such contexts are only implementation details - the concept is the same. The thread has to store some reference to the context and pass that to the thread-safe function. Depending on the implementation this could be a reference to a class instance, a pointer to a structure/memory or simply an index in a pre-allocated array as you've shown above.
deltarho[1859] wrote:However, if our shared function uses inline assembler then we will have register variable collisions as well. We cannot dimension registers. This had not occurred to me before but using 'straight forward' inline assembler is not thread safe. There may exist ways to eliminate register variable collisions but that and eliminating memory variable collisions will undoubtedly see a further reduction in the generator's speed..
No, you won't have collisions in CPU registers and don't have to care about thread safety there as the registers are never shared among different threads. Different physical cores have their own set of registers, for the rest see https://en.wikipedia.org/wiki/Context_switch or https://users.cs.duke.edu/~narten/110/n ... ode13.html for details (does that state/context concept look familiar to you? :-) ).
deltarho[1859] wrote:The easiest method, I reckon, is the one described above - 100% code duplication - that will guarantee thread safety without further ado and leave the speed of the generator in tact.
Maybe, but it isn't required. I can't see why there should be any speed difference when using separate contexts rather than a single one, but maybe I have missed something.
deltarho[1859] wrote:There is another way and that is to use classes but my understanding is that FreeBASIC has not implemented that functionality yet.
While there is no "class" keyword yet FreeBasic's "type"s implement many features known from classes in other programming languages. It should be more than enough to implement this.
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: So, RND is not thread safe then.

Post by deltarho[1859] »

St_W wrote:...the registers are never shared among different threads. Different physical cores have their own set of registers,... [links re Context switching]
Context switching occurs when the OS decides to switch from one thread to another. We have not got that. Our situation has nothing to do with context switching.
St_W wrote:No, you won't have collisions in CPU registers...
I agree. In our situation we have a function Foo, say, called by a threadA, say. Foo uses memory variables and register variables. If another threadB, say, calls Foo then the memory variables may get changed by both threadA and threadB. Foo may not have time to act upon threadA's change before threadB intervenes - leading to possible data corruption. With regard register variables threadA will use its registers and threadB will use its registers so we don't have a conflict there.

Thanks, St_W, I hadn't thought along those lines.
St_W wrote:While there is no "class" keyword yet FreeBasic's "type"s implement many features known from classes in other programming languages. It should be more than enough to implement this.
I need to do some reading.<smile>
St_W
Posts: 1619
Joined: Feb 11, 2009 14:24
Location: Austria
Contact:

Re: So, RND is not thread safe then.

Post by St_W »

deltarho[1859] wrote:Context switching occurs when the OS decides to switch from one thread to another. We have not got that. Our situation has nothing to do with context switching.
I wanted to say that register collisions do not happen because of this as they are always independent from other threads. Of course that doesn't apply to variables in memory referenced by multiple threads ("shared state", see below).
deltarho[1859] wrote:In our situation we have a function Foo, say, called by a threadA, say. Foo uses memory variables and register variables. If another threadB, say, calls Foo then the memory variables may get changed by both threadA and threadB. Foo may not have time to act upon threadA's change before threadB intervenes - leading to possible data corruption.
That describes shared state variables, if I got that right. As I've written above do not use such a shared state but create separate state variables for each thread ("context"). So instead of letting Foo always access a memory variable X in the example we add a context argument pointing to an individual X for each context like this: Foo(context) will access context->X instead of X and every thread has to use a separate context. If that isn't possible you'd have to synchronize the access to these variables (Locks, ...).

I don't see a connection here: the one thing are registers which are inherently thread-safe because of context switching and the other thing is a shared state in memory, which has to be avoided or synchronized for thread safety.
deltarho[1859] wrote:
St_W wrote:While there is no "class" keyword yet FreeBasic's "type"s implement many features known from classes in other programming languages. It should be more than enough to implement this.
I need to do some reading.<smile>
For the mentioned things a plain type should do; if you need/want RTTI you have to inherit from Object ("type ... extends Object").
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: So, RND is not thread safe then.

Post by deltarho[1859] »

I had to pop out for a while.
St_W wrote:That describes shared state variables, if I got that right.
That describes any memory variable in a function being called by more than one thread. The state variables are determined by some arithmetic which uses memory variables. If that arithmetic is interrupted by another thread we may get incorrect state variables being saved.

Locks are out of the question - we get a suspension of parallel processing.

Back to reading,
St_W
Posts: 1619
Joined: Feb 11, 2009 14:24
Location: Austria
Contact:

Re: So, RND is not thread safe then.

Post by St_W »

deltarho[1859] wrote:
St_W wrote:That describes shared state variables, if I got that right.
That describes any memory variable in a function being called by more than one thread. The state variables are determined by some arithmetic which uses memory variables. If that arithmetic is interrupted by another thread we may get incorrect state variables being saved.
Ok, thanks, so I think we meant the same but there were some misunderstandings. With "shared state variables" I had a more general/abstract concept of a "state" of an algorithm in mind. From a more technical perspective the "shared" attribute is actually the important one - as long as memory (representing any state) is used by a single thread only everything is okay, otherwise that memory has to be allocated for each thread separately (which I called "context" previously). However, I'm wondering why the "memory variables" in the calculation you mentioned are not on the stack but seem to be shared among threads?
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: So, RND is not thread safe then.

Post by deltarho[1859] »

St_W wrote:However, I'm wondering why the "memory variables" in the calculation you mentioned are not on the stack but seem to be shared among threads?
If you look at my opening post Code2.bas the first function has:

Code: Select all

Dim As Ulongint oldstate = rng->state
...
...
...
Dim As Ulong xorshifted = ((oldstate Shr 18u) xor oldstate) Shr 27u
Dim As Ulong rot = oldstate Shr 59u
The arithmetic/"calculation" uses oldstate, xorshifted and rot. They need to be context dimensioned as well because more than one thread will change them. Ok, we could use a structure/memory for them but they need attention in one form or another.

I don't see stack use being helpful - as soon as they are retrieved they are in broad daylight and open season for another thread to clobber them.
counting_pine
Site Admin
Posts: 6323
Joined: Jul 05, 2005 17:32
Location: Manchester, Lancs

Re: So, RND is not thread safe then.

Post by counting_pine »

Could someone note this in a bug report at https://sf.net/p/fbc/bugs?
St_W
Posts: 1619
Joined: Feb 11, 2009 14:24
Location: Austria
Contact:

Re: So, RND is not thread safe then.

Post by St_W »

deltarho[1859] wrote:If you look at my opening post Code2.bas the first function has:

Code: Select all

Dim As Ulongint oldstate = rng->state
...
Dim As Ulong xorshifted = ((oldstate Shr 18u) xor oldstate) Shr 27u
Dim As Ulong rot = oldstate Shr 59u
The arithmetic/"calculation" uses oldstate, xorshifted and rot. They need to be context dimensioned as well because more than one thread will change them. Ok, we could use a structure/memory for them but they need attention in one form or another.
I don't see stack use being helpful - as soon as they are retrieved they are in broad daylight and open season for another thread to clobber them.
Sorry, I haven't looked on your code in detail previously, but I did now.
Those variables are already defined locally inside the function, so they are put on the stack. And every thread has its own stack, so those won't cause any threading conflicts.

I would implement the PCG32 generator like that, which seems to be even slightly faster on my PC (probably because they don't use "dim shared" variables for the state):

Code: Select all

' Generator PCG32
Type pcg32
	
	public:
		declare sub init(seed as ULongInt = 0)
		declare function rand() as ULong
		declare function rands() as Single
	
	private:
  	state As Ulongint
  	inc   as ULongInt
End Type

function pcg32.rand() as ULong
  Dim As Ulongint oldstate = this.state
  ' Advance internal state
  this.state = oldstate * 6364136223846793005ULL + (this.inc Or 1)
  '' Calculate output function (XSH RR), uses old state for max ILP
  ' rotate32((state ^ (state >> 18)) >> 27, state >> 59)
  Dim As Ulong xorshifted = ((oldstate Shr 18u) xor oldstate) Shr 27u
  Dim As Ulong rot = oldstate Shr 59u
  Return (xorshifted Shr rot) Or (xorshifted Shl ((-rot) And 31))
End Function

sub pcg32.init(seed as ULongInt = 0)
  Dim i As Integer
  If Seed = 0 Then
    Randomize , 5
    this.state = Rnd*(2^64)
    this.inc = Rnd*(2^63)
  Else
    this.state = Seed
  End If
  For i As Integer = 1 To 200
    this.rand()
  Next i
End Sub

function pcg32.rands() as Single
  Dim TempVar As Ulong
  Dim As Ulongint oldstate = this.state
  '' Advance internal state
  this.state = oldstate * 6364136223846793005ULL + (this.inc Or 1)
  '' Calculate output function (XSH RR), uses old state for max ILP
  Dim As Ulong xorshifted = ((oldstate Shr 18u) xor oldstate) Shr 27u
  Dim As Ulong rot = oldstate Shr 59u
  Tempvar =  (xorshifted Shr rot) Or (xorshifted Shl ((-rot) And 31))
  Asm
    mov eax, dword Ptr [TempVar]
    movd xmm0, eax
    psrlq xmm0, 9
    mov eax, 1
    cvtsi2ss xmm1, eax
    por xmm0, xmm1
    subss xmm0, xmm1
    movd [Function], xmm0
  End Asm
End Function


'--------------------------------------------

Dim Shared pcg32A As pcg32
Dim Shared pcg32B As pcg32


Dim As Ulong i
Dim As Double t1, y
Dim Shared As Double t2
Dim Shared As Ulong ltime
Dim As Any Ptr hThread

Sub SecondThread( x As Any Ptr )
  Dim As Ulong i
  Dim As Double y
 
  t2 = Timer
  For i = 1 To ltime
    y = pcg32B.rands()
  Next
  t2 = Timer - t2
 
End Sub

pcg32A.init()
pcg32B.init()

ltime = 100000000

'hThread = ThreadCreate( @SecondThread, 0 )

t1 = Timer
For i = 1 To ltime
  y = pcg32A.rands()
Next
t1 = Timer - t1

Threadwait( hThread )

Print Int(100/t1);" mill/sec"
'Print Int(100/t1);" miil/sec", int(100/t2);" mill/sec"

Sleep
This avoids code duplication. By using class methods the context reference does not have to be passed explicitly, but is passed implicitly and available via the "this" keyword. "this" would not be required in many (if not all) cases in this example, I've just added it for clarity.
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: So, RND is not thread safe then.

Post by deltarho[1859] »

@counting_pine

It is not a bug, nobody writes random number generators with a view to thread safety. I do not think that you will find a single compiler in any language whose inbuilt generator is thread safe.

@St_W
Those variables are already defined locally inside the function, so they are put on the stack.
Dim creates space on the stack? I didn't know that - Help just says "Declares a variable by name and reserves memory to accommodate it." This is also different to PowerBASIC. Ouch!

I seriously like your code. I shall go through it with a fine tooth comb tomorrow. You are doing things which I haven't seen yet which is great for me because my old grey matter will kick in at 'ten to the dozen' with others things that I can do.

I am getting '142 mill/sec 145 mill/sec' compared with my 100% code duplication of '195 mill/sec 193 mil/sec'. My figures are slightly down on yesterday but hose two tests were dome within seconds of each other. I figured it important that whatever alternative to 100% code duplication was considered it still 'beat the pants' of FBs generators. You have done just that: Thread safe and faster than any of FBs generators, by a good margin. I have eight cores on my machine. It would very easy to ensure that all of them have their own thread safe generator and unique sequence.

Code: Select all

Dim Shared pcg32A As pcg32
Dim Shared pcg32B As pcg32
This is what would be done in PowerBASIC with global objects and object duplication. Interesting.

I hope FreeBASIC newbies, like me, are reading this.
St_W wrote:I think you would be a perfect candidate for improving the RND implementation in FreeBasic's runtime library.
Et tu, Brute?
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: So, RND is not thread safe then.

Post by deltarho[1859] »

@St_W

Nice code. I was so close with a code snippet that I was testing. I had some function declarations in a type structure but was getting ' error 255: An ENUM, TYPE or UNION cannot be empty, found 'End' in 'End Type' '. Declarations are not datatypes so the compiler designated the type structure as empty and it will not tolerate empty type structures. I saw that your code was working so went back to my code snippet and dropped in 'Dim dummy As Ulong'. The structure was not empty now so I got past the compiler.

Now, call me old fashioned but we should not have to use a dummy variable to get something to work. For me that is a bug workaround. The compiler should allow an empty type structure and issue a warning along the lines of "Oy, pudding head - you've got an empty ENUM, TYPE or UNION".<smile>

Hadn't seen the use of 'This' before - I like that.

With

Code: Select all

Dim Shared pcg32A As pcg32
Dim Shared pcg32B As pcg32
pcg32A did not have to be global but I favour that as we could require random numbers within Subs/Functions in the primary thread.

Playing around with the code I see that we could dispense with those statements and use:

Code: Select all

Dim pcg as pcg32
pcg.init()
and use pcg for the rest of the primary thread.

With regard the secondary thread we could use the same two lines and pcg for the rest of that thread function.

In effect then, we are using local declarations of the generators as opposed to global declarations.

I don't think that I would though - globals give us much more flexibilty. Having said that there are those whose life's work is to shoot globals on sight and are getting their 'sawn-off' out of the cabinet as I write this.

Of course, we now know that RndMT can also be made thread safe but I shall leave that as a reader exercise because I do not have a burning desire to return to RndMT.<smile>

CryptoRnd and CryptoRndII don't use a state vector so they should be thread safe without further ado which should qualify me as the first person on the planet to write a thread safe random number generator; although it did not dawn on me at the time.<laugh> Having said that I am having a slight problem in proving it. It would seem that thread creation and thread pooling are mutually exclusive. Makes sense, I guess, but MSDN don't appear to mention it.

It is tempting to use this thread safe version as my default. Generator speeds, in general, look good on paper but in practical terms the difference between the thread safe version of PCG32 and the non thread safe version may not be that noticeable. This should definitely be in the run-time library since none of the current residents are thread safe - courtesy of counting_pine, St_W and deltarho[1859]. <smile>

Thanks St_W for taking the time out to write your code.
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: So, RND is not thread safe then.

Post by deltarho[1859] »

It would seem that thread creation and thread pooling are mutually exclusive.
That is a false statement.
Having said that I am having a slight problem in proving it.
What we have is an analogy to context switching. Both CryptoRnd and CryptoRndII switch buffers when one buffer has been exhausted. The last thing we want is another thread coming in during the switch. For small requests, below a buffer size, there was no problem. For requests above the buffer size Windows kindly lets us know that the exe has stopped working - but it takes a while to do so. As a temporary fix I dropped a mutex into the code to allow the switch to work unencumbered. I sailed through 100 milliun requests, that is 762 switches, without issue. I need to do some work to determine the minimum time we need to use the mutex. I will drop a note in the 'A fast CPRNG' thread advising that both CryptoRnd and CryptoRndII should not be used in more than one thread of execution until further notice. I doubt that anyone has experienced this issue because I am not aware of a contract being put out on me, yet.<smile>
Post Reply