Dadas duas grandes matrizes,
A = np.random.randint(10,size=(10000,2))
B = np.random.randint(10,size=(10000,2))
Gostaria de determinar se algum dos vetores tem um produto vetorial igual a zero. Nós poderíamos fazer
C = np.cross(A[:,None,:],B[None,:,:])
e então verifique se C contém 0 ou não.
not C.all()
No entanto, este processo requer o cálculo de todos os produtos cruzados, o que pode ser demorado. Em vez disso, eu preferiria deixar numpy realizar o produto vetorial, mas SE um zero for alcançado em qualquer ponto, simplesmente corte toda a operação e termine mais cedo. O numpy tem uma operação de "rescisão antecipada" que interromperá as operações numpy antecipadamente se elas atingirem uma condição? Algo como,
np.allfunc()
np.anyfunc()
O exemplo acima é um caso em que A e B têm uma probabilidade extremamente alta de ter um produto vetorial zero em algum ponto (na verdade, é muito provável que ocorra perto do início), tanto que a execução de um python-for-loop (eca!) é muito mais rápido do que usar o código altamente otimizado do numpy.
Em geral, qual é a maneira mais rápida de determinar se A e B têm produto vetorial zero?
Simplificando, não, Numpy não. Na prática, isso é um pouco mais complexo do que isso.
Na verdade, a função
allfunc
andanyfunc
que você propõe é semelhante anp.all
andnp.any
. Essa função faz algum tipo de rescisão antecipada. Aqui está uma prova na minha máquina:O primeiro é tão rápido que não pode iterar por todo o array, já que a taxa de transferência da memória atingiria 71 GiB/s, enquanto minha RAM só pode atingir a largura de banda máxima teórica de 47,7 GiB/s (cerca de 40 GiB/s na prática). Meu cache de CPU está longe de ser grande o suficiente para armazenar uma parte significativa desse array e o array ocupa 200 MiB de RAM, então cada booleano ocupa 1 byte de RAM. Estranhamente, a velocidade
all
eany
é bastante decepcionante no primeiro caso, poisv[2]
éFalse
na prática. Numpy certamente executa o cálculo pedaço por pedaço e executa o encerramento antecipado somente após calcular um pedaço (se necessário).O problema com
any
eall
é que eles exigem uma matriz booleana no parâmetro. Assim, a matriz booleana precisa ser totalmente computada primeiro, anulando o benefício de qualquer encerramento antecipado .Alternativamente, Numpy fornece ufuncs que são projetados para operar em arrays elemento por elemento. Um exemplo de ufunc é
np.ufunc.reduce
. Eles poderiam ser usados para compor algoritmos de tal forma que o encerramento antecipado seria teoricamente possível. Esta solução seria significativamente mais rápida do que as operações normais não ufunc quando o encerramento é possível bem cedo. No entanto, quando o encerramento só é possível ultimamente, o ufuncs seria muito mais lento do que as operações não-ufunc. Na verdade, os ufuncs são (atualmente) fundamentalmente ineficientes em comparação com as operações não-ufunc. Isso ocorre porque os ufuncs exigem uma chamada de função cara (nativa) por item. Esta chamada de função, aplicada em itens escalares, evita diversas otimizações críticas como o uso deInstruções SIMD . Ainda assim, ufuncs muitas vezes pode ser mais rápido do que usar código Python puro elemento por elemento, graças a "vectorization" . A maneira atual como os ufuncs podem ser compostos é que o AFAIK não é suficiente, no seu caso de uso, para escrever um código eficiente que se beneficie do encerramento antecipado.A maneira padrão de encerrar antecipadamente no Numpy é dividir o cálculo em vários pedaços (manualmente). Essa solução geralmente é rápida porque é vetorizada, geralmente compatível com SIMD e eficiente em cache (assumindo que os pedaços sejam pequenos o suficiente). A sobrecarga do Numpy é pequena, desde que os pedaços sejam grandes o suficiente. Aqui está um exemplo:
Para o seu exemplo de entrada, são necessários 239 µs na minha máquina, enquanto a implementação ingênua do Numpy leva 234 ms. Isso é cerca de 1000 vezes mais rápido neste caso. Com pedaços menores, como 32x32, o cálculo é ainda mais rápido: 32,3 µs.
Na verdade, pedaços menores são melhores quando há muitos zeros (já que leva tempo para calcular o primeiro pedaço). 256x256 deve ser relativamente bom na maioria das máquinas (bastante amigável ao cache, eficiente em termos de memória e com baixa sobrecarga Numpy).
Uma solução mais simples (que geralmente também é mais rápida) é usar módulos/ferramentas, como Cython ou Numba , compilando seu código para que os loops possam ser rápidos graças à compilação para funções nativas. No entanto, com esta solução, você mesmo precisa reimplementar o produto vetorial. Na verdade, o Numba ainda não oferece suporte e o Cython não será mais rápido (do que um código Python puro usando Numpy) se você apenas chamar funções Numpy em loops.