Ayant prit vent des nouvelles fonctionnalités de la version 4.40 de PureBasic concernant le multithreading, j’ai eu l’envie de confectionner une petite lib concernant le sujet en m’inspirant des travaux de Freaks.
L’objectif est de faire exécuter un morceau de code qui se trouve à l’intérieur d’une procédure par plusieurs threads simultanément.
J'obtient un gain de performance d'environ 30% sur ma machine équipée d'un Core2Duo, je n'ai pas pu tester les performances sur un triple ou quadruple coeurs.
Voici la liste des commandes :
- ParallelFor(id, wait.b, *procedure_ptr, parametre)
- P_For(variable, start)
- P_Next(variable)
- IsParallelFor(id) (renvoi 1 si les threads tournent toujours, sinon 0)
- KillParallelFor(id)
- PauseParallelFor(id)
- ResumeParallelFor(id)
/!\ Vous devez utiliser l'option threadsafe du compilateur !
/!\ Les procédures créées doivent contenir le parametre core et un autre paramètre optionnel !
Le fichier .pbi :
Code : Tout sélectionner
CompilerSelect #PB_Compiler_OS
CompilerCase #PB_OS_Windows ;{
Global Total_CPU = Val(GetEnvironmentVariable("NUMBER_OF_PROCESSORS"))-1
;}
CompilerCase #PB_OS_Linux ;{
grep = RunProgram("grep","-c processor /proc/cpuinfo","./",#PB_Program_Open|#PB_Program_Read)
If grep
Sortie$ + ReadProgramString(grep) + Chr(13)
Global Total_CPU = Val(Sortie$)-1
EndIf
;}
CompilerEndSelect
Global wait_thread = CreateSemaphore()
Global mutex = CreateMutex()
Macro P_For(variable, start)
For variable=start+core
EndMacro
Macro P_Next(variable)
variable+Total_CPU
Next variable
EndMacro
Structure ParallelFor
thread.l
core.l
parent.l
wait.b
return_procedure.l
EndStructure
Global NewList Working_Threads.ParallelFor()
Prototype ProtoLoop(core, parametre)
Global Loop.ProtoLoop
Procedure WorkerThread(parametre)
thread_id = Working_Threads()\thread
core = Working_Threads()\core
wait = Working_Threads()\wait
SignalSemaphore(wait_thread)
Resultat = Loop(core, parametre)
;Fin du thread
LockMutex(mutex)
ForEach Working_Threads()
If Working_Threads()\thread = thread_id
If wait = #False
;efface le thread
DeleteElement(Working_Threads())
Break
Else
;procedure return
If Working_Threads()\return_procedure = 0
Working_Threads()\return_procedure = Resultat
Break
EndIf
EndIf
EndIf
Next
UnlockMutex(mutex)
EndProcedure
Procedure ParallelFor(id, wait.b, *procedure_ptr, parametre)
Loop.ProtoLoop = *procedure_ptr
LockMutex(mutex)
For x=0 To Total_CPU
AddElement(Working_Threads())
Working_Threads()\core = x
Working_Threads()\thread = CreateThread(@WorkerThread(), parametre)
Working_Threads()\parent = id
Working_Threads()\wait = wait
If x=0
first_thread = Working_Threads()\thread
EndIf
WaitSemaphore(wait_thread)
Next x
UnlockMutex(mutex)
If wait = #True
For x=0 To Total_CPU
WaitThread(first_thread+x)
Next x
;procedurereturn et efface les threads
LockMutex(mutex)
ForEach Working_Threads()
If Working_Threads()\parent = id
Resultat = Working_Threads()\return_procedure
If Resultat
return_procedure = Resultat
EndIf
DeleteElement(Working_Threads())
EndIf
Next
UnlockMutex(mutex)
ProcedureReturn return_procedure
Else
ProcedureReturn id
EndIf
EndProcedure
Procedure IsParallelFor(id)
LockMutex(mutex)
ForEach Working_Threads()
If Working_Threads()\parent = id
ProcedureReturn 1
EndIf
Next
UnlockMutex(mutex)
EndProcedure
Procedure KillParallelFor(id)
LockMutex(mutex)
ForEach Working_Threads()
If Working_Threads()\parent = id
KillThread(Working_Threads()\thread)
DeleteElement(Working_Threads())
EndIf
Next
UnlockMutex(mutex)
EndProcedure
Procedure PauseParallelFor(id)
LockMutex(mutex)
ForEach Working_Threads()
If Working_Threads()\parent = id
PauseThread(Working_Threads()\thread)
EndIf
Next
UnlockMutex(mutex)
EndProcedure
Procedure ResumeParallelFor(id)
LockMutex(mutex)
ForEach Working_Threads()
If Working_Threads()\parent = id
ResumeThread(Working_Threads()\thread)
EndIf
Next
UnlockMutex(mutex)
EndProcedure
(Vitesse)
Code : Tout sélectionner
XIncludeFile "Multicore_Include.pbi"
#Size = 10000000
Global Dim tableau.s(#Size)
Declare single_crypte()
Declare multi_crypte(core, param)
Time = ElapsedMilliseconds()
single_crypte()
MessageRequester("Sans parallelfor", Str(ElapsedMilliseconds()-Time)+"ms")
Time = ElapsedMilliseconds()
ParallelFor(0, #true, @multi_crypte(), 0)
MessageRequester("Avec parallelfor", Str(ElapsedMilliseconds()-Time)+"ms")
Procedure single_crypte()
*Buffer = AllocateMemory(4)
For x=0 To #Size
PokeL(*Buffer, x)
tableau(x) = MD5Fingerprint(*Buffer, 4)
Next x
EndProcedure
Procedure multi_crypte(core, param)
*Buffer = AllocateMemory(4)
P_For(x, 0) To #Size
PokeL(*Buffer, x)
tableau(x) = MD5Fingerprint(*Buffer, 4)
P_Next(x)
EndProcedure
Code : Tout sélectionner
XIncludeFile "Multicore_Include.pbi"
Declare compter(core, nombre)
Resultat = ParallelFor(0, #True, @compter(), 55)
Debug Resultat
Procedure compter(core, nombre)
P_For(x, 0) To 100
A=x
P_Next(x)
ProcedureReturn nombre*10
EndProcedure