Anyway, this example is not really finished and I will surely advance it further,
but you will get the idea
So, I wanted an easy method of using all my CPU cores to do complex calculations.
I didn't really test it yet, but if I didn't really do a great mistake (possible,
cause I'm in a hurry right now ), it really is twice as fast now.
Ok, let's see:
1. Example, use the standard way. 2 For-Loops to calculate an array of values.
I do it for 2 seconds and count how many times it calculated:
Code: Select all
DisableDebugger ;- EXAMPLE maxX = 1400 maxY = 1050 Global Dim a.d(maxX, maxY) Delay(100) time = ElapsedMilliseconds() counter = 0 Repeat For y = 0 To maxY For x = 0 To maxX c.l = (Int(y) ! Int(x)) & $FF a(x, y) = Pow(c, x) Next Next counter + 1 Until ElapsedMilliseconds() - time > 2000 MessageRequester("Fertig", "Durchläufe: "+Str(counter))
Ok, now let's try the ParallelFor:
Code: Select all
DisableDebugger EnableExplicit CompilerIf #PB_Compiler_Thread CompilerElse CompilerError "In order to use ParallelFor, you have to compile as thread safe." CompilerEndIf ; a procedure to get the number of CPUs/Cores ; somebody could add the windows version ;) Procedure.l GetCPUCount() CompilerIf #PB_Compiler_OS = #PB_OS_Linux Protected file.l, count.l, s.s, dir.l 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 CompilerElse ProcedureReturn 2 CompilerEndIf EndProcedure ; this is the callback procedure lookalike. If the callback returns #True, ; the thread will terminate. ; i.l = the variable that will be incremented like in "For i = 80 to 110" ; *dataPtr = a value you can specify that will be given to each callback Prototype.l ParallelLoopFunc(i.l, *dataPtr) ; internal structures Structure PARALLELLOOP i1.l ; starting number i2.l ; finishing number loop.ParallelLoopFunc ; callback prototype *dataPtr ; a value given to the callback EndStructure Structure PARALLELTHREAD ; a structure for ParallelForLinear() id.l ; ThreadID info.PARALLELLOOP ; Thread-Data for running the callback EndStructure ; This is the actual thread: Procedure _ParallelFor_Thread(*info.PARALLELLOOP) Protected i.l ; in *info, all the information is stored, like ; start and stop value for 'i' ; or the data to be given to the callback With *info ; now run the callback with 'i' as the first ; parameter exactly (\i1 - \i2 + 1) times For i = \i1 To \i2 ; call the Callback with 'i' and '*dataPtr' ; and check, if it returns #True. If \loop(i, \dataPtr) ; if returned #true, break and therefore ; terminate the thread Break EndIf Next EndWith ; I have finished my job -> terminate thread EndProcedure ; ParallelFor() is a procedure that will create 2 threads: ; i1 = thread1, start value ; i2 = thread1, stop value ; j1 = thread2, start value ; j2 = thread2, stop value ; loopFunc = pointer to the callback function ; *dataPtr = a value given to each callback function Procedure ParallelFor(i1.l, i2.l, j1.l, j2.l, loopFunc.ParallelLoopFunc, *dataPtr) Protected *th1, *th2 Protected info1.PARALLELLOOP, info2.PARALLELLOOP ; these requirements have to be fullfilled If loopFunc And i1 <= i2 And j1 <= j2 ; create the sub-job for thread 1: With info1 \i1 = i1 ; start \i2 = i2 ; stop \loop = loopFunc \dataPtr = *dataPtr EndWith *th1 = CreateThread(@_ParallelFor_Thread(), @info1) If *th1 ; if thread 1 has been created, create thread 2 With info2 \i1 = j1 \i2 = j2 \loop = loopFunc \dataPtr = *dataPtr EndWith *th2 = CreateThread(@_ParallelFor_Thread(), @info2) ; and now wait for both threads to terminate If *th2 WaitThread(*th1) WaitThread(*th2) Else WaitThread(*th1) EndIf EndIf EndIf EndProcedure ; ParallelForLinear() is a procedure that will create 1 or more threads ; and split the job into equal sub-jobs/threads ; i1 = start value ; i2 = stop value - be sure that i2 is significantly larger than i1 ; loopFunc = pointer to the callback function ; *dataPtr = a value given to each callback function ; cores = there you can specify, how many threads shall be generated. You can ; use GetCPUCount() for this to use all cores. Procedure ParallelForLinear(i1.l, i2.l, loopFunc.ParallelLoopFunc, *dataPtr, cores.l = 2) Protected NewList threads.PARALLELTHREAD() Protected z.l, stepSize.l, j.l, j2.l If loopFunc And i1 <= i2 And cores >= 1 ; stepSize is the range every thread has to process stepSize = (i2 - i1) / cores j = i1 ; first thread starts with i1 ; for each core, create a job For z = 1 To cores ; the 'stop' for the thread will be start+stepSize: j2 = j + stepSize If j2 > i2 j2 = i2 z = cores ; stop the For-Loop if no things for other cores EndIf If j > i2 ; just to be on the safe side. not tested that much Break EndIf ; create a new thread/sub-job AddElement(threads()) With threads()\info \i1 = j \i2 = j2 \loop = loopFunc \dataPtr = *dataPtr EndWith j + stepSize + 1 ; next thread will continue with the work Next ; now we will start all the threads ; it's now because I don't want to know what can happen if ; I access the LinkedList within the threads and still adding ; new elements ForEach threads() threads()\id = CreateThread(@_ParallelFor_Thread(), @threads()\info) If threads()\id = 0 ; if something went wrong, break up Break EndIf Next ; wait for each thread to stop ForEach threads() WaitThread(threads()\id) Next EndIf EndProcedure ;- HOWTO: ; In general: ; If you have a BIG For-Loop (BIG means either many many iterations, big ; loop-body or in general, looong execution times (so that the overhead ; produced by starting threads etc. doesn't matter)), then you just have ; to follow these few steps: ; - Be sure, that no iteration of the loop depends on another iteration ; i.e. you cannot use the result of a calculation that was done an ; iteration earlier. ; - Be sure, that you are able to synchronize the loop-body so that ; threads can safely do their work -> Thread safety! ; - Then just replace a for-loop like ; For x = 50 to 50000 ; with ; ParallelForLinear(50, 50000, @LoopBody(), @myData, GetCPUCount()) ; - Capsule the loop body into a LoopBody() procedure that follows the ; definition of the prototype ParallelLoopFunc ; and you should be done. ;- EXAMPLE DisableExplicit maxX = 1400 maxY = 1050 Global Dim a.d(maxX, maxY) Delay(100) time = ElapsedMilliseconds() counter = 0 Procedure InnerLoop(i.l, maxX.l) For x = 0 To maxX c.l = (Int(y) ! Int(x)) & $FF a(x, y) = Pow(c, x) Next EndProcedure Repeat ParallelForLinear(0, maxY, @InnerLoop(), maxX, GetCPUCount()) ; For y = 0 To maxY ; For x = 0 To maxX ; c.l = (Int(y) ! Int(x)) & $FF ; a(x, y) = Pow(c, x) ; Next ; Next counter + 1 Until ElapsedMilliseconds() - time > 2000 MessageRequester("Fertig", "Durchläufe: "+Str(counter))
Yeah, it doesn't scale up to 4 or more cores, but let's wait'n'see
Edit: Added comments and an alpha version of the linear scaling version