Memparalelkan for-loop dengan Python


35

Apakah ada alat dalam Python yang seperti parl Matlab? Saya menemukan utas ini , tetapi sudah berusia empat tahun. Saya pikir mungkin seseorang di sini mungkin memiliki pengalaman yang lebih baru.

Berikut adalah contoh jenis hal yang ingin saya pisahkan:

X = np.random.normal(size=(10, 3))
F = np.zeros((10, ))
for i in range(10):
    F[i] = my_function(X[i,:])

di mana my_functionmengambil ndarrayukuran (1,3)dan mengembalikan skalar.

Setidaknya, saya ingin menggunakan beberapa core secara bersamaan --- seperti parfor. Dengan kata lain, asumsikan sistem memori bersama dengan 8-ke-16 core.



Terima kasih, @ doug-lipinski. Contoh-contoh itu, seperti yang saya temukan saat mencari di Google, memiliki beberapa perhitungan sepele berdasarkan indeks iterasi. Dan mereka selalu mengklaim kode itu "sangat mudah." Contoh saya mendefinisikan array (mengalokasikan memori) di luar for-loop. Saya baik-baik saja melakukannya dengan cara lain; itulah cara saya melakukannya di Matlab. Bagian rumit yang tampaknya melawan contoh-contoh tersebut adalah mendapatkan bagian dari array yang diberikan ke fungsi di dalam loop.
Paul G. Constantine

Jawaban:


19

Joblib melakukan apa yang Anda inginkan. Pola penggunaan dasar adalah:

from joblib import Parallel, delayed

def myfun(arg):
     do_stuff
     return result

results = Parallel(n_jobs=-1, verbose=verbosity_level, backend="threading")(
             map(delayed(myfun), arg_instances))

di mana arg_instancesdaftar nilai yang myfundihitung secara paralel. Batasan utamanya adalah yang myfunharus menjadi fungsi tingkat atas. The backendParameter dapat berupa "threading"atau "multiprocessing".

Anda dapat melewatkan parameter umum tambahan ke fungsi paralel. Tubuh myfunjuga dapat merujuk pada variabel global yang diinisialisasi, nilai-nilai yang akan tersedia untuk anak-anak.

Args dan hasil bisa sangat banyak dengan backend threading tetapi hasilnya harus serializable dengan backend multiprosesing.


Dask juga menawarkan fungsionalitas serupa. Mungkin lebih baik jika Anda bekerja dengan data inti atau Anda mencoba untuk memparalelkan perhitungan yang lebih kompleks.


Saya melihat nilai tambah nol untuk menggunakan baterai termasuk multiprosesing. Saya berani bertaruh bahwa joblib menggunakannya di bawah tenda.
Xavier Combelle

1
Harus disebutkan bahwa joblib bukanlah sihir, threadingbackend menderita bottleneck GIL dan multiprocessingbackend membawa overhead yang besar karena serialisasi semua parameter dan nilai pengembalian. Lihat jawaban ini untuk detail level rendah dari pemrosesan paralel dengan Python.
Jakub Klinkovský

Saya tidak dapat menemukan kombinasi kompleksitas fungsi dan jumlah iterasi yang joblib akan lebih cepat daripada for-loop. Bagi saya, ia memiliki kecepatan yang sama jika n_jobs = 1, dan jauh lebih lambat dalam semua kasus lainnya
Aleksejs Fomins

@AleksejsFomins Paralelisme berbasis thread tidak akan membantu untuk kode yang tidak merilis GIL tetapi sejumlah besar melakukannya, terutama ilmu data atau perpustakaan numerik. Kalau tidak, Anda perlu mutiproses, Jobli mendukung keduanya. Modul multiprosesing sekarang juga memiliki paralel mapyang dapat Anda gunakan secara langsung. Juga jika Anda menggunakan mkl kompilasi numpy itu akan memparalelkan operasi vektor secara otomatis tanpa Anda melakukan apa pun. Numpy di Ananconda adalah mkl diaktifkan secara default. Tidak ada solusi universal. Joblib sangat cerewet dan ada sedikit aksi di tahun 2015.
Daniel Mahler

Terima kasih atas saranmu. Saya ingat pernah mencoba multi-pemrosesan sebelum dan bahkan menulis beberapa posting, karena itu tidak skala seperti yang saya harapkan. Mungkin saya harus melihatnya lagi
Aleksejs Fomins

