Loop Design For Safe Parallel Processing with PB Example

Applications, Games, Tools, User libs and useful stuff coded in PureBasic
buddymatkona
Enthusiast
Enthusiast
Posts: 252
Joined: Mon Aug 16, 2010 4:29 am

Loop Design For Safe Parallel Processing with PB Example

Post by buddymatkona »

Normal Program
.
. code as usual until you get to a cpu-intensive loop
.
BIGLOOP ...Break it up and do Part1, Part2, ...PartN in parallel

let the loop finish

. then resume normal sequential processing
.
. code as usual
.
End
Do you have a multicore processor with some idle CPUs? Whenever you have a loop doing something repetitive to a lot of data, you may want to break up the work. A quad core with hyperthreading enabled can do such processing in about 1/8 the time it takes a single CPU.
To keep parallel processing simple and avoid race conditions, break loops up into threaded segments and give threads their own Input/Output blocks of global memory. A PureBasic console example showing one way to segment a loop and run segments in parallel is shown below. You can experiment with different workloads and different numbers of threads by changing control parameters in the first line of code. Use that idle time!

Code: Select all

#SimWorkLoop = 40000 : N = 1000 : NThreads = 3 ; Loop Controls for adding workload, count for the loop to be segmented, number of segments (threads) 
    ;Compile with Debugger - NThreads loop segments will be done sequentially in one thread
    ;Compile without Debugger - Nthreads loop segments will be done in parallel threads (NThreads = number of near-idle CPUs should be optimum)
Global Dim RecIn(N + 1) ;Define  thread input data in MAIN before launching the threads
Global Dim RecOut(N + 1) ;Thread output area - use any shared resources such as File I/O in MAIN 
Global Dim Start(NThreads + 1) ; Start index within arrays RecIN and RecOut for each thread
Global Dim Stop(NThreads + 1) ; Start(ThreadNumber) to Stop(ThreadNumber) defines  each thread's share of the data
Dim AllThreads(NThreads + 1) ; OS assigned ThreadIDs

