ParallelFor et MultiThreading

Vous débutez et vous avez besoin d'aide ? N'hésitez pas à poser vos questions
Atomo
Messages : 207
Inscription : lun. 17/sept./2007 12:27

ParallelFor et MultiThreading

Message 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
Dernière modification par Atomo le mar. 07/avr./2009 10:58, modifié 4 fois.
Anonyme2
Messages : 3518
Inscription : jeu. 22/janv./2004 14:31
Localisation : Sourans

Message 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
Anonyme

Message 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
erix14
Messages : 480
Inscription : sam. 27/mars/2004 16:44
Contact :

Message 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
Atomo
Messages : 207
Inscription : lun. 17/sept./2007 12:27

Message par Atomo »

Merci pour vos retours, j'ai modifié le include avec le code de Cpl.Bator.
Avatar de l’utilisateur
flaith
Messages : 1487
Inscription : jeu. 07/avr./2005 1:06
Localisation : Rennes
Contact :

Message par flaith »

WinXP SP3
Intel Core2Quad Q9550 2.83Ghz

Sans : 5609ms
Avec : 3688ms

Sans : 5610ms
Avec : 3562ms

:wink:
Avatar de l’utilisateur
Progi1984
Messages : 2659
Inscription : mar. 14/déc./2004 13:56
Localisation : France > Rennes
Contact :

Message 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
Avatar de l’utilisateur
Jacobus
Messages : 1559
Inscription : mar. 06/avr./2004 10:35
Contact :

Message 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
Quand tous les glands seront tombés, les feuilles dispersées, la vigueur retombée... Dans la morne solitude, ancré au coeur de ses racines, c'est de sa force maturité qu'il renaîtra en pleine magnificence...Jacobus.
Ollivier
Messages : 4197
Inscription : ven. 29/juin/2007 17:50
Localisation : Encore ?
Contact :

Message 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
Guimauve
Messages : 1015
Inscription : mer. 11/févr./2004 0:32
Localisation : Québec, Canada

Message 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
Avatar de l’utilisateur
cederavic
Messages : 1338
Inscription : lun. 09/févr./2004 23:38
Localisation : Bordeaux

Message par cederavic »

Impressionant!
AMD Phenom x4 9950 + Vista Ultimate x64
Sans : 5535
Avec : 1928
Avatar de l’utilisateur
Jacobus
Messages : 1559
Inscription : mar. 06/avr./2004 10:35
Contact :

Message 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...
Quand tous les glands seront tombés, les feuilles dispersées, la vigueur retombée... Dans la morne solitude, ancré au coeur de ses racines, c'est de sa force maturité qu'il renaîtra en pleine magnificence...Jacobus.
Atomo
Messages : 207
Inscription : lun. 17/sept./2007 12:27

Message 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. :?
Avatar de l’utilisateur
cederavic
Messages : 1338
Inscription : lun. 09/févr./2004 23:38
Localisation : Bordeaux

Message 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...
Ollivier
Messages : 4197
Inscription : ven. 29/juin/2007 17:50
Localisation : Encore ?
Contact :

Message 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
Répondre