9

Apa yang Anda cari adalah Numba , yang dapat memparalelkan for for loop secara otomatis. Dari dokumentasi mereka

from numba import jit, prange

@jit
def parallel_sum(A):
    sum = 0.0
    for i in prange(A.shape[0]):
        sum += A[i]

    return sum

8

Tanpa mengasumsikan sesuatu yang istimewa dalam my_functionmemilih multiprocessing.Pool().map()adalah tebakan yang baik untuk menyejajarkan loop sederhana tersebut. joblib, dask, mpiPerhitungan atau numbaseperti yang diusulkan dalam jawaban lainnya terlihat tidak membawa keuntungan apapun untuk kasus-kasus penggunaan tersebut dan menambahkan dependensi tidak berguna (untuk meringkas mereka berlebihan). Menggunakan threading seperti yang diusulkan dalam jawaban lain tidak mungkin menjadi solusi yang baik, karena Anda harus akrab dengan interaksi GIL dari kode Anda atau kode Anda harus melakukan input / output.

Yang mengatakan numbamungkin ide yang baik untuk mempercepat kode python murni berurutan, tapi saya merasa ini di luar ruang lingkup pertanyaan.

import multiprocessing
import numpy as np

if __name__ == "__main__":
   #the previous line is necessary under windows to not execute 
   # main module on each child under windows

   X = np.random.normal(size=(10, 3))
   F = np.zeros((10, ))

   pool = multiprocessing.Pool(processes=16)
   # if number of processes is not specified, it uses the number of core
   F[:] = pool.map(my_function, (X[i,:] for i in range(10)) )

