Saya menggunakan dekorator numbas @jit untuk menambahkan dua array numpy dengan python. Performanya sangat tinggi jika saya menggunakan @jit dibandingkan dengan python.

Namun tidak menggunakan semua inti CPU bahkan jika saya memasukkan @numba.jit(nopython = True, parallel = True, nogil = True).

Apakah ada cara untuk menggunakan semua inti CPU dengan numba @jit.

Ini kode saya:

import time                                                
import numpy as np                                         
import numba                                               

SIZE = 2147483648 * 6                                      

a = np.full(SIZE, 1, dtype = np.int32)                     

b = np.full(SIZE, 1, dtype = np.int32)                     

c = np.ndarray(SIZE, dtype = np.int32)                     

@numba.jit(nopython = True, parallel = True, nogil = True) 
def add(a, b, c):                                          
    for i in range(SIZE):                                  
        c[i] = a[i] + b[i]                                 

start = time.time()                                        
add(a, b, c)                                               
end = time.time()                                          

print(end - start)                                        
18
user8353052 10 Agustus 2017, 12:51

2 jawaban

Jawaban Terbaik

Anda dapat meneruskan parallel=True ke fungsi numba jitted apa pun, tetapi itu tidak berarti selalu menggunakan semua inti. Anda harus memahami bahwa numba menggunakan beberapa heuristik untuk membuat kode dieksekusi secara paralel, terkadang heuristik ini tidak menemukan apa pun untuk diparalelkan dalam kode. Saat ini ada permintaan tarik sehingga mengeluarkan Peringatan jika tidak memungkinkan itu "sejajar". Jadi ini lebih seperti parameter "tolong buat itu dijalankan secara paralel jika memungkinkan" bukan "menerapkan eksekusi paralel".

Namun Anda selalu dapat menggunakan utas atau proses secara manual jika Anda benar-benar tahu bahwa Anda dapat memparalelkan kode Anda. Cukup mengadaptasi contoh penggunaan multi-threading dari numba docs:

#!/usr/bin/env python
from __future__ import print_function, division, absolute_import

import math
import threading
from timeit import repeat

import numpy as np
from numba import jit

nthreads = 4
size = 10**7  # CHANGED

# CHANGED
def func_np(a, b):
    """
    Control function using Numpy.
    """
    return a + b

# CHANGED
@jit('void(double[:], double[:], double[:])', nopython=True, nogil=True)
def inner_func_nb(result, a, b):
    """
    Function under test.
    """
    for i in range(len(result)):
        result[i] = a[i] + b[i]

def timefunc(correct, s, func, *args, **kwargs):
    """
    Benchmark *func* and print out its runtime.
    """
    print(s.ljust(20), end=" ")
    # Make sure the function is compiled before we start the benchmark
    res = func(*args, **kwargs)
    if correct is not None:
        assert np.allclose(res, correct), (res, correct)
    # time it
    print('{:>5.0f} ms'.format(min(repeat(lambda: func(*args, **kwargs),
                                          number=5, repeat=2)) * 1000))
    return res

def make_singlethread(inner_func):
    """
    Run the given function inside a single thread.
    """
    def func(*args):
        length = len(args[0])
        result = np.empty(length, dtype=np.float64)
        inner_func(result, *args)
        return result
    return func

def make_multithread(inner_func, numthreads):
    """
    Run the given function inside *numthreads* threads, splitting its
    arguments into equal-sized chunks.
    """
    def func_mt(*args):
        length = len(args[0])
        result = np.empty(length, dtype=np.float64)
        args = (result,) + args
        chunklen = (length + numthreads - 1) // numthreads
        # Create argument tuples for each input chunk
        chunks = [[arg[i * chunklen:(i + 1) * chunklen] for arg in args]
                  for i in range(numthreads)]
        # Spawn one thread per chunk
        threads = [threading.Thread(target=inner_func, args=chunk)
                   for chunk in chunks]
        for thread in threads:
            thread.start()
        for thread in threads:
            thread.join()
        return result
    return func_mt


