AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / coding / Perguntas / 79248192
Accepted
julian2000P
julian2000P
Asked: 2024-12-03 23:21:47 +0800 CST2024-12-03 23:21:47 +0800 CST 2024-12-03 23:21:47 +0800 CST

Por que minha multiplicação de matrizes usando numpy é tão lenta?

  • 772

Estou tentando multiplicar duas matrizes no numpy com dimensionalidade bastante grande. Veja os 3 métodos abaixo. Eu percebo as 3 matrizes aleatoriamente para mostrar meu problema. A primeira matriz, ou seja, Y1[:,:,0]é parte de uma matriz 3D maior no início. A segunda é uma .copy()dessa matriz e a terceira é sua própria matriz.

Por que a primeira multiplicação é muito mais lenta que as duas segundas?

import numpy as np
from time import time

Y1 = np.random.uniform(-1, 1, (5000, 1093, 201))
Y2 = Y1[:,:,0].copy()
Y3 = np.random.uniform(-1, 1, (5000, 1093))

W = np.random.uniform(-1, 1, (1093, 30))

# method 1
START = time()
Y1[:,:,0].dot(W)
END = time()
print(f"Method 1 : {END - START}")

# method 2
START = time()
Y2.dot(W)
END = time()
print(f"Method 2 : {END - START}")

# method 3
START = time()
Y3.dot(W)
END = time()
print(f"Method 3 : {END - START}")

Os tempos de saída são aproximadamente 34, 0,06 e 0,06 segundos, respectivamente.

Vejo a diferença: enquanto as duas últimas matrizes são matrizes 2D "reais", a primeira é uma fatia da minha matriz 3D maior.

É o subconjunto Y1[:,:,0]que o torna tão lento? Além disso, notei que criar a cópia de Y1 para a matriz Y2 também é bem lento.

Afinal, recebo esta matriz 3D e tenho que calcular repetidamente o produto matricial das fatias de Y1 com uma matriz W (potencialmente diferente). Existe uma maneira melhor/mais rápida de fazer isso?

Desde já, obrigado!

numpy
  • 3 3 respostas
  • 38 Views

