Loops são um dos mais básicos conceitos em programação. Eles são usados para repetir blocos de códigos múltiplas vezes sem precisar reescrever tudo a cada repetição. Como loops são conceitos muito fundamentais em programação, eles são aprendidos logo no início e se tornam ferramentas integrais na escrita de códigos. Porém, quando o número de repetições (iterações) que precisa ser realizado é grande, loops são pouco eficientes. Nestes casos, a vetorização é muito mais apropriada.
Vetorização
Vetorização é uma técnica computacional que permite a execução de operações em matrizes, vetores ou tensores de uma só vez, em vez de iterar sobre elementos individuais conforme feito em loops. Consequentemente, a vetorização minimiza a necessidade de operações iterativas que podem ser computacionalmente caras. O resultado é um processo de computação mais simplificado e eficiente capaz de lidar com conjuntos de dados complexos com relativa facilidade.
Outra vantagem da vetorização se refere à clareza dos códigos. Um código vetorizado pode ser mais conciso e fácil de ler do que códigos baseados em loops.
Vetorização em Python
Tarefas de vetorização em Python são realizadas principalmente com a biblioteca NumPy. Ela fornece operações de matrizes, vetores e tensores altamente otimizadas utilizando NumPy arrays principalmente.
NumPy arrays (matrizes n-dimensionais) são o centro da computação numérica em Python. Pense neles como listas modificadas para lidar com grandes conjuntos de dados multidimensionais com eficiência.
Arrays implementados em NumPy suportam uma ampla gama de operações vetorizadas. Entre algumas dessas operações estão:
- Aritmética elementar como adição, subtração, multiplicação e divisão
- Funções matemáticas como exponenciação e logaritmos
- Operações estatísticas como cálculo de média e desvio padrão
- Operações de álgebra linear como multiplicação de matrizes e cálculo de produto escalar
Como a Vetorização Acelera Códigos Python?
Como mencionado acima, a vetorização em Python depende fortemente de arrays NumPy. Eles são estruturas de dados otimizadas para cálculos numéricos. Os arrays armazenam elementos de forma homogênea (mesmo tipo de dados) em blocos de memória contíguos, permitindo operações eficientes.
Quando uma operação vetorizada é executada, o NumPy usa um código C compilado nos bastidores, o qual é muito mais rápido do que o código interpretado do Python para loops.
Além disso, o NumPy geralmente aproveita recursos de hardware para realizar o processamento paralelo de vários elementos simultaneamente.
Quando Usar Vetorização em Python?
As seguintes situações se beneficiam do uso de vetorização em Python:
- Operações repetitivas: tarefas que envolvem a aplicação da mesma operação a cada elemento de uma matriz são ideais para vetorização.
- Cálculos numéricos em grandes conjuntos de dados: a vetorização é excelente quando se lida com matrizes grandes, ou seja, com milhares ou milhões de elementos. Os ganhos de desempenho tornam-se mais pronunciados à medida que o tamanho do conjunto de dados aumenta.
Quando a velocidade do código é fundamental, a vetorização geralmente fornece otimizações significativas.
Quais são as Limitações para o Uso de Vetorização?
Existem situações onde o uso de vetorização pode não ser benéfico ou não ser adequado. Entre essas situações estão:
- Dependências sequenciais: a vetorização não garante melhora de desempenho para operações onde o resultado de um elemento afeta o processamento de elementos subsequentes.
- Códigos com lógicas complexas: tarefas com ramificações condicionais intrincadas ou dependências de elementos podem não ser facilmente vetorizáveis.
- Códigos com muitas operações não numéricas: a vetorização é muito eficiente para cálculos numéricos, mas pode ser menos eficiente em outros cenários.
- Sobrecarga de memória: operações vetorizadas geralmente criam matrizes temporárias para armazenar resultados intermediários, aumentando potencialmente o uso de memória. Isso pode ser um problema para conjuntos de dados muito grandes ou sistemas com pouca memória.
- Concisão com perda de clareza: embora o código vetorizado possa ser mais conciso, isso pode sacrificar a clareza para aqueles que não estão familiarizados com a sintaxe NumPy e operações vetorizadas.
- Depuração: depurar códigos vetorizados pode ser mais desafiador do que depurar loops, pois os erros podem não se manifestar tão claramente em suas estruturas concisas.
Vetorização na Prática
Para entender melhor o poder da vetorização em Python, a melhor estratégia consiste em explorar alguns exemplos. Para acompanhar essa parte na prática, será preciso ter a biblioteca NumPy instalada. Uma das formas de instalação é através do pip:
pip install numpy
No início do código Python, importaremos o NumPy e o módulo time para verificar o tempo de execução das operações que testaremos.
import numpy as np
import time
Exemplo 1: Calculando Somas
A primeira situação que testaremos será uma soma executada repetidamente. Para verificar o tempo de execução dessa operação com loop e com vetorização, usaremos a diferença entre o tempo de início e fim de cada caso. O código abaixo mostra o exemplo implementado usando um for loop tradicional e seu análogo vetorizado.
# loop tradicional
init = time.time()
tot = 0
for item in range(10000000):
tot = tot + item
fim = time.time()
print('resultado da soma: ' + str(tot), 'tempo de execução: ' + str(round(fim - init, 5)))
# Vetorização com NumPy
init_np = time.time()
tot_np = np.sum(np.arange(10000000))
fim_np = time.time()
print('resultado da soma: ' + str(tot_np), 'tempo de execução: ' + str(round(fim_np - init_np, 5)))
Abaixo mostramos os resultados obtidos. Ambos os métodos usados produzem o mesmo resultado, mas com diferentes tempos de execução.
resultado da soma: 49999995000000 tempo de execução: 0.62156
resultado da soma: 49999995000000 tempo de execução: 0.01533
Exemplo 2: Multiplicação de um Conjunto de Dados
Agora testaremos uma multiplicação repetitiva.
# loop tradicional
init = time.time()
arr = np.arange(1000000)
res = np.zeros_like(arr)
for i in range(len(arr)):
res[i] = arr[i] * 2
fim = time.time()
print('tempo de execução: ' + str(round(fim - init, 5)))
# Vetorização com NumPy
init_np = time.time()
res_np = arr * 2
fim_np = time.time()
print('tempo de execução: ' + str(round(fim_np - init_np, 5)))
A diferença nos tempos de execução está abaixo.
tempo de execução: 0.15112
tempo de execução: 0.00196
Exemplo 3: Operações com Condicionais em um Conjunto de Dados
Operações vetorizadas podem ser facilmente usadas com condicionais. No código abaixo, multiplicamos cada elemento de um array por 2 se ele é maior do que 10. A mesma operação é feita com um loop tradicional e, em seguida, com vetorização.
# Loop tradicional
init = time.time()
arr = np.arange(1000000)
res = np.zeros_like(arr)
for i in range(len(arr)):
if arr[i] > 10:
res[i] = arr[i] * 2
fim = time.time()
print('tempo de execução: ' + str(round(fim - init, 5)))
# Vetorização com NumPy
init_np = time.time()
res = arr[arr > 10] * 2
fim_np = time.time()
print('tempo de execução: ' + str(round(fim_np - init_np, 5)))
Abaixo mostramos os diferentes tempos de execução obtidos.
tempo de execução: 0.21324
tempo de execução: 0.00538
Exemplo 4: Operações de Álgebra Linear
Para terminar, mostraremos um exemplo com álgebra linear (multiplicação de matrizes). Com uma implementação tradicional, esse tipo de operação requer o uso de vários loops, tornando o processo bastante lento. A verão vetorizada, além de ser concisa e clara, é extremamente eficiente.
# Loop tradicional
init = time.time()
A = np.random.rand(100, 100)
B = np.random.rand(100, 100)
C = np.zeros((100, 100))
for i in range(len(A)):
for j in range(len(B[0])):
for k in range(len(B)):
C[i,j] += A[i, k] * B[k, j]
fim = time.time()
print('tempo de execução: ' + str(round(fim - init, 5)))
# Vetorização com NumPy
init_np = time.time()
C = np.matmul(A, B)
fim_np = time.time()
print('tempo de execução: ' + str(round(fim_np - init_np, 5)))
Aqui estão os tempos de execução para cada opção testada.
tempo de execução: 0.38465
tempo de execução: 0.00776
Conclusões
Vetorização é uma técnica computacional eficiente para a execução de operações em conjuntos de dados de uma só vez, em vez de iterar sobre elementos individuais como tradicionalmente feito em loops. Sua linguagem concisa torna os códigos mais limpos e compactos. Portanto, as vantagens da vetorização são inúmeras. Para inserir essas vantagens nos seus códigos Python, o melhor caminho é conhecer melhor a biblioteca NumPy.