Ich habe das Beispiel für die parallele Berechnung aus der Python-Dokumentation auf F # portiert. Ich denke, dass Sie beginnen können, indem Sie die Schreibstile vergleichen.
GIL
Ich habe Multithreading in Python versucht.
Ich habe versucht, parallel zu rechnen, aber es hat sich nicht viel verbessert. Die folgenden Artikel werden erwähnt.
Python verwendet "Global Interpreter Lock (GIL)" als Mechanismus, um die Konsistenz der Programmausführung zu gewährleisten. blockquote>Anscheinend wird Python Thread unterstützt, um "Blockieren von E / A" beim Tätigen von Systemaufrufen zu handhaben ... Es scheint, dass es nicht in erster Linie darauf abzielt, zu beschleunigen ... .. blockquote>In der Dokumentation wird erklärt, wie.
(Global Interpreter Lock) Ein Mechanismus, der vom CPython-Interpreter verwendet wird, um sicherzustellen, dass jeweils nur ein Thread Python-Bytecode ausführt. Dies vereinfacht die Implementierung von CPython, indem das Objektmodell (einschließlich wichtiger integrierter Typen wie dict) für den gleichzeitigen Zugriff implizit sicher gemacht wird. Das Sperren des gesamten Interpreters erleichtert das Multithreading des Interpreters auf Kosten der Parallelisierung von Multiprozessor-Maschinen.
Einige Standard- oder externe Erweiterungsmodule sind jedoch so konzipiert, dass GIL bei umfangreichen Verarbeitungsvorgängen wie Komprimierung und Hashing deaktiviert wird. Darüber hinaus wird GIL bei der E / A-Verarbeitung immer freigegeben.
In der Vergangenheit wurden "freie Multithread-Interpreter" entwickelt (die die in Betrieb befindlichen Daten mit einer feineren Körnung sperren), die jedoch aufgrund der schlechten Leistung auf einem typischen Einzelprozessor nicht erfolgreich waren. Es wird angenommen, dass Versuche, dieses Leistungsproblem zu überwinden, die Implementierung komplexer machen und die Wartungskosten erhöhen.
ProcessPoolExecutor
Parallele Berechnungen in Python sind eher Multiprozess- als Multithread-Berechnungen. ProcessPoolExecutor kümmert sich um die Verwaltung von Prozessen und den Aufruf von Funktionen zwischen Prozessen. Standardmäßig werden so viele Arbeitsprozesse erstellt, wie logische Prozessoren vorhanden sind, und die Verarbeitung wird zugewiesen.
Ändern Sie das ProcessPoolExecutor-Beispiel in der Dokumentation. Überprüfen Sie die erforderliche Zeit, indem Sie zwischen parallel und seriell wechseln. Sie können es mit Multithreading vergleichen, indem Sie es durch ThreadPoolExecutor ersetzen.
parallel-sample.py
import concurrent.futures import math PRIMES = [ 112272535095293, 112582705942171, 112272535095293, 115280095190773, 115797848077099, 1099726899285419] def is_prime(n): if n < 2: return False if n == 2: return True if n % 2 == 0: return False sqrt_n = int(math.floor(math.sqrt(n))) for i in range(3, sqrt_n + 1, 2): if n % i == 0: return False return True def main_parallel(cls): with cls() as executor: for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)): print('%d is prime: %s' % (number, prime)) def main(): for number, prime in zip(PRIMES, map(is_prime, PRIMES)): print('%d is prime: %s' % (number, prime)) if __name__ == '__main__': import sys if sys.argv[-1] == "--process": main_parallel(concurrent.futures.ProcessPoolExecutor) elif sys.argv[-1] == "--thread": main_parallel(concurrent.futures.ThreadPoolExecutor) else: main()
Ausführungsergebnis
$ time python parallel-sample.py 112272535095293 is prime: True 112582705942171 is prime: True 112272535095293 is prime: True 115280095190773 is prime: True 115797848077099 is prime: True 1099726899285419 is prime: False real 0m2.745s user 0m2.703s sys 0m0.031s $ time python parallel-sample.py --process 112272535095293 is prime: True 112582705942171 is prime: True 112272535095293 is prime: True 115280095190773 is prime: True 115797848077099 is prime: True 1099726899285419 is prime: False real 0m0.983s user 0m3.984s sys 0m0.172s $ time python parallel-sample.py --thread 112272535095293 is prime: True 112582705942171 is prime: True 112272535095293 is prime: True 115280095190773 is prime: True 115797848077099 is prime: True 1099726899285419 is prime: False real 0m5.527s user 0m5.422s sys 0m0.109s
Multiprozess beschleunigt, Multithreading verlangsamt sich jedoch.
F#
Port nach F #. Da die Verwendung von Multithreading in .NET Framework kein Problem darstellt, wird die Implementierung von Multiprocessing weggelassen.
parallel-sample.fsx
let PRIMES = [ 112272535095293L 112582705942171L 112272535095293L 115280095190773L 115797848077099L 1099726899285419L] let is_prime n = if n < 2L then false elif n = 2L then true elif n % 2L = 0L then false else let sqrt_n = int64 (floor (sqrt (float n))) seq { for i in {3L .. 2L .. sqrt_n + 1L} do if n % i = 0L then yield false yield true } |> Seq.head let is_prime_async n = async { return is_prime n } let main_parallel() = PRIMES |> Seq.map is_prime_async |> Async.Parallel |> Async.RunSynchronously |> Seq.zip PRIMES |> Seq.iter (fun (number, prime) -> printfn "%d is prime: %b" number prime) let main() = PRIMES |> Seq.map is_prime |> Seq.zip PRIMES |> Seq.iter (fun (number, prime) -> printfn "%d is prime: %b" number prime) match Array.last (System.Environment.GetCommandLineArgs()) with | "--thread" -> main_parallel() | _ -> main()
Ausführungsergebnis
$ time mono parallel-sample.exe 112272535095293 is prime: true 112582705942171 is prime: true 112272535095293 is prime: true 115280095190773 is prime: true 115797848077099 is prime: true 1099726899285419 is prime: false real 0m0.662s user 0m0.625s sys 0m0.031s $ time mono parallel-sample.exe --thread 112272535095293 is prime: true 112582705942171 is prime: true 112272535095293 is prime: true 115280095190773 is prime: true 115797848077099 is prime: true 1099726899285419 is prime: false real 0m0.308s user 0m0.813s sys 0m0.078s
Der Effekt ist schwer zu erkennen, es sei denn, er ist etwas schwerer, aber die Geschwindigkeit verbessert sich.
Prozessablauf
Anhand der Typen ist leicht zu erkennen, was mit "Async" und "Async" passiert.
Asynchronisierungsfunktion
let is_prime n = ... // int64 -> bool let is_prime_async n = ... // int64 -> Async<bool>
Liste der Argumente anwenden
PRIMES |> Seq.map is_prime_async // seq<Async<bool>>
Kombinieren Sie mehrere asynchrone Prozesse
|> Async.Parallel // Async<bool array>
Führt eine asynchrone Verarbeitung aus und gibt das Ergebnis als Array zurück
|> Async.RunSynchronously // bool array
Parallele Berechnungen werden im Thread-Pool durchgeführt. Selbst wenn Sie eine große Anzahl von Berechnungen gleichzeitig bestehen, werden diese entsprechend geplant.
Recommended Posts