Namun ada beberapa peringatan (tapi yang seharusnya tidak mempengaruhi sebagian besar aplikasi):

  • di bawah windows tidak ada dukungan fork, jadi penerjemah dengan modul utama diluncurkan pada setiap anak, sehingga mungkin ada overhead (ad itu alasan untuk if __name__ == "__main__"
  • Argumen dan hasil dari my_function acar dan tidak dikekang, mungkin overhead terlalu besar, lihat jawaban ini untuk menguranginya https://stackoverflow.com/a/37072511/128629 . Ini juga membuat objek yang tidak dapat dipilih menjadi tidak dapat digunakan
  • my_functionseharusnya tidak bergantung pada status bersama seperti berkomunikasi dengan variabel global karena status tidak dibagi di antara proses. fungsi murni (fungsi dalam indera matematika) adalah contoh fungsi yang tidak berbagi status

6

Kesan saya tentang parfor adalah bahwa MATLAB merangkum detail implementasi, sehingga bisa menggunakan paralelisme memori bersama (yang Anda inginkan) dan paralelisme memori terdistribusi (jika Anda menjalankan server komputasi terdistribusi MATLAB ).

Jika Anda ingin paralelisme memori bersama, dan Anda menjalankan semacam tugas paralel, paket pustaka standar multiprosesing mungkin yang Anda inginkan, mungkin dengan front-end yang bagus, seperti joblib , seperti yang disebutkan dalam posting Doug. Perpustakaan standar tidak akan hilang, dan dipelihara, sehingga berisiko rendah.

Ada opsi lain di luar sana juga, seperti Parallel Python dan kemampuan paralel IPython . Pandangan sekilas pada Python Paralel membuat saya berpikir bahwa ini lebih dekat dengan semangat parfor, karena perpustakaan merangkum detail untuk kasing terdistribusi, tetapi biaya untuk melakukannya adalah Anda harus mengadopsi ekosistemnya. Biaya menggunakan IPython serupa; Anda harus mengadopsi cara IPython dalam melakukan sesuatu, yang mungkin atau mungkin tidak sepadan bagi Anda.

Jika Anda peduli dengan memori yang didistribusikan, saya sarankan mpi4py . Lisandro Dalcin bekerja dengan baik, dan mpi4py digunakan dalam pembungkus PETSc Python, jadi saya tidak berpikir itu akan hilang dalam waktu dekat. Seperti multiprocessing, ini adalah antarmuka tingkat rendah (er) untuk paralelisme daripada parfor, tetapi yang cenderung bertahan untuk sementara waktu.


Terima kasih, @ Geoff. Apakah Anda punya pengalaman bekerja dengan perpustakaan ini? Mungkin saya akan mencoba menggunakan mpi4py pada mesin memori / prosesor multicore bersama.
Paul G. Constantine

@PaulGConstantine Saya berhasil menggunakan mpi4py; itu cukup menyakitkan, jika Anda terbiasa dengan MPI. Saya belum pernah menggunakan multiprosesor, tetapi saya merekomendasikannya kepada kolega, yang mengatakan itu bekerja dengan baik untuk mereka. Saya telah menggunakan IPython juga, tetapi bukan fitur paralelisme, jadi saya tidak bisa bicara seberapa bagus kerjanya.
Geoff Oxberry

1
Aron memiliki tutorial mpi4py yang bagus yang ia persiapkan untuk kursus PyHPC di Supercomputing: github.com/pyHPC/pyhpc-tutorial
Matt Knepley

4

Sebelum mencari alat "kotak hitam", yang dapat digunakan untuk menjalankan fungsi python paralel "generik", saya akan menyarankan untuk menganalisis bagaimana my_function()dapat diparalelkan dengan tangan.

Pertama, bandingkan waktu eksekusi my_function(v)untuk foroverhead python loop: [C] Python forloop sangat lambat, sehingga waktu yang dihabiskan my_function()bisa diabaikan.

>>> timeit.timeit('pass', number=1000000)
0.01692986488342285
>>> timeit.timeit('for i in range(10): pass', number=1000000)
0.47521495819091797
>>> timeit.timeit('for i in xrange(10): pass', number=1000000)
0.42337894439697266

Pemeriksaan kedua jika ada implementasi vektor sederhana my_function(v)yang tidak memerlukan loop:F[:] = my_vector_function(X)

(Dua poin pertama ini cukup sepele, maafkan saya jika saya menyebutkannya di sini hanya untuk kelengkapan.)

Ketiga dan yang paling poin penting, setidaknya untuk implementasi CPython, adalah untuk memeriksa apakah my_functionmenghabiskan sebagian besar itu waktu di dalam atau di luar yang juru kunci global yang , atau GIL . Jika waktu dihabiskan di luar GIL, maka threadingmodul perpustakaan standar harus digunakan. ( Ini contohnya). BTW, orang bisa menganggap menulis my_function()sebagai ekstensi C hanya untuk melepaskan GIL.

Akhirnya, jika my_function()tidak merilis GIL, seseorang dapat menggunakan multiprocessingmodul .

Referensi: Python docs tentang Eksekusi Bersamaan , dan intro numpy / scipy pada pemrosesan paralel .


2

Anda dapat mencoba Julia. Ini cukup dekat dengan Python, dan memiliki banyak konstruksi MATLAB. Terjemahan di sini adalah:

F = @parallel (vcat) for i in 1:10
    my_function(randn(3))
end

Ini membuat angka acak menjadi paralel juga, dan hanya menyatukan hasilnya pada akhirnya selama pengurangan. Itu menggunakan multiprocessing (jadi Anda perlu melakukan addprocs(N)untuk menambahkan proses sebelum menggunakan, dan ini juga berfungsi pada beberapa node pada HPC seperti yang ditunjukkan dalam posting blog ini ).

Anda juga bisa menggunakan pmap:

F = pmap((i)->my_function(randn(3)),1:10)

Jika Anda menginginkan paralelisme utas, Anda dapat menggunakan Threads.@threads(walaupun pastikan Anda membuat algoritme yang aman untuk thread). Sebelum membuka Julia, setel variabel lingkungan JULIA_NUM_THREADS, lalu itu:

Ftmp = [Float64[] for i in Threads.nthreads()]
Threads.@threads for i in 1:10
    push!(Ftmp[Threads.threadid()],my_function(randn(3)))
end
F = vcat(Ftmp...)

Di sini saya membuat array terpisah untuk setiap utas, sehingga mereka tidak berbenturan ketika menambahkan ke array, kemudian hanya menggabungkan array setelahnya. Threading cukup baru sehingga saat ini hanya ada penggunaan langsung dari thread, tapi saya yakin pengurangan dan peta yang di-threaded akan ditambahkan sama seperti untuk multiprocessing.


Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.