2017-06-26 2 views
15

Ich würde gerne verstehen, wo die Gewinne kommen, wenn Sie Numba verwenden, um reinen numpy Code in einer for-Schleife zu beschleunigen. Gibt es Profiling-Tools, mit denen Sie in jitted Funktionen schauen können?Wo sind die Gewinne mit numba, die für reinen numpy Code kommen?

Der Demo-Code (wie unten) verwendet nur sehr einfache Matrixmultiplikation, um dem Computer Arbeit zu verschaffen. Sind die beobachteten Gewinne aus:

  1. ein schneller loop,
  2. die Neufassung der numpy durch die jit während des Übersetzungsvorgangs abgefangen Funktionen oder
  3. weniger Overhead mit jit als numpy auslagert Ausführung über Wrapper-Funktionen zu niedrig Level-Bibliotheken wie LINPACK
%matplotlib inline 
import numpy as np 
from numba import jit 
import pandas as pd 

#Dimensions of Matrices 
i = 100 
j = 100 

def pure_python(N,i,j): 
    for n in range(N): 
     a = np.random.rand(i,j) 
     b = np.random.rand(i,j) 
     c = np.dot(a,b) 

@jit(nopython=True) 
def jit_python(N,i,j): 
    for n in range(N): 
     a = np.random.rand(i,j) 
     b = np.random.rand(i,j) 
     c = np.dot(a,b) 

time_python = [] 
time_jit = [] 
N = [1,10,100,500,1000,2000] 
for n in N: 
    time = %timeit -oq pure_python(n,i,j) 
    time_python.append(time.average) 
    time = %timeit -oq jit_python(n,i,j) 
    time_jit.append(time.average) 

df = pd.DataFrame({'pure_python' : time_python, 'jit_python' : time_jit}, index=N) 
df.index.name = 'Iterations' 
df[["pure_python", "jit_python"]].plot() 

erzeugt das folgende Diagramm.

runtime comparisons for a range of iteration lengths

+0

Ich denke, Numba erkennt 'np.random.rand' und' np.dot'. (Wenn nicht, glaube ich nicht, dass Sie sie im nopython-Modus verwenden könnten.) – user2357112

+0

In der Tat. Sie werden von '' numba' 'wie in den Dokumenten unterstützt. http://numba.pydata.org/numba-doc/dev/reference/numpysupported.html. Ich bin hauptsächlich neugierig, wie das Code-Interception funktioniert und ob das die Quelle von Gewinnen im obigen Beispiel ist. – sanguineturtle

+0

Können Sie einige Setup-Informationen hinzufügen? Auf Win 64, Python 3.5, Numba 0.33, habe ich nur eine bescheidene Beschleunigung (10-15%) – chrisb

Antwort

4

TL: DR Die zufällige und Looping beschleunigt, aber die Matrixmultiplikation ausnehmen nicht für kleine Matrixgröße. Bei kleinen Matrix/Loop-Größen scheint es signifikante Beschleunigungen zu geben, die wahrscheinlich mit Python-Overhead zusammenhängen. Bei großem N beginnt die Matrix zu dominieren und der Jit weniger hilfreich.

Funktionsdefinitionen, mit einer quadratischen Matrix zur Vereinfachung.

from IPython.display import display 
import numpy as np 
from numba import jit 
import pandas as pd 

#Dimensions of Matrices 
N = 1000 

def py_rand(i, j): 
    a = np.random.rand(i, j) 

jit_rand = jit(nopython=True)(py_rand) 

def py_matmul(a, b): 
    c = np.dot(a, b) 

jit_matmul = jit(nopython=True)(py_matmul) 

def py_loop(N, val): 
    count = 0 
    for i in range(N): 
     count += val  


jit_loop = jit(nopython=True)(py_loop)  

def pure_python(N,i,j): 
    for n in range(N): 
     a = np.random.rand(i,j) 
     b = np.random.rand(i,j) 
     c = np.dot(a,a) 

jit_func = jit(nopython=True)(pure_python) 

Timing:

df = pd.DataFrame(columns=['Func', 'jit', 'N', 'Time']) 
def meantime(f, *args, **kwargs): 
    t = %timeit -oq -n5 f(*args, **kwargs) 
    return t.average 


