Alejandro Mesias @meszias
• Campineiro
• Pai de 3 filhos
• Zup Innovation
• Pythonista a +10 anos.
• Tkinter, Django, Flask, Pymodbus...
Introdução ao Zumba - Contexto
• Python se tornou uma linguagem muito popular de computação
cientifica.
• Python integra bem com bibliotecas compiladas: MKL (intel),
TensorFlow, ROOT (Análise matemática), etc.
• Compilação de modulos em C/C++, ctypes, libs próprias em binário.
*Stan Seibert, Anaconda
Introdução ao Numba – Motivação
A Compiler for Python?
Striking a Balance Between Productivity
and Performance.
(Atingindo um equilíbrio entre produtividade e desempenho.)
*Stan Seibert, Anaconda
Introdução ao Numba - Motivação
O objetivo foi fazer um compilador que atenda:
• Funcione no interpretador python padrão, e não substituir o mesmo
• Que se integre perfeitamente com Numpy
• Compativel com paradgmas de computação multitread e distribuída
• Pode ser direcionado a non-CPU hardware (fazer uso de GPU/Cuda)
*Stan Seibert, Anaconda
Numba: Um compilador JIT Compiler para Python
• Uma biblioteca de compilação “uma função por vez” para python
• Um toolbox de compilação com diferentes objetivos e modelos de execução:
• Single-tread CPU, multi-threaded CPU, GPU
• Funções regulares, funções de array, etc.
• Speed-up: 2x comparado a Numpy, até 200x (comparado com Python puro)
• Combinar facilidade de escrever Python com a velocidades próximas do Fortran.
• O objetivo é capacitar cientistas que criam ferramentas para outros cientistas.
*Stan Seibert, Anaconda
Um labirinto sinuoso de trade-offs
Numba pode ser melhor entendido pelo que ele Não é:
• Substituição do interpretador Python: PyPy, Pyston, Pyjion
• Dificil de implementar
• Dificil (mas não impossível) de manter compatibilidade
com extensões python existentes
• Não endereça para destinos não-CPU
• Tradutor do python para C/C++: Cython, Pythran, Theano, ShedSkin, Niutka
• Análise estática de linguagens dinâmicas é limitante
• Código gerado antecipado é subespecializado (tanto em tipos de dados quanto em recursos
de CPU) ou inchado para cobrir todas as variantes.
• Compilação com JIT requer compilador C/C++ no sistema do usuário
Exemplo Básico
Sequencia de Fibonacci não recursiva.
Até 92, 1.000.000 execuções.
Usando time.perf_counter_ns()
4315505000µs ou 4.31s
def fibonacci(n):
a, b = 0, 1
for _ in range(0, n-1):
c = a + b
a = b
b = c
return a
Exemplo Básico
Sequencia de Fibonacci não recursiva.
Até 92, 1.000.000 execuções.
Usando time.perf_counter_ns()
Sem otimização: 4.19s
Otimizado: 0.22s (19.5x mais rápido)
@njit
def fibonacci(n):
a, b = 0, 1
for _ in range(0, n-1):
c = a+b
a = b
b = c
return a
Exemplo Básico
Sequencia de Fibonacci não recursiva.
Até 92, 1.000.000 execuções.
Usando time.perf_counter_ns()
Sem otimização: 4.19s
Otimizado: 0.27s (19.5x mais rápido)
“... até 200x
(comparado
com Python
puro)”
Exemplo Básico
Sequencia de Fibonacci não recursiva.
Até 92, 1.000.000 execuções.
Usando time.perf_counter_ns()
Sem otimização: 4.3s
Otimizado: 0.22s (19.5x mais rápido)
Loop Otimizado: 0.042s (101x mais rápido)
@numba.njit("uint64(uint64, uint64)")
def runner(runs, fibn):
val1 = 0
for i in range(runs):
val1 = fibonacci(fibn)
return val1
Numba - Instalação
pip install numba
conda install numba
O que será instalado:
Numba – 0.51.2 => Lib
Numpy – 1.19.4 => Tudo é ndarray
llvmlite - 0.34.0 => Compilação
Dicas over exemplos
Pontos fracos do Numba
• Documentação com poucos detalhes (nem todos são especialistas!)
• Stack de falha pouco detalhada
• Nem sempre são explícitos os erros de execução. (ex: index no array)
Opcional (NUMBA_DEVELOPER_MODE=1)
• Voltar ao python puro é sempre uma boa opção (NUMBA_DISABLE_JIT=1)
• Use sua intuição !
Tipos de variável
Type name(s) Shorthand Comments
intc – C int-sized integer
uintc –
C int-sized unsigned
integer
intp – pointer-sized integer
uintp –
pointer-sized
unsigned integer
float32 f4
single-precision
floating-point
number
float64, double f8
double-precision
floating-point
number
complex64 c8
single-precision
complex number
complex128 c16
double-precision
complex number
Type name(s) Shorthand Comments
Boolean b1
represented as a
byte
uint8, byte u1 8-bit unsigned byte
uint16 u2
16-bit unsigned
integer
uint32 u4
32-bit unsigned
integer
uint64 u8
64-bit unsigned
integer
int8, char i1 8-bit signed byte
int16 i2 16-bit signed integer
int32 i4 32-bit signed integer
int64 i8 64-bit signed integer
Numba - utilização
Algumas regras de utilização:
• Evite dict, list, tuplas. Existem recomendações para esses tipos:
• Typed Dict
• key_type
• value_type
from numba.typed import Dict
from numba.core import types
from numpy as np
# The Dict.empty() constructs a typed dictionary.
# The key and value typed must be explicitly declared.
d = Dict.empty(
key_type=types.unicode_type,
value_type=types.float64[:],
)
d['posx'] = np.asarray([1, 0.5, 2], dtype='f8')
String
Lista de float64
ndarray
Numba - utilização
Algumas regras de utilização:
• Typed List
• Empty_list
• pega o primeiro tipo usado
from numba import types
from numba.typed import List
alloc_list = List.empty_list(types.int64,
10)
mylist = List()
mylist.append(100)
Numba - utilização
Algumas regras de utilização:
• Manupulação de String dentro do numba é limitado
• Não há fstring, string format ou “%s %d” %(‘a’, 1)
• É possível concatenar somando strings
• É possível usar selectors “bom dia”[4:]
• Evite acesso a variáveis globais, a não ser que sejam do numba,
prefira passar por parâmetro (cuidado com o gil).
• Existem classes, mas não funcionam como nopython=true
Numba - utilização
Algumas regras de utilização:
• O tipo de cada atributo é
especificado (obrigatório)
• Funciona como uma classe mas,
tem peso extra para compilar
dinamicamente (não tem cache=True)
• Prático para trocar variáveis com
funções numba
spec = [
('value', int32), # a simple scalar field
('array', float32[:]), # an array field
]
@jitclass(spec)
class Bag(object):
def __init__(self, value):
self.value = value
self.array = np.zeros(value, dtype=np.float32)
@property
def size(self):
return self.array.size
n = 21
mybag = Bag(n)
Numba - utilização
Algumas regras de utilização:
• Conflito com tipos numpy no @jitclass:
No spec = [ ] apenas tipos
do Numba.
Alternativas
para extrair tipos numpy:
Numpy, processamento paralelo, otimização
Calculando distribuição 50:50 de 2 colunas randomicas
def calculate3(x):
a = b = c = d = 0
for i in prange(x.shape[0]):
row = x[i]
if row[0] > 50:
a += 1
else:
b += 1
if row[1] < 50:
c += 1
else:
d += 1
return [a, b, c, d]
@njit(cache=True)
def calculate2(x):
a = b = c = d = 0
for i in prange(x.shape[0]):
row = x[i]
if row[0] > 50:
a += 1
else:
b += 1
if row[1] < 50:
c += 1
else:
d += 1
return [a, b, c, d]
@njit(parallel=True, nogil=True,
cache=True,
locals={'a': uint64, 'b': uint64,
'c': uint64,'d': uint64})
def calculate1(x):
a = b = c = d = 0
for i in prange(x.shape[0]):
row = x[i]
if row[0] > 50:
a += 1
else:
b += 1
if row[1] < 50:
c += 1
else:
d += 1
return [a, b, c, d]
Python puro Njit Simples Njit paranelizado
Comparação de resultados
# size_array = 10**6
size_array = 10**9
ttt1 = time.perf_counter_ns()
x1 = random.randint(100, size=(size_array, 2))
total_pos1 = calculate1(x1)
ttt1= time.perf_counter_ns() - ttt1
x1 = None
del x1
print(f'Fim 1 Tempo: {ttt1:,}us ({ttt1/10**9}s)')
Iterações Calc1 Calc2 Calc3
10**3 0.049s 0.002s 0.0007s
10**6 0.7s 0.12s 0.68s
10**8 2.29s (34x) 1.68 (48x) 78s
10**9 17.72s 17.54s 778s
0.00
100.00
200.00
300.00
400.00
500.00
600.00
700.00
800.00
900.00
mil 1 milhão 100 mi 1 bilhão
Comparação de tempos
Calc1 Calc2 Calc3
0
2
4
6
8
10
12
14
16
18
20
mil 1 milhão 100 mi 1 bilhão
Comparação 1x2
Calc1 Calc2
Fim
...poderia ter falado de Cuda e processamento por GPU
Mas nem cheguei a executar, não tenho Nvidia, então ficará para próxima.
Perguntas?

Ganhando performance no Python com Numba em projetos não científicos

  • 1.
    Alejandro Mesias @meszias •Campineiro • Pai de 3 filhos • Zup Innovation • Pythonista a +10 anos. • Tkinter, Django, Flask, Pymodbus...
  • 2.
    Introdução ao Zumba- Contexto • Python se tornou uma linguagem muito popular de computação cientifica. • Python integra bem com bibliotecas compiladas: MKL (intel), TensorFlow, ROOT (Análise matemática), etc. • Compilação de modulos em C/C++, ctypes, libs próprias em binário. *Stan Seibert, Anaconda
  • 3.
    Introdução ao Numba– Motivação A Compiler for Python? Striking a Balance Between Productivity and Performance. (Atingindo um equilíbrio entre produtividade e desempenho.) *Stan Seibert, Anaconda
  • 4.
    Introdução ao Numba- Motivação O objetivo foi fazer um compilador que atenda: • Funcione no interpretador python padrão, e não substituir o mesmo • Que se integre perfeitamente com Numpy • Compativel com paradgmas de computação multitread e distribuída • Pode ser direcionado a non-CPU hardware (fazer uso de GPU/Cuda) *Stan Seibert, Anaconda
  • 5.
    Numba: Um compiladorJIT Compiler para Python • Uma biblioteca de compilação “uma função por vez” para python • Um toolbox de compilação com diferentes objetivos e modelos de execução: • Single-tread CPU, multi-threaded CPU, GPU • Funções regulares, funções de array, etc. • Speed-up: 2x comparado a Numpy, até 200x (comparado com Python puro) • Combinar facilidade de escrever Python com a velocidades próximas do Fortran. • O objetivo é capacitar cientistas que criam ferramentas para outros cientistas. *Stan Seibert, Anaconda
  • 6.
    Um labirinto sinuosode trade-offs Numba pode ser melhor entendido pelo que ele Não é: • Substituição do interpretador Python: PyPy, Pyston, Pyjion • Dificil de implementar • Dificil (mas não impossível) de manter compatibilidade com extensões python existentes • Não endereça para destinos não-CPU • Tradutor do python para C/C++: Cython, Pythran, Theano, ShedSkin, Niutka • Análise estática de linguagens dinâmicas é limitante • Código gerado antecipado é subespecializado (tanto em tipos de dados quanto em recursos de CPU) ou inchado para cobrir todas as variantes. • Compilação com JIT requer compilador C/C++ no sistema do usuário
  • 7.
    Exemplo Básico Sequencia deFibonacci não recursiva. Até 92, 1.000.000 execuções. Usando time.perf_counter_ns() 4315505000µs ou 4.31s def fibonacci(n): a, b = 0, 1 for _ in range(0, n-1): c = a + b a = b b = c return a
  • 8.
    Exemplo Básico Sequencia deFibonacci não recursiva. Até 92, 1.000.000 execuções. Usando time.perf_counter_ns() Sem otimização: 4.19s Otimizado: 0.22s (19.5x mais rápido) @njit def fibonacci(n): a, b = 0, 1 for _ in range(0, n-1): c = a+b a = b b = c return a
  • 9.
    Exemplo Básico Sequencia deFibonacci não recursiva. Até 92, 1.000.000 execuções. Usando time.perf_counter_ns() Sem otimização: 4.19s Otimizado: 0.27s (19.5x mais rápido) “... até 200x (comparado com Python puro)”
  • 10.
    Exemplo Básico Sequencia deFibonacci não recursiva. Até 92, 1.000.000 execuções. Usando time.perf_counter_ns() Sem otimização: 4.3s Otimizado: 0.22s (19.5x mais rápido) Loop Otimizado: 0.042s (101x mais rápido) @numba.njit("uint64(uint64, uint64)") def runner(runs, fibn): val1 = 0 for i in range(runs): val1 = fibonacci(fibn) return val1
  • 11.
    Numba - Instalação pipinstall numba conda install numba O que será instalado: Numba – 0.51.2 => Lib Numpy – 1.19.4 => Tudo é ndarray llvmlite - 0.34.0 => Compilação
  • 12.
  • 13.
    Pontos fracos doNumba • Documentação com poucos detalhes (nem todos são especialistas!) • Stack de falha pouco detalhada • Nem sempre são explícitos os erros de execução. (ex: index no array) Opcional (NUMBA_DEVELOPER_MODE=1) • Voltar ao python puro é sempre uma boa opção (NUMBA_DISABLE_JIT=1) • Use sua intuição !
  • 14.
    Tipos de variável Typename(s) Shorthand Comments intc – C int-sized integer uintc – C int-sized unsigned integer intp – pointer-sized integer uintp – pointer-sized unsigned integer float32 f4 single-precision floating-point number float64, double f8 double-precision floating-point number complex64 c8 single-precision complex number complex128 c16 double-precision complex number Type name(s) Shorthand Comments Boolean b1 represented as a byte uint8, byte u1 8-bit unsigned byte uint16 u2 16-bit unsigned integer uint32 u4 32-bit unsigned integer uint64 u8 64-bit unsigned integer int8, char i1 8-bit signed byte int16 i2 16-bit signed integer int32 i4 32-bit signed integer int64 i8 64-bit signed integer
  • 15.
    Numba - utilização Algumasregras de utilização: • Evite dict, list, tuplas. Existem recomendações para esses tipos: • Typed Dict • key_type • value_type from numba.typed import Dict from numba.core import types from numpy as np # The Dict.empty() constructs a typed dictionary. # The key and value typed must be explicitly declared. d = Dict.empty( key_type=types.unicode_type, value_type=types.float64[:], ) d['posx'] = np.asarray([1, 0.5, 2], dtype='f8') String Lista de float64 ndarray
  • 16.
    Numba - utilização Algumasregras de utilização: • Typed List • Empty_list • pega o primeiro tipo usado from numba import types from numba.typed import List alloc_list = List.empty_list(types.int64, 10) mylist = List() mylist.append(100)
  • 17.
    Numba - utilização Algumasregras de utilização: • Manupulação de String dentro do numba é limitado • Não há fstring, string format ou “%s %d” %(‘a’, 1) • É possível concatenar somando strings • É possível usar selectors “bom dia”[4:] • Evite acesso a variáveis globais, a não ser que sejam do numba, prefira passar por parâmetro (cuidado com o gil). • Existem classes, mas não funcionam como nopython=true
  • 18.
    Numba - utilização Algumasregras de utilização: • O tipo de cada atributo é especificado (obrigatório) • Funciona como uma classe mas, tem peso extra para compilar dinamicamente (não tem cache=True) • Prático para trocar variáveis com funções numba spec = [ ('value', int32), # a simple scalar field ('array', float32[:]), # an array field ] @jitclass(spec) class Bag(object): def __init__(self, value): self.value = value self.array = np.zeros(value, dtype=np.float32) @property def size(self): return self.array.size n = 21 mybag = Bag(n)
  • 19.
    Numba - utilização Algumasregras de utilização: • Conflito com tipos numpy no @jitclass: No spec = [ ] apenas tipos do Numba. Alternativas para extrair tipos numpy:
  • 20.
  • 21.
    Calculando distribuição 50:50de 2 colunas randomicas def calculate3(x): a = b = c = d = 0 for i in prange(x.shape[0]): row = x[i] if row[0] > 50: a += 1 else: b += 1 if row[1] < 50: c += 1 else: d += 1 return [a, b, c, d] @njit(cache=True) def calculate2(x): a = b = c = d = 0 for i in prange(x.shape[0]): row = x[i] if row[0] > 50: a += 1 else: b += 1 if row[1] < 50: c += 1 else: d += 1 return [a, b, c, d] @njit(parallel=True, nogil=True, cache=True, locals={'a': uint64, 'b': uint64, 'c': uint64,'d': uint64}) def calculate1(x): a = b = c = d = 0 for i in prange(x.shape[0]): row = x[i] if row[0] > 50: a += 1 else: b += 1 if row[1] < 50: c += 1 else: d += 1 return [a, b, c, d] Python puro Njit Simples Njit paranelizado
  • 22.
    Comparação de resultados #size_array = 10**6 size_array = 10**9 ttt1 = time.perf_counter_ns() x1 = random.randint(100, size=(size_array, 2)) total_pos1 = calculate1(x1) ttt1= time.perf_counter_ns() - ttt1 x1 = None del x1 print(f'Fim 1 Tempo: {ttt1:,}us ({ttt1/10**9}s)') Iterações Calc1 Calc2 Calc3 10**3 0.049s 0.002s 0.0007s 10**6 0.7s 0.12s 0.68s 10**8 2.29s (34x) 1.68 (48x) 78s 10**9 17.72s 17.54s 778s 0.00 100.00 200.00 300.00 400.00 500.00 600.00 700.00 800.00 900.00 mil 1 milhão 100 mi 1 bilhão Comparação de tempos Calc1 Calc2 Calc3
  • 23.
    0 2 4 6 8 10 12 14 16 18 20 mil 1 milhão100 mi 1 bilhão Comparação 1x2 Calc1 Calc2
  • 24.
    Fim ...poderia ter faladode Cuda e processamento por GPU Mas nem cheguei a executar, não tenho Nvidia, então ficará para próxima. Perguntas?