Macro ThreadLoop(ProcName)
  CompilerIf #PB_Compiler_Debugger = 0
    For ICreate = 1 To NThreads : AllThreads(ICreate) = CreateThread(@ProcName(), ICreate) : Delay(50) : Next ICreate ;Do threads in parallel
    For iwait = 1 To NThreads : WaitThread(AllThreads(iwait)) : Delay(50) : Next iwait ;Without a delay between thread calls, PureBasic will crash.
    ;PrintN (" PB_Compiler_Debugger = " + Str(#PB_Compiler_Debugger))
  CompilerElse
    For I = 1 To NThreads 
      ProcName(i) ; The loop segments will be done in sequence with ordinary procedure calls if #PB_Compiler_Debugger <> 0
      ;PrintN (" PB_Compiler_Debugger = " + Str(#PB_Compiler_Debugger))
    Next i
  CompilerEndIf
EndMacro

Macro SegmentLoop(LoopCount, NumberSegments, StartArray, StopArray)
; Divide the loop count into NumberSegments of Start-Stop indices for parallel processing
PerThread = (LoopCount+NumberSegments/2)/NumberSegments ; thread size portion of the original loop count
For I = 1 To NumberSegments 
  StartArray(I) = PerThread * (I - 1) + 1 
  StopArray(I) = PerThread * I 
Next I 
StopArray(NumberSegments) = LoopCount
EndMacro

Procedure DoTheWork(*ThreadNumber)
      StartTime.i = ElapsedMilliseconds()
      tn.i = *ThreadNumber ; shorter name :)    
      PrintN("-----Starting Thread Number: "+Str(tn))
     For IWork = 1 To #SimWorkLoop    ; simulate work
         InputSum.i = 0   
        For MyLoopPart =  Start(tn) To Stop(tn)
          InputSum = InputSum + RecIn(MyLoopPart) ;  dummy input data 
          RecOut(MyLoopPart) = tn * 1000 + MyLoopPart  ; thread specific return data 
        Next MyLoopPart                      
     Next IWork   
      WorkTime.i = ElapsedMilliseconds() - StartTime     
      WorkStop.s = FormatDate("%mm %dd %yyyy %hh:%ii", Date())
      PrintN(WorkStop +" Thread Number: " + Str(tn) + " is ending. Thread Execution Time(msec)= " + Str(WorkTime))     
EndProcedure

;************************ THREADTEST **************************************

   SegmentLoop(N, NThreads, Start, Stop) ; Macro to Divide a 1-N loop into Start-Stop segments

For I = 1 To N : RecIn(I) = I : Next I ; Simulate input data
OpenConsole()
StartTime.i = ElapsedMilliseconds()

   ThreadLoop(DoTheWork) ; Macro to Parallel Process loop segments when you compile without Debugger
  
WorkTime.i = ElapsedMilliseconds() - StartTime
   PrintN(Str(NThreads) + " threads finished.                          Total  Time(msec)= " + Str(WorkTime) )
   PrintN (" Press ENTER To quit ThreadTest.")
   Input()
End ;************************ THREADTEST **************************************
User avatar
blueb
Addict
Addict
Posts: 1116
Joined: Sat Apr 26, 2003 2:15 pm
Location: Cuernavaca, Mexico

Re: Loop Design For Safe Parallel Processing with PB Example

Post by blueb »

Dug up from the graveyard :mrgreen:
buddymatkona's code appears to work well...

Perhaps too well??

I modified it to get 32 threads (16 core/32 thread machine)
and I get...

Code: Select all

-----Starting Thread Number: 1
08 31 2016 08:40 Thread Number: 1 is ending. Thread Execution Time(msec)= 14
-----Starting Thread Number: 2
08 31 2016 08:40 Thread Number: 2 is ending. Thread Execution Time(msec)= 13
-----Starting Thread Number: 3
08 31 2016 08:40 Thread Number: 3 is ending. Thread Execution Time(msec)= 11
-----Starting Thread Number: 4
08 31 2016 08:40 Thread Number: 4 is ending. Thread Execution Time(msec)= 6
-----Starting Thread Number: 5
08 31 2016 08:40 Thread Number: 5 is ending. Thread Execution Time(msec)= 6
-----Starting Thread Number: 6
08 31 2016 08:40 Thread Number: 6 is ending. Thread Execution Time(msec)= 6
-----Starting Thread Number: 7
08 31 2016 08:40 Thread Number: 7 is ending. Thread Execution Time(msec)= 6
-----Starting Thread Number: 8
08 31 2016 08:40 Thread Number: 8 is ending. Thread Execution Time(msec)= 6
-----Starting Thread Number: 9
08 31 2016 08:40 Thread Number: 9 is ending. Thread Execution Time(msec)= 5
-----Starting Thread Number: 10
08 31 2016 08:40 Thread Number: 10 is ending. Thread Execution Time(msec)= 5
-----Starting Thread Number: 11
08 31 2016 08:40 Thread Number: 11 is ending. Thread Execution Time(msec)= 6
-----Starting Thread Number: 12
08 31 2016 08:40 Thread Number: 12 is ending. Thread Execution Time(msec)= 6
-----Starting Thread Number: 13
08 31 2016 08:40 Thread Number: 13 is ending. Thread Execution Time(msec)= 6
-----Starting Thread Number: 14
08 31 2016 08:40 Thread Number: 14 is ending. Thread Execution Time(msec)= 6
-----Starting Thread Number: 15
08 31 2016 08:40 Thread Number: 15 is ending. Thread Execution Time(msec)= 6
-----Starting Thread Number: 16
08 31 2016 08:40 Thread Number: 16 is ending. Thread Execution Time(msec)= 11
-----Starting Thread Number: 17
08 31 2016 08:40 Thread Number: 17 is ending. Thread Execution Time(msec)= 9
-----Starting Thread Number: 18
08 31 2016 08:40 Thread Number: 18 is ending. Thread Execution Time(msec)= 8
-----Starting Thread Number: 19
08 31 2016 08:40 Thread Number: 19 is ending. Thread Execution Time(msec)= 7
-----Starting Thread Number: 20
08 31 2016 08:40 Thread Number: 20 is ending. Thread Execution Time(msec)= 6
-----Starting Thread Number: 21
08 31 2016 08:40 Thread Number: 21 is ending. Thread Execution Time(msec)= 7
-----Starting Thread Number: 22
08 31 2016 08:40 Thread Number: 22 is ending. Thread Execution Time(msec)= 7
-----Starting Thread Number: 23
08 31 2016 08:40 Thread Number: 23 is ending. Thread Execution Time(msec)= 21
-----Starting Thread Number: 24
08 31 2016 08:40 Thread Number: 24 is ending. Thread Execution Time(msec)= 7
-----Starting Thread Number: 25
08 31 2016 08:40 Thread Number: 25 is ending. Thread Execution Time(msec)= 7
-----Starting Thread Number: 26
08 31 2016 08:40 Thread Number: 26 is ending. Thread Execution Time(msec)= 7
-----Starting Thread Number: 27
08 31 2016 08:40 Thread Number: 27 is ending. Thread Execution Time(msec)= 6
-----Starting Thread Number: 28
08 31 2016 08:40 Thread Number: 28 is ending. Thread Execution Time(msec)= 6
-----Starting Thread Number: 29
08 31 2016 08:40 Thread Number: 29 is ending. Thread Execution Time(msec)= 5
-----Starting Thread Number: 30
08 31 2016 08:40 Thread Number: 30 is ending. Thread Execution Time(msec)= 11
-----Starting Thread Number: 31
08 31 2016 08:40 Thread Number: 31 is ending. Thread Execution Time(msec)= 6
-----Starting Thread Number: 32
08 31 2016 08:40 Thread Number: 32 is ending. Thread Execution Time(msec)= 12
32 threads finished.                          Total  Time(msec)= 263
 Press ENTER To quit ThreadTest.
Can anyone test this?
Any comments?
- It was too lonely at the top.

System : PB 6.21(x64) and Win 11 Pro (x64)
Hardware: AMD Ryzen 9 5900X w/64 gigs Ram, AMD RX 6950 XT Graphics w/16gigs Mem
Post Reply