for N in [10, 100, 1000, 2000]: 
    a = np.random.randn(N, N) 
    b = np.random.randn(N, N) 

    df = df.append({'Func': 'jit_rand', 'N': N, 'Time': meantime(jit_rand, N, N)}, ignore_index=True) 
    df = df.append({'Func': 'py_rand', 'N': N, 'Time': meantime(py_rand, N, N)}, ignore_index=True) 

    df = df.append({'Func': 'jit_matmul', 'N': N, 'Time': meantime(jit_matmul, a, b)}, ignore_index=True) 
    df = df.append({'Func': 'py_matmul', 'N': N, 'Time': meantime(py_matmul, a, b)}, ignore_index=True) 

    df = df.append({'Func': 'jit_loop', 'N': N, 'Time': meantime(jit_loop, N, 2.0)}, ignore_index=True) 
    df = df.append({'Func': 'py_loop', 'N': N, 'Time': meantime(py_loop, N, 2.0)}, ignore_index=True) 

    df = df.append({'Func': 'jit_func', 'N': N, 'Time': meantime(jit_func, 5, N, N)}, ignore_index=True) 
    df = df.append({'Func': 'py_func', 'N': N, 'Time': meantime(pure_python, 5, N, N)}, ignore_index=True) 

df['jit'] = df['Func'].str.contains('jit') 
df['Func'] = df['Func'].apply(lambda s: s.split('_')[1]) 
df.set_index('Func') 
display(df) 

Ergebnis:

Func jit  N Time 
0 rand True 10 1.030686e-06 
1 rand False 10 1.115149e-05 
2 matmul True 10 2.250371e-06 
3 matmul False 10 2.199343e-06 
4 loop True 10 2.706000e-07 
5 loop False 10 7.274286e-07 
6 func True 10 1.217046e-05 
7 func False 10 2.495837e-05 
8 rand True 100 5.199217e-05 
9 rand False 100 8.149794e-05 
10 matmul True 100 7.848071e-05 
11 matmul False 100 2.130794e-05 
12 loop True 100 2.728571e-07 
13 loop False 100 3.003743e-06 
14 func True 100 6.739634e-04 
15 func False 100 1.146594e-03 
16 rand True 1000 5.644258e-03 
17 rand False 1000 8.012790e-03 
18 matmul True 1000 1.476098e-02 
19 matmul False 1000 1.613211e-02 
20 loop True 1000 2.846572e-07 
21 loop False 1000 3.539849e-05 
22 func True 1000 1.256926e-01 
23 func False 1000 1.581177e-01 
24 rand True 2000 2.061612e-02 
25 rand False 2000 3.204709e-02 
26 matmul True 2000 9.866484e-02 
27 matmul False 2000 1.007234e-01 
28 loop True 2000 3.011143e-07 
29 loop False 2000 7.477454e-05 
30 func True 2000 1.033560e+00 
31 func False 2000 1.199969e+00 

Es ist wie numba sieht optimiert die Schleife weg, so dass ich werde nicht die Mühe mit ihm im Vergleich

Grundstück:

def jit_speedup(d): 
    py_time = d[d['jit'] == False]['Time'].mean() 
    jit_time = d[d['jit'] == True]['Time'].mean() 
    return py_time/jit_time 

import seaborn as sns 
result = df.groupby(['Func', 'N']).apply(jit_speedup).reset_index().rename(columns={0: 'Jit Speedup'}) 
result = result[result['Func'] != 'loop'] 
sns.factorplot(data=result, x='N', y='Jit Speedup', hue='Func') 

enter image description here

So für die Schleife 5 Wiederholungen ist, beschleunigt die jit ganz solide Dinge, bis die Matrixmultiplikation genug teuer wird die anderen Kopf unbedeutend im Vergleich zu machen.

+0

Sie könnten daran interessiert sein, den 'def py_loop():' Code zu reparieren, da sich der ursprüngliche Code als prinzipiell ** ** O (1) '** skaliert erwiesen hat (konzeptionell vielleicht ein übersehener Shortcut/maskierter variabler Fehler). unabhängig von "N"), was das Experiment verzerrt, indem immer ** nur ~ ~ 163 - 294 [ns] '** Verarbeitungsdauern zurückgegeben werden (was bestätigt, dass nur "konstante" reine Signatur-Verarbeitungs-Overhead + eine JMP/RET-Dauer keine 'N'-mal Schleifen-Ed-Code-Ausführung). – user3666197

+1

ah du hast Recht Ich habe das nicht verstanden - Reparatur! – evamicur

+0

Nein, ** Sie müssen einen Wert ** zurückgeben - einen "da" -produzierten Wert, sonst sieht die JIT-Compiler-Analyse keinen greifbaren Grund dafür, die Silizium-Schleife so oft durch einen "stillen" Code zu machen, wenn es nichts zurückliefert ... ** Bessere 'numba.jit (...)' Test-Payload ein bisschen sorgfältiger gestalten **, sonst würdest du wieder versuchen, nichts vernünftiges für den O/P zu testen. – user3666197

Verwandte Themen