Portiertes Python-Parallelberechnungsbeispiel auf F #

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.
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 ... ..

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