Page 1 sur 2

ParallelFor et MultiThreading

Publié : dim. 05/avr./2009 18:39
par Atomo
Salut à tous,
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
Exemples :

(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
(ProcedureReturn, le paramètre wait doit être sur #True sinon ce sera le numéro du parallelfor qui sera renvoyé)

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

Publié : lun. 06/avr./2009 16:30
par Anonyme2
Intéressant le gain, processeur Intel (R) Core(TM) 2 CPU 4300 1,8GHz 1,8 GHz, 2 Go RAM, Windows Vista Edition familiale Premium 32 bits.

Voici 3 tests de vitesse

Sans parallelfor : 8502ms
Avec parallelfor : 6521ms

Sans parallelfor : 8330ms
Avec parallelfor : 6646ms

Sans parallelfor : 8315ms
Avec parallelfor : 6443ms

Publié : lun. 06/avr./2009 18:57
par Anonyme
voila un correctif pour rendre le code compatible linux :

Code : Tout sélectionner

CompilerIf #PB_Compiler_OS = #PB_OS_Windows
	Global Total_CPU = Val(GetEnvironmentVariable("NUMBER_OF_PROCESSORS"))-1
CompilerEndIf

CompilerIf #PB_Compiler_OS = #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$)
EndIf 
CompilerEndIf

Publié : lun. 06/avr./2009 19:16
par erix14
Merci pour cette démo :)
Intel(R) Core(TM)2 Duo CPU E6750 @ 2.66 GHz
Windows 7 Ultimate (Build 7000)

Sans parallelfor : 5413ms
Avec parallelfor : 3682ms

Sans parallelfor : 5382ms
Avec parallelfor : 3635ms

Publié : lun. 06/avr./2009 19:35
par Atomo
Merci pour vos retours, j'ai modifié le include avec le code de Cpl.Bator.

Publié : lun. 06/avr./2009 19:38
par flaith
WinXP SP3
Intel Core2Quad Q9550 2.83Ghz

Sans : 5609ms
Avec : 3688ms

Sans : 5610ms
Avec : 3562ms

:wink:

Publié : lun. 06/avr./2009 19:44
par Progi1984
Cpl.Bator a écrit :voila un correctif pour rendre le code compatible linux :

Code : Tout sélectionner

CompilerIf #PB_Compiler_OS = #PB_OS_Windows
	Global Total_CPU = Val(GetEnvironmentVariable("NUMBER_OF_PROCESSORS"))-1
CompilerEndIf

CompilerIf #PB_Compiler_OS = #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$)
EndIf 
CompilerEndIf
As tu pensé à utiliser au CompilerSelect ?

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$)
    EndIf 
  ;}
CompilerEndSelect

Publié : lun. 06/avr./2009 19:52
par Jacobus
Intel(R) Core(TM)2 Duo CPU E8400 @ 3 GHz / 2.66 GHz
Windows Vista Ultimate sp1

Sans parallelfor = 5429
avec parallelfor = 3416

Publié : lun. 06/avr./2009 20:11
par Ollivier
Avec : 33188ms
Sans : 30281ms

VIA C7-M 797MHz

(Voix de roquet branleur) "Alors les aminches! On fait les durs avec vos multi-core?"

Ollivier

Publié : mar. 07/avr./2009 0:40
par Guimauve
Bonjour à tous,

Moi j'ai des résultats du genre :

Sans parallelFor 9641 ms
Avec parallelFor 8594 ms

Sur ma vieille guimbarde :

AMD Athlon XP2200+
1.80GHz
1.00 Go RAM

A+
Guimauve

Publié : mar. 07/avr./2009 6:29
par cederavic
Impressionant!
AMD Phenom x4 9950 + Vista Ultimate x64
Sans : 5535
Avec : 1928

Publié : mar. 07/avr./2009 7:51
par Jacobus
Ollivier a écrit :Avec : 33188ms
Sans : 30281ms

VIA C7-M 797MHz

(Voix de roquet branleur) "Alors les aminches! On fait les durs avec vos multi-core?"

Ollivier
Quand ils sont sortis ils devaient être plus balaises que les pentiums et au top sur les portables. Le tien est de quand ? 2004/2005 ? Faut que j'essaye sur mon amd sempron 800 Mhz. Le genre qui doit voisiner avec le tien. L'hyper-threading n'est pas supporté par tous les systèmes...

Publié : mar. 07/avr./2009 10:51
par Atomo
cederavic a écrit :Impressionant!
AMD Phenom x4 9950 + Vista Ultimate x64
Sans : 5535
Avec : 1928
Ce qui est étonnant c'est que tu obtiens des performances doubles par rapport à flaith alors que vous avez vous 2 des QuadeCore. :?

Publié : mar. 07/avr./2009 10:54
par cederavic
Peut -être parceque j'ai la black edition du phenom 9950 et que par conséquent, on va dir que quelques réglages on été modifiés :)
Carte mere Gigabyte MA790-FX DQ6 si ça peut être utile aussi...

Publié : mar. 07/avr./2009 13:42
par Ollivier
@Jacobus

Ne t'inquiète pas! Je l'ai payé 99€ neuf en mi 2008!! A ce prix-là, je ne suis pas exigent! Dis-toi que je ne travaille qu'avec ça!!!

Alors, on dira que je vous regarde avec des yeux de nain de jardin, vous qui avez des configs à 500€ minimum! :D

Ollivier