CallParallel : parallel coding for PB 4.41+

Share your advanced PureBasic knowledge/code with the community.
User avatar
eddy
Addict
Addict
Posts: 1479
Joined: Mon May 26, 2003 3:07 pm
Location: Nantes

CallParallel : parallel coding for PB 4.41+

Post by eddy »

This is the second example updated for PB 4.41+. : http://www.purebasic.fr/english/viewtop ... 12&t=30873
- supported : win / linux ( because there's no yet a Mac specific code for the following function GetCPUcount() )
- tested on a dual core machine.
I didn't keep any macros of the previous version.

This one is easier to use in my opinion. :)

Code: Select all

EnableExplicit
Global ParallelCPUCount
Global ParallelMutex
Global ParallelStart
Global ParallelStarted
Global ParallelRunning
Global *ParallelValue
Global *ParallelFunc
Global *ParallelCustomData
Threaded ParallelLocked.b ; MUST NOT be global, so every thread gets its own copy of this variable!

Procedure GetCPUCount()
   CompilerSelect #PB_Compiler_OS
      CompilerCase #PB_OS_Windows
         Protected Count, i, ProcessMask, SystemMask
         
         If GetProcessAffinityMask_(GetCurrentProcess_(), @ProcessMask, @SystemMask)
            For i=0 To 31
               If ProcessMask & (1<<i)
                  Count+1
               EndIf
            Next
         EndIf
         
         If Count=0
            ProcedureReturn 1
         Else
            ProcedureReturn Count
         EndIf
      CompilerCase #PB_OS_Linux
         ;
         ; Code by remi_meier
         ;
         Protected.i file, count, dir
         Protected.s s
         
         If FileSize("/proc/cpuinfo")<>-1
            file=ReadFile(#PB_Any, "/proc/cpuinfo")
            
            If IsFile(file)
               count=0
               While Not Eof(file)
                  s.s=ReadString(file)
                  If Left(s, 9)="processor"
                     count+1
                  EndIf
               Wend
               
               CloseFile(file)
            EndIf
            
         Else
            dir=ExamineDirectory(#PB_Any, "/proc/acpi/processor", "")
            count=0
            If IsDirectory(dir)
               While NextDirectoryEntry(dir)
                  If Left(DirectoryEntryName(dir), 3)="CPU"
                     count+1
                  EndIf
               Wend
               FinishDirectory(dir)
            EndIf
         EndIf
         
         ProcedureReturn count
   CompilerEndSelect
EndProcedure
Procedure ParallelWorkerThread(dummy)
   Protected *Function, *CurrentValue, *CustomData
   
   Repeat
      WaitSemaphore(ParallelStart)     ; wait for "weakup" - semaphore
      WaitSemaphore(ParallelRunning)   ; this one should be immediately available (allows to later know when the work is complete)
      *CustomData=*ParallelCustomData
      *Function=*ParallelFunc
      *CurrentValue=*ParallelValue     ; copy the values locally
      SignalSemaphore(ParallelStarted) ; signal the "startup-complete" semaphore so the next thread can be started
      CallFunctionFast(*Function, *CurrentValue, *CustomData)  ; execute function
      SignalSemaphore(ParallelRunning) ; signal completion of the job
   ForEver
EndProcedure
Procedure CallParallel(*Function, *CurrentValue, *CustomData=#Null) ; Call a function in parallel (only if multiple processors are available)
   If ParallelCPUCount>1 And (ParallelLocked Or TryLockMutex(ParallelMutex))
      ParallelLocked=1
      
      ; specify the parallel function (with its custom data if necessary)
      *ParallelFunc=*Function
      *ParallelCustomData=*CustomData
      
      ; release one thread at a time so they can access the "CurrentValue"
      ; only the thread startup is serialized. the Loop() is run in parallel
      *ParallelValue=*CurrentValue
      SignalSemaphore(ParallelStart)
      WaitSemaphore(ParallelStarted)
   Else
      CallFunctionFast(*Function, *CurrentValue, *CustomData)
   EndIf
EndProcedure
Procedure FinishParallel() ; Waits for all running jobs to complete
   Define i
   If ParallelLocked
      ; wait for all worker threads to complete their work
      For i=1 To ParallelCPUCount
         WaitSemaphore(ParallelRunning)
      Next
      
      ; reset the running count semaphore back to the start value
      For i=1 To ParallelCPUCount
         SignalSemaphore(ParallelRunning);, ProcessorCount)
      Next
      ParallelLocked=0
      UnlockMutex(ParallelMutex)
   EndIf
EndProcedure
Procedure InitParallel(MinThreads=1) ; Initializes the parallel code ('MinThreads' >= your actual processor/core count)
   ParallelCPUCount=GetCPUCount()
   If ParallelCPUCount<MinThreads
      ParallelCPUCount=MinThreads
   EndIf
   
   If ParallelCPUCount>1
      ParallelMutex=CreateMutex()
      ParallelStart=CreateSemaphore(0)                   ;max=1
      ParallelStarted=CreateSemaphore(0)                 ;max=1
      ParallelRunning=CreateSemaphore(ParallelCPUCount)  ;max= ProcessorCount
      
      Protected i
      For i=1 To ParallelCPUCount
         CreateThread(@ParallelWorkerThread(), 0)
      Next
      
      CompilerIf #PB_Compiler_Thread=0
         MessageRequester("Init Parallel", "Initialization failed because your executable is not compiled in ThreadSafe mode.")
         End
      CompilerEndIf
   EndIf
EndProcedure
DisableExplicit

; ********************
; Example
; ********************

InitParallel()

Define maxX=1024
Define maxY=1024
Define img=CreateImage(#PB_Any, maxX, maxY, 32)
Define x, y

Procedure DrawingPlots(y, img)
   StartDrawing(ImageOutput(img))
      Define x
      For x=0 To ImageWidth(img)-1
         Define xx, yy, zz
         xx=x%256
         xx/#PI
         xx=Pow(Sin(xx)*$F, 7.3)
         yy=y%512
         yy/#PI
         yy=Pow(Cos(xx/3+yy)*$F, xx)
         zz=(xx ! yy) & $FF
         Plot(x, y, RGB(xx, yy, zz))
      Next
   StopDrawing()
EndProcedure

;/// Linear Coding
Delay(100)
startTime=ElapsedMilliseconds()
For y=0 To ImageHeight(img)-1
   DrawingPlots(y, img)
Next
duration1=ElapsedMilliseconds()-startTime

;/// Parallel Coding
Delay(100)
startTime=ElapsedMilliseconds()
For y=0 To ImageHeight(img)-1
   CallParallel(@DrawingPlots(), y, img)
Next
FinishParallel()
duration2=ElapsedMilliseconds()-startTime

MessageRequester("Terminated ", "Time: "+Str(duration1)+#LF$+"Time (optimized, CPU count="+Str(GetCPUCount())+"): "+Str(duration2)+#LF$)

OpenWindow(0, 0, 0, 800, 600, "Parallel Coding", #PB_Window_ScreenCentered | #PB_Window_SystemMenu)
ScrollAreaGadget(1, 0, 0, 800, 600, maxX, maxY)
ImageGadget(0, 0, 0, 0, 0, ImageID(img))
CloseGadgetList()

Repeat : Until WaitWindowEvent()=#PB_Event_CloseWindow
Imagewin10 x64 5.72 | IDE | PB plugin | Tools | Sprite | JSON | visual tool
remi_meier
Enthusiast
Enthusiast
Posts: 468
Joined: Sat Dec 20, 2003 6:19 pm
Location: Switzerland

Re: CallParallel : parallel coding for PB 4.41+

Post by remi_meier »

Nice!
Btw, I found a nice replacement for GetCPUCount() on Linux:
get_nprocs_()
:lol:
Athlon64 3700+, 1024MB Ram, Radeon X1600
kinglestat
Enthusiast
Enthusiast
Posts: 746
Joined: Fri Jul 14, 2006 8:53 pm
Location: Malta
Contact:

Re: CallParallel : parallel coding for PB 4.41+

Post by kinglestat »

Was wondering about this code as i do like threaded apps. This is an exampe which uses normal threads, and is compatible with all versions of PB. The thread would need to be created for all cpus; in the example I am assuming 2 cores or 2 cpus

What I like more with the threaded version is there is a delay which would make a heavy app more friendly with respect to other running apps.

Code: Select all


EnableExplicit
Global ParallelCPUCount
Global ParallelMutex
Global ParallelStart
Global ParallelStarted
Global ParallelRunning
Global *ParallelValue
Global *ParallelFunc
Global *ParallelCustomData

Global  imgHeight
Global  img
Global  mut = CreateMutex()

Structure   stDraw
  loop.i
  y.i
  release.i
EndStructure


Threaded ParallelLocked.b ; MUST NOT be global, so every thread gets its own copy of this variable!

Procedure GetCPUCount()
   CompilerSelect #PB_Compiler_OS
      CompilerCase #PB_OS_Windows
         Protected Count, i, ProcessMask, SystemMask
         
         If GetProcessAffinityMask_(GetCurrentProcess_(), @ProcessMask, @SystemMask)
            For i=0 To 31
               If ProcessMask & (1<<i)
                  Count+1
               EndIf
            Next
         EndIf
         
         If Count=0
            ProcedureReturn 1
         Else
            ProcedureReturn Count
         EndIf
      CompilerCase #PB_OS_Linux
         ;
         ; Code by remi_meier
         ;
         Protected.i file, count, dir
         Protected.s s
         
         If FileSize("/proc/cpuinfo")<>-1
            file=ReadFile(#PB_Any, "/proc/cpuinfo")
            
            If IsFile(file)
               count=0
               While Not Eof(file)
                  s.s=ReadString(file)
                  If Left(s, 9)="processor"
                     count+1
                  EndIf
               Wend
               
               CloseFile(file)
            EndIf
            
         Else
            dir=ExamineDirectory(#PB_Any, "/proc/acpi/processor", "")
            count=0
            If IsDirectory(dir)
               While NextDirectoryEntry(dir)
                  If Left(DirectoryEntryName(dir), 3)="CPU"
                     count+1
                  EndIf
               Wend
               FinishDirectory(dir)
            EndIf
         EndIf
         
         ProcedureReturn count
   CompilerEndSelect
EndProcedure
Procedure ParallelWorkerThread(dummy)
   Protected *Function, *CurrentValue, *CustomData
   
   Repeat
      WaitSemaphore(ParallelStart)     ; wait for "weakup" - semaphore
      WaitSemaphore(ParallelRunning)   ; this one should be immediately available (allows to later know when the work is complete)
      *CustomData=*ParallelCustomData
      *Function=*ParallelFunc
      *CurrentValue=*ParallelValue     ; copy the values locally
      SignalSemaphore(ParallelStarted) ; signal the "startup-complete" semaphore so the next thread can be started
      CallFunctionFast(*Function, *CurrentValue, *CustomData)  ; execute function
      SignalSemaphore(ParallelRunning) ; signal completion of the job
   ForEver
EndProcedure
Procedure CallParallel(*Function, *CurrentValue, *CustomData=#Null) ; Call a function in parallel (only if multiple processors are available)
   If ParallelCPUCount>1 And (ParallelLocked Or TryLockMutex(ParallelMutex))
      ParallelLocked=1
      
      ; specify the parallel function (with its custom data if necessary)
      *ParallelFunc=*Function
      *ParallelCustomData=*CustomData
      
      ; release one thread at a time so they can access the "CurrentValue"
      ; only the thread startup is serialized. the Loop() is run in parallel
      *ParallelValue=*CurrentValue
      SignalSemaphore(ParallelStart)
      WaitSemaphore(ParallelStarted)
   Else
      CallFunctionFast(*Function, *CurrentValue, *CustomData)
   EndIf
EndProcedure
Procedure FinishParallel() ; Waits for all running jobs to complete
   Define i
   If ParallelLocked
      ; wait for all worker threads to complete their work
      For i=1 To ParallelCPUCount
         WaitSemaphore(ParallelRunning)
      Next
      
      ; reset the running count semaphore back to the start value
      For i=1 To ParallelCPUCount
         SignalSemaphore(ParallelRunning);, ProcessorCount)
      Next
      ParallelLocked=0
      UnlockMutex(ParallelMutex)
   EndIf
EndProcedure
Procedure InitParallel(MinThreads=1) ; Initializes the parallel code ('MinThreads' >= your actual processor/core count)
   ParallelCPUCount=GetCPUCount()
   If ParallelCPUCount<MinThreads
      ParallelCPUCount=MinThreads
   EndIf
   
   If ParallelCPUCount>1
      ParallelMutex=CreateMutex()
      ParallelStart=CreateSemaphore(0)                   ;max=1
      ParallelStarted=CreateSemaphore(0)                 ;max=1
      ParallelRunning=CreateSemaphore(ParallelCPUCount)  ;max= ProcessorCount
      
      Protected i
      For i=1 To ParallelCPUCount
         CreateThread(@ParallelWorkerThread(), 0)
      Next
      
      CompilerIf #PB_Compiler_Thread=0
         MessageRequester("Init Parallel", "Initialization failed because your executable is not compiled in ThreadSafe mode.")
         End
      CompilerEndIf
   EndIf
EndProcedure
DisableExplicit

; ********************
; Example
; ********************

InitParallel()

Define maxX=1024
Define maxY=1024
;Define img=CreateImage(#PB_Any, maxX, maxY, 32)

img=CreateImage(#PB_Any, maxX, maxY, 32)
imgHeight = ImageHeight(img)

Define x, y

Procedure DrawingPlots(y, img)
   StartDrawing(ImageOutput(img))
      Define x
      For x=0 To ImageWidth(img)-1
         Define xx, yy, zz
         xx=x%256
         xx/#PI
         xx=Pow(Sin(xx)*$F, 7.3)
         yy=y%512
         yy/#PI
         yy=Pow(Cos(xx/3+yy)*$F, xx)
         zz=(xx ! yy) & $FF
         Plot(x, y, RGB(xx, yy, zz))
      Next
   StopDrawing()
EndProcedure

Define.i  duration1, duration2, duration3, release
Define.stDraw draw1, draw2

Procedure   SingleThread( *draw.stDraw )

  While *draw\y < *draw\loop
     DrawingPlots(*draw\y, img)
     *draw\y + 1
  Wend
  
  *draw\release + 1

EndProcedure

; Threaded
Delay(100)
draw1\release=0
draw1\loop = ImageHeight(img) / 2
draw1\y = 0

draw2\release=0
draw2\loop = ImageHeight(img)
draw2\y = ImageHeight(img) / 2


startTime=ElapsedMilliseconds()
CreateThread( @SingleThread(), @draw1 )
CreateThread( @SingleThread(), @draw2 )

Repeat
 Delay(1)
Until draw1\release And draw2\release

duration3=ElapsedMilliseconds()-startTime



;/// Linear Coding
Delay(100)
startTime=ElapsedMilliseconds()
For y=0 To imgHeight-1
   DrawingPlots(y, img)
Next
duration1=ElapsedMilliseconds()-startTime

;/// Parallel Coding
Delay(100)
startTime=ElapsedMilliseconds()
For y=0 To imgHeight-1
   CallParallel(@DrawingPlots(), y, img)
Next
FinishParallel()
duration2=ElapsedMilliseconds()-startTime

MessageRequester("Terminated ", "Threaded: " + Str(duration3) + #LF$+"Time: "+Str(duration1)+#LF$+"Time (optimized, CPU count="+Str(GetCPUCount())+"): "+Str(duration2)+#LF$)

OpenWindow(0, 0, 0, 800, 600, "Parallel Coding", #PB_Window_ScreenCentered | #PB_Window_SystemMenu)
ScrollAreaGadget(1, 0, 0, 800, 600, maxX, maxY)
ImageGadget(0, 0, 0, 0, 0, ImageID(img))
CloseGadgetList()

Repeat : Until WaitWindowEvent()=#PB_Event_CloseWindow

I may not help with your coding
Just ask about mental issues!

http://www.lulu.com/spotlight/kingwolf
http://www.sen3.net
User avatar
NoahPhense
Addict
Addict
Posts: 1999
Joined: Thu Oct 16, 2003 8:30 pm
Location: North Florida

Re: CallParallel : parallel coding for PB 4.41+

Post by NoahPhense »

Very nice, the graphics are viewable after the processes are done. Is there a way to show the working progress? If not in the graphics side of things, maybe in a debug window...

- np
Post Reply