func_nb = make_singlethread(inner_func_nb)
func_nb_mt = make_multithread(inner_func_nb, nthreads)

a = np.random.rand(size)
b = np.random.rand(size)

correct = timefunc(None, "numpy (1 thread)", func_np, a, b)
timefunc(correct, "numba (1 thread)", func_nb, a, b)
timefunc(correct, "numba (%d threads)" % nthreads, func_nb_mt, a, b)

Saya menyoroti bagian-bagian yang saya ubah, yang lainnya disalin kata demi kata dari contoh. Ini menggunakan semua inti pada mesin saya (4 mesin inti oleh karena itu 4 utas) tetapi tidak menunjukkan percepatan yang signifikan:

numpy (1 thread)       539 ms
numba (1 thread)       536 ms
numba (4 threads)      442 ms

Kurangnya (banyak) speedup dengan multithreading dalam hal ini adalah bahwa penambahan adalah operasi bandwidth-terbatas. Itu berarti dibutuhkan lebih banyak waktu untuk memuat elemen dari array dan menempatkan hasilnya di array hasil daripada melakukan penambahan yang sebenarnya.

Dalam kasus ini Anda bahkan bisa melihat perlambatan karena eksekusi paralel!

Hanya jika fungsinya lebih kompleks dan operasi sebenarnya membutuhkan waktu yang signifikan dibandingkan dengan memuat dan menyimpan elemen array, Anda akan melihat peningkatan besar dengan eksekusi paralel. Contoh dalam dokumentasi numba adalah seperti itu:

def func_np(a, b):
    """
    Control function using Numpy.
    """
    return np.exp(2.1 * a + 3.2 * b)

@jit('void(double[:], double[:], double[:])', nopython=True, nogil=True)
def inner_func_nb(result, a, b):
    """
    Function under test.
    """
    for i in range(len(result)):
        result[i] = math.exp(2.1 * a[i] + 3.2 * b[i])

Ini sebenarnya berskala (hampir) dengan jumlah utas karena dua perkalian, satu penambahan dan satu panggilan ke math.exp jauh lebih lambat daripada memuat dan menyimpan hasil:

func_nb = make_singlethread(inner_func_nb)
func_nb_mt2 = make_multithread(inner_func_nb, 2)
func_nb_mt3 = make_multithread(inner_func_nb, 3)
func_nb_mt4 = make_multithread(inner_func_nb, 4)

a = np.random.rand(size)
b = np.random.rand(size)

correct = timefunc(None, "numpy (1 thread)", func_np, a, b)
timefunc(correct, "numba (1 thread)", func_nb, a, b)
timefunc(correct, "numba (2 threads)", func_nb_mt2, a, b)
timefunc(correct, "numba (3 threads)", func_nb_mt3, a, b)
timefunc(correct, "numba (4 threads)", func_nb_mt4, a, b)

Hasil:

numpy (1 thread)      3422 ms
numba (1 thread)      2959 ms
numba (2 threads)     1555 ms
numba (3 threads)     1080 ms
numba (4 threads)      797 ms
19
MSeifert 10 Agustus 2017, 11:25

Demi kelengkapan, di tahun 2018 (numba v 0.39) Anda bisa melakukannya

from numba import prange

Dan ganti range dengan prange dalam definisi fungsi asli Anda, itu saja.

Itu segera membuat pemanfaatan CPU 100% dan dalam kasus saya mempercepat waktu runtime dari 2,9 menjadi 1,7 detik (untuk SIZE = 2147483648 * 1, pada mesin dengan 16 core 32 utas).

Kernel yang lebih kompleks sering kali dapat lebih dipercepat dengan mengirimkan fastmath=True.

7
Anatoly Alekseev 22 Agustus 2018, 17:40