2013-03-11 15 views
19

Così ho fatto alcuni test e ottenuto risultati strani.Tipi di dati Numpy Python Prestazioni

Codice:

import numpy as np 
import timeit 


setup = """ 
import numpy as np 
A = np.ones((1000,1000,3), dtype=datatype) 
""" 

datatypes = "np.uint8", "np.uint16", "np.uint32", "np.uint64", "np.float16", "np.float32", "np.float64" 

stmt1 = """ 
A = A * 255 
A = A/255 
A = A - 1 
A = A + 1 
""" 
#~ np.uint8 : 1.04969205993 
#~ np.uint16 : 1.19391073202 
#~ np.uint32 : 1.37279821351 
#~ np.uint64 : 2.99286961148 
#~ np.float16 : 9.62375889588 
#~ np.float32 : 0.884994368045 
#~ np.float64 : 0.920502625252 

stmt2 = """ 
A *= 255 
A /= 255 
A -= 1 
A += 1 
""" 
#~ np.uint8 : 0.959514497259 
#~ np.uint16 : 0.988570167659 
#~ np.uint32 : 0.963571471946 
#~ np.uint64 : 2.07768933333 
#~ np.float16 : 9.40085450056 
#~ np.float32 : 0.882363984225 
#~ np.float64 : 0.910147440048 

stmt3 = """ 
A = A * 255/255 - 1 + 1 
""" 
#~ np.uint8 : 1.05919667881 
#~ np.uint16 : 1.20249978404 
#~ np.uint32 : 1.58037744789 
#~ np.uint64 : 3.47520357571 
#~ np.float16 : 10.4792515701 
#~ np.float32 : 1.29654744484 
#~ np.float64 : 1.80735079168 

stmt4 = """ 
A[:,:,:2] *= A[:,:,:2] 
""" 
#~ np.uint8 : 1.23270964172 
#~ np.uint16 : 1.3260807837 
#~ np.uint32 : 1.32571002402 
#~ np.uint64 : 1.76836543305 
#~ np.float16 : 2.83364821535 
#~ np.float32 : 1.31282323872 
#~ np.float64 : 1.44151875479 

stmt5 = """ 
A[:,:,:2] = A[:,:,:2] * A[:,:,:2] 
""" 
#~ np.uint8 : 1.38166223494 
#~ np.uint16 : 1.49569114821 
#~ np.uint32 : 1.53105315419 
#~ np.uint64 : 2.03457943366 
#~ np.float16 : 3.01117795524 
#~ np.float32 : 1.51807271679 
#~ np.float64 : 1.7164808877 

stmt6 = """ 
A *= 4 
A /= 4 
""" 
#~ np.uint8 : 0.698176392658 
#~ np.uint16 : 0.709560468038 
#~ np.uint32 : 0.701653066443 
#~ np.uint64 : 1.64199069295 
#~ np.float16 : 4.86752675499 
#~ np.float32 : 0.421001675475 
#~ np.float64 : 0.433056710408 

stmt7 = """ 
np.left_shift(A, 2, A) 
np.right_shift(A, 2, A) 
""" 
#~ np.uint8 : 0.381521115341 
#~ np.uint16 : 0.383545967785 
#~ np.uint32 : 0.386147272415 
#~ np.uint64 : 0.665969478824 


for stmt in [stmt1, stmt2, stmt3, stmt4, stmt5, stmt6, stmt7]: 
    print stmt 
    for d in datatypes: 
     s = setup.replace("datatype", d) 
     T = timeit.Timer(stmt=stmt, setup=s) 
     print d,":", min(T.repeat(number=30)) 
    print 
print 

Perché float16 così lento? Perché float32 è così veloce? È spesso più veloce delle operazioni con numeri interi.

Se si dispone di suggerimenti sulle prestazioni correlati, sarei lieto di ascoltarli.

Questo è Python 2.6.6 32 bit su Windows 8 64 bit. Numeri per Numpy 1.6, Numpy 1.7 simili. Metterà alla prova MKL versione ottimizzata ora: http://www.lfd.uci.edu/~gohlke/pythonlibs/#numpy

edit: scopre la versione MKL è leggermente più veloce in alcuni casi, in virgola mobile, ma a volte un sacco più lento per ops interi:

stmt2 = """ 
A *= 255 
A /= 255 
A -= 1 
A += 1 
""" 
#np1.6 
#~ np.uint8 : 0.959514497259 
#~ np.uint16 : 0.988570167659 
#~ np.uint32 : 0.963571471946 
#~ np.uint64 : 2.07768933333 
#~ np.float16 : 9.40085450056 
#~ np.float32 : 0.882363984225 
#~ np.float64 : 0.910147440048 

# np1.7 
#~ np.uint8 : 0.979 
#~ np.uint16 : 1.010 
#~ np.uint32 : 0.972 
#~ np.uint64 : 2.081 
#~ np.float16 : 9.362 
#~ np.float32 : 0.882 
#~ np.float64 : 0.918 

# np1.7 mkl 
#~ np.uint8 : 1.782 
#~ np.uint16 : 1.145 
#~ np.uint32 : 1.265 
#~ np.uint64 : 2.088 
#~ np.float16 : 9.029 
#~ np.float32 : 0.800 
#~ np.float64 : 0.866 
+1

https://en.wikipedia.org/wiki/Half-precision_floating-point_format "È progettato per l'archiviazione, non per l'esecuzione di calcoli aritmetici." – endolith

risposta

17

Mezza precisione aritmetica (float16) è qualcosa che deve essere "emulato" da numpy, suppongo, poiché non ci sono tipi corrispondenti nel linguaggio C sottostante (e nelle appropriate istruzioni del processore) per esso. D'altra parte, le operazioni di precisione singola (float32) e doppia precisione (float64) possono essere eseguite in modo molto efficiente utilizzando i tipi di dati nativi.

Come le buone prestazioni per le operazioni a precisione singola: i processori moderni dispongono di unità efficienti per l'aritmetica in virgola mobile vettorizzata (ad esempio AVX) in quanto è anche necessaria per ottenere buone prestazioni multimediali.

8

I numeri in virgola mobile a 16 bit non sono supportati direttamente dalle CPU più comuni (sebbene i fornitori di schede grafiche siano apparentemente coinvolti in questo tipo di dati, quindi mi aspetto che le GPU lo supportino alla fine). Mi aspetto che vengano emulati, in un modo relativamente lento. Google mi dice che float16 was once hardware-dependent e alcune persone volevano emularlo per l'hardware che non lo supporta, anche se non ho trovato nulla sul fatto che ciò sia realmente accaduto.

I float a 32 bit, d'altra parte, non sono supportati solo in modo nativo, è possibile anche vettorializzare molte operazioni su di essi con le estensioni del set di istruzioni SIMD, che riduce drasticamente il sovraccarico per il tipo di operazione di riferimento. L'eccezione è mescolare i dati in giro, ma in quel caso, float32 è alla pari con int32 ed entrambi possono usare le stesse istruzioni SIMD per caricare e archiviare più grandi blocchi di memoria.

Mentre ci sono anche istruzioni SIMD per la matematica intera, sono meno comuni (ad esempio, VEDERE le ha introdotte in una versione successiva rispetto alle versioni float) e spesso meno sofisticate. La mia ipotesi è che (la tua build di) NumPy non abbia implementazioni SIMD delle operazioni che sono più lente per te. In alternativa, le operazioni su interi potrebbero non essere ottimizzate: i float sono usati in molte applicazioni facili da vettorizzare le cui prestazioni sono molto importanti (ad esempio immagine/media/video en- e decodifica), quindi possono essere più ottimizzate.