3 respostas

  • Voted
  1. Best Answer
    chrslg
    2024-12-04T00:32:52+08:002024-12-04T00:32:52+08:00

    Este é um problema de cache. Se você estudar a diferença de custo em comparação ao tamanho do terceiro eixo, você verá uma relação linear a princípio (k=1 => nenhuma diferença, k=2, método 1 custa o dobro, k=3, método 1 custa três vezes mais, etc.), limitada por um máximo (para k=20 ou k=30 não muda realmente a situação)

    Esse limite máximo depende do tamanho dos outros eixos

    O problema é que a multiplicação de matrizes (e, basicamente, qualquer operação em arrays) opera frequentemente iterativamente. Então os dados na memória são lidos um após o outro.

    A primeira leitura de dados custa um pouco, porque a memória é lenta. Mas quando você acessa dados na memória, uma linha inteira (algo como 64 ou 128 bytes) é lida e armazenada no cache. Se a próxima operação usar o próximo número na matriz, e esse número estiver logo ao lado do anterior na memória, ele provavelmente pertence à mesma linha do cache. E não será necessário lê-lo na memória, nós o temos na memória cache (muito mais rápida).

    É um pouco simplificado demais. E não é tão óbvio ver como se aplica à multiplicação de matrizes, porque uma multiplicação de matrizes não é tão sequencial. Mas, basicamente, quanto mais você usa dados que estão próximos uns dos outros na memória, mais rápido. E as pessoas geralmente ignoram isso, pensando que isso é uma espécie de otimização de hacker para ganhar algum nanossegundo extra. Mas o efeito pode ser enorme.

    Para quantidades muito pequenas de dados, que cabem inteiramente no cache (alguns quilobytes) e um algoritmo complexo o suficiente para lê-los mais de uma vez (até mesmo uma multiplicação de matrizes se qualifica), isso realmente não aparece, porque todos os dados acabarão no cache após algumas etapas de computação.

    Mas se tudo não couber no cache, quanto maior o espaço entre seus dados, menos você poderá reutilizar uma linha de cache. E mais você terá que reler dados na memória. A ponto de ler a memória ser o principal custo.

    Então, o seu problema é que em

    Y1=np.random.uniform(-1, 1, (5000, 1093, 201))
    

    cada dado de Y1[:,:,0]é separado por pelo menos 201x8 = 1608 bytes. Então, cache (para Y1— ele ainda é usado para W, mas todos os métodos são iguais para isso) é inútil: nenhuma chance de ter um acesso rápido a um valor de Y1[:,:,0]graças ao fato de que já lemos um valor próximo a ele na memória: eles estão todos distantes um do outro.

    Outra maneira de convencê-lo de que esse é o seu problema, e talvez a solução, se necessário. Basta olhar o que teria acontecido se

    Y1=np.random.uniform(-1, 1, (201, 5000, 1093)).transpose(1,2,0)
    

    Y1é exatamente o mesmo formato que o seu. Mesma matriz 5000x1093x201. E você mantém os mesmos subdados Y1[:,:,0]do formato 5000x1093.

    A única diferença entre o meu Y1e o seu é invisível do ponto de vista puramente "matemático"; a única diferença é onde exatamente, na memória física, os dados são armazenados.

    No meu Y1, Y1[i,j,0]está longe de Y1[i+1,j,0], e longe de Y1[i,j+1,0](mas perto de Y1[i,j,1], mas isso não vai ajudar no seu caso). Você pode ver isso assistindoY1.strides

    Y1.strides
    # (1757544, 1608, 8)
    

    que informa quantos bytes separam dois valores consecutivos ao longo de cada eixo. Você vê que é maior do que o tamanho típico do cache ao longo de todos os eixos, exceto o último, que é o que você não usa

    Enquanto meuY1

    (8744, 8, 43720000)
    

    Claro, o problema é que, quando você reduz seu problema à única parte que é lenta, você pode concluir que deve codificar Y1como eu fiz.

    Mas suponho que seu código não aloca 201 números e nunca usa os outros 200. Dito de outra forma, algumas outras partes não mostradas do seu código provavelmente usam esse terceiro eixo.

    Então, talvez o impulso que você ganha ao ordenar Y1na ordem ideal para esta parte do código teria que ser compensado em outra parte do código por uma computação mais lenta.

    Última observação: ao fazer esse tipo de computação, é importante evitar executar as coisas apenas uma vez. Por causa, também, do cache. O primeiro algoritmo é enviesado porque ele tem que ler W, enquanto os outros dois podem encontrá-lo já esperando no cache (provavelmente não no seu caso, porque seus dados são muito bing. Mas para dados menores, você teria concluído que o primeiro método é mais lento, qualquer que seja o primeiro, apenas porque é o que pagou o custo de carregar os dados no cache

    • 1
  2. D Stanley
    2024-12-03T23:47:00+08:002024-12-03T23:47:00+08:00

    Se você quiser comparar o desempenho de vários métodos, então você precisa considerar as operações atomicamente. Eu consideraria dois cenários:

    1. fatia-copiar-ponto
    Y2 = Y1[:,:,0]
    # get slice time
    Y2 = Y2.copy()
    # get copy time
    Y2 = Y2.dot(W)
    # get dot time
    
    1. fatia-ponto
    Y2 = Y1[:,:,0]
    # get slice time
    Y2 = Y2.dot(W)
    # get dot time
    

    Isso lhe dirá se o tempo está sendo gasto no fatiamento, na cópia ou no ponto. Suspeito que a cópia seja a parte cara para um array grande. Também lhe dirá se dottem desempenho diferente em um array inteiro em comparação a um slice.

    Depois de saber onde está o gargalo, você pode identificar sua pergunta para tornar essa parte mais rápida.

    • 0
  3. Slavensky
    2024-12-03T23:53:05+08:002024-12-03T23:53:05+08:00

    Você pode usar a soma de Einstein para acelerar um pouco o processo.

    np.einsum('ijk,jl->ilk', Y1, W)
    

    onde ijké o formato de Y1e jlé o formato de W. Isso resulta em uma matriz de dimensões (5000, 30, 201). No meu Macbook, essa operação leva 157 segundos, o que é muito mais rápido do que fazer fatiamento 201 vezes.

    • 0

relate perguntas

  • Como classificar o tensor em "lote" por valor de chave específico?

  • Aviso de descontinuação do notebook Jupyter ao encontrar a raiz do determinante de uma matriz

  • Como você concatena matrizes internas do tensor ao longo do eixo?

  • Digite regra de promoção para i4 e S8 no documento numpy

  • Transmitindo uma matriz numpy em uma matriz de tamanho maior usando uma matriz de índice

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Vue 3: Erro na criação "Identificador esperado, mas encontrado 'import'" [duplicado]

    • 1 respostas
  • Marko Smith

    Por que esse código Java simples e pequeno roda 30x mais rápido em todas as JVMs Graal, mas não em nenhuma JVM Oracle?

    • 1 respostas
  • Marko Smith

    Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores?

    • 1 respostas
  • Marko Smith

    Como faço para corrigir um erro MODULE_NOT_FOUND para um módulo que não importei manualmente?

    • 6 respostas
  • Marko Smith

    `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso?

    • 3 respostas
  • Marko Smith

    Quando devo usar um std::inplace_vector em vez de um std::vector?

    • 3 respostas
  • Marko Smith

    Um programa vazio que não faz nada em C++ precisa de um heap de 204 KB, mas não em C

    • 1 respostas
  • Marko Smith

    PowerBI atualmente quebrado com BigQuery: problema de driver Simba com atualização do Windows

    • 2 respostas
  • Marko Smith

    AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos

    • 1 respostas
  • Marko Smith

    Estou tentando fazer o jogo pacman usando apenas o módulo Turtle Random e Math

    • 1 respostas
  • Martin Hope
    Aleksandr Dubinsky Por que a correspondência de padrões com o switch no InetAddress falha com 'não cobre todos os valores de entrada possíveis'? 2024-12-23 06:56:21 +0800 CST
  • Martin Hope
    Phillip Borge Por que esse código Java simples e pequeno roda 30x mais rápido em todas as JVMs Graal, mas não em nenhuma JVM Oracle? 2024-12-12 20:46:46 +0800 CST
  • Martin Hope
    Oodini Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores? 2024-12-12 06:27:11 +0800 CST
  • Martin Hope
    sleeptightAnsiC `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso? 2024-11-09 07:18:53 +0800 CST
  • Martin Hope
    The Mad Gamer Quando devo usar um std::inplace_vector em vez de um std::vector? 2024-10-29 23:01:00 +0800 CST
  • Martin Hope
    Chad Feller O ponto e vírgula agora é opcional em condicionais bash com [[ .. ]] na versão 5.2? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench Por que um traço duplo (--) faz com que esta cláusula MariaDB seja avaliada como verdadeira? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng Por que `dict(id=1, **{'id': 2})` às vezes gera `KeyError: 'id'` em vez de um TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos 2024-03-20 03:12:31 +0800 CST
  • Martin Hope
    MarkB Por que o GCC gera código que executa condicionalmente uma implementação SIMD? 2024-02-17 06:17:14 +0800 CST

Hot tag

python javascript c++ c# java typescript sql reactjs html

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve