.__skip_array_function__
para permitir pular __array_function__
despacho. (https://github.com/numpy/numpy/pull/13389)numpy/core/overrides.py
em C para aumentar a velocidade (https://github.com/numpy/numpy/issues/12028):get_overloaded_types_and_args
array_function_implementation_or_override
ndarray.__array_function__
?array_function_dispatch
?numpy.core
np.core.defchararray
(# 12154)np.einsum
e np.block
(https://github.com/numpy/numpy/pull/12163)numpy.lib
numpy.fft
/ numpy.linalg
(https://github.com/numpy/numpy/pull/12117)arange?
] (https://github.com/numpy/numpy/issues/12379)ndarray.__repr__
não deve contar com __array_function__
(https://github.com/numpy/numpy/pull/12212)stacklevel
deve ser aumentado em 1 para funções agrupadas, então tracebacks apontam para o lugar certo (gh-13329)Pode ser bom fundir um preliminar "Decorar todas as funções públicas NumPy com @array_function_dispatch" para algumas funções de alto perfil e solicitar aos consumidores downstream do protocolo para experimentá-lo
Assim que fundirmos https://github.com/numpy/numpy/pull/12099 , tenho outro PR pronto que adicionará decoradores de despacho para a maior parte de numpy.core
. Vai ser muito fácil terminar as coisas - este levou menos de uma hora para montar.
cc @ eric-wieser @mrocklin @mhvk @hameerabbasi
Veja https://github.com/shoyer/numpy/tree/array-function-easy-impl para meu branch implementando todas as substituições "fáceis" em funções com wrappers Python. As partes restantes são np.block
, np.einsum
e um punhado de funções multiarray escritas inteiramente em C (por exemplo, np.concatenate
). Vou dividir isso em um monte de PRs quando terminarmos com o # 12099.
Observe que não escrevi testes para substituições em cada função individual. Gostaria de adicionar alguns testes de integração quando terminarmos (por exemplo, um array de pato que registra todas as operações aplicadas), mas não acho que seria produtivo escrever testes de despacho para cada função individual. As verificações em # 12099 devem capturar os erros mais comuns em despachantes, e cada linha de código em funções de despachante deve ser executada por testes existentes.
@shoyer - nos testes, concordo que não é particularmente útil escrever testes para cada um; em vez disso, dentro de numpy, pode fazer mais sentido começar a usar as substituições de forma relativamente rápida em MaskedArray
.
@mhvk parece bom para mim, embora eu deixe alguém que usa / conhece MaskedArray assumir a liderança nisso.
Consulte https://github.com/numpy/numpy/pull/12115 , https://github.com/numpy/numpy/pull/12116 , # 12119 e https://github.com/numpy/numpy/pull/ 12117 para PRs implementando suporte a __array_function__
em funções definidas em Python.
@shoyer - vendo algumas das implementações, tenho duas preocupações:
reshape
, a funcionalidade original já fornecia uma maneira de sobrescrevê-la, definindo um método reshape
. Estamos efetivamente descontinuando isso para qualquer classe que defina __array_function__
.np.median
, o uso cuidadoso de np.asanyarray
e ufuncs garantiu que as subclasses já pudessem usá-los. Mas essa funcionalidade não pode mais ser acessada diretamente.Acho que no geral essas duas coisas são provavelmente benefícios, já que simplificamos a interface e podemos otimizar as implementações para ndarray
puros - embora o último sugira que ndarray.__array_function__
deve assumir a conversão de listas, etc., a ndarray
, para que as implementações possam pular essa parte). Ainda assim, pensei em observá-lo, pois me faz temer a implementação disso por Quantity
um pouco mais do que eu pensava - em termos de quantidade de trabalho e possível impacto no desempenho.
embora o último sugira que ndarray .__ array_function__ deve assumir a conversão de listas, etc., em ndarray, de modo que as implementações possam pular essa parte).
Não tenho certeza se estou seguindo aqui.
De fato, estamos efetivamente substituindo o método antigo de substituir funções como reshape
e mean
, embora o método antigo ainda suporte implementações incompletas da API do NumPy.
Não tenho certeza se estou seguindo aqui.
Acho que o problema é que, se implementarmos __array_function__
mesmo para uma única função, os mecanismos anteriores quebram completamente e não há como fazer o failover. É por isso que proponho que revisitemos minha proposta NotImplementedButCoercible
.
@hameerabbasi - sim, esse é o problema. Embora precisemos ser cuidadosos aqui, como tornamos fácil confiar em soluções de fita adesiva das quais realmente preferiríamos nos livrar ... (é por isso que escrevi acima que meus "problemas" podem na verdade ser benefícios ...) . Talvez seja o caso de tentar como está em 1.16 e, em seguida, decidir sobre a experiência real se queremos fornecer um retorno de "ignorar meu __array_function__
para este caso".
Re: estilo do despachante: Minhas preferências de estilo são baseadas em considerações de tempo de memória / importação e verbosidade. Muito simplesmente, mescle os despachantes onde a assinatura provavelmente permanecerá a mesma. Dessa forma, criamos a menor quantidade de objetos e os acertos do cache também serão maiores.
Dito isso, não sou muito contra o estilo lambda.
O estilo para escrever funções de despachante agora surgiu em alguns PRs. Seria bom fazer uma escolha consistente em NumPy.
Temos algumas opções:
Opção 1 : escreva um despachante separado para cada função, por exemplo,
def _sin_dispatcher(a):
return (a,)
@array_function_dispatch(_sin_dispatcher)
def sin(a):
...
def _cos_dispatcher(a):
return (a,)
@array_function_dispatch(_cos_dispatcher)
def cos(a):
...
Vantagens:
sin(x=1)
-> TypeError: _sin_dispatcher() got an unexpected keyword argument 'x'
.Desvantagens:
Opção 2 : reutilizar funções do despachante dentro de um módulo, por exemplo,
def _unary_dispatcher(a):
return (a,)
@array_function_dispatch(_unary_dispatcher)
def sin(a):
...
@array_function_dispatch(_unary_dispatcher)
def cos(a):
...
Vantagens:
Desvantagens:
sin(x=1)
-> TypeError: _unary_dispatcher() got an unexpected keyword argument 'x'
Opção 3 : use funções lambda
quando a definição do despachante caberia em uma linha, por exemplo,
# inline style (shorter)
@array_function_dispatch(lambda a: (a,))
def sin(a):
...
@array_function_dispatch(lambda a, n=None, axis=None, norm=None: (a,))
def fft(a, n=None, axis=-1, norm=None):
...
# multiline style (more readable?)
@array_function_dispatch(
lambda a: (a,)
)
def sin(a):
...
@array_function_dispatch(
lambda a, n=None, axis=None, norm=None: (a,)
)
def fft(a, n=None, axis=-1, norm=None):
...
Vantagens:
Desvantagens:
TypeError: <lambda>() got an unexpected keyword argument 'x'
)@shoyer : editado para adicionar o espaçamento PEP8 de duas linhas para tornar o aspecto de "linhas de código" mais realista
Observe que os problemas de mensagem de erro podem ser corrigidos reconstruindo o objeto de código , embora isso venha com algum custo de tempo de importação. Talvez valha a pena investigar e descobrir o atum de @nschloe para comparar algumas opções.
Sim, o módulo decorador também pode ser usado para gerar definição de função (ele usa uma abordagem um pouco diferente para a geração de código, um pouco mais como namedtuple porque usa exec()
).
Contanto que o erro não seja resolvido, acho que precisamos nos ater às opções com um despachante que tenha um nome claro. Gostaria de agrupar ligeiramente os despachantes (2) por motivos de memória, mas manteria a mensagem de erro muito em mente, então sugeriria chamar o despachante algo como _dispatch_on_x
.
Porém, se pudermos mudar o erro, as coisas mudam. Por exemplo, pode ser tão simples quanto capturar exceções, substituindo <lambda>
pelo nome da função no texto da exceção e, em seguida, levantando novamente. (Ou essa coisa de corrente hoje em dia?)
Concordo que a mensagem de erro deve ser clara, idealmente não deve mudar em nada.
OK, por enquanto acho melhor adiar o uso de lambda
, a menos que tenhamos algum tipo de geração de código funcionando.
https://github.com/numpy/numpy/pull/12175 adiciona um rascunho de como as substituições para funções multiarray (escritas em C) poderiam parecer se adotarmos a abordagem do wrapper Python.
@mattip, onde estamos implementando matmul
como um ufunc? Assim que terminarmos todas essas substituições de __array_function__
, acho que é a última coisa que precisamos para tornar a API pública do NumPy totalmente sobrecarregável. Seria bom ter tudo pronto para o NumPy 1.16!
PR # 11175, que implementa o NEP 20, tem progredido lentamente. É um bloqueador para PR # 11133, que contém o código de loop matmul. Esse ainda precisa ser atualizado e depois verificado através de benchmarks se o novo código não é mais lento que o antigo.
Tenho quatro PRs para revisão que devem completar o conjunto completo de substituições. Avaliações / aprovações / fusões finais serão apreciadas para que possamos começar a testar __array_function__
sério! https://github.com/numpy/numpy/pull/12154 , https://github.com/numpy/numpy/pull/12163 , https://github.com/numpy/numpy/pull/12119 , https: //github.com/numpy/numpy/pull/12175
Adicionar substituições a np.core
causou a falha de alguns testes do pandas (https://github.com/pandas-dev/pandas/issues/23172). Não temos certeza do que está acontecendo ainda, mas devemos definitivamente descobrir antes de lançar.
Veja https://github.com/numpy/numpy/issues/12225 para meu melhor palpite sobre por que isso está causando falhas de teste no dask / pandas.
Alguns benchmarks de tempos de importação (no meu macbook pro com uma unidade de estado sólido):
decorator.decorate
(# 12226): 183,694 msMeu script de benchmark
import numpy as np
import subprocess
times = []
for _ in range(100):
result = subprocess.run("python -X importtime -c 'import numpy'",
shell=True, capture_output=True)
last_line = result.stderr.rstrip().split(b'\n')[-1]
time = float(last_line.decode('ascii')[-15:-7].strip().rstrip())
times.append(time)
print(np.median(times) / 1e3)
Alguma ideia do uso de memória (antes / depois)? Isso também é útil, especialmente para aplicativos de IoT.
Você sabe como medir de forma confiável o uso de memória para um módulo?
No sábado, 20 de outubro de 2018 às 6h56 Hameer Abbasi [email protected]
escrevi:
Alguma ideia do uso de memória (antes / depois)? Isso é meio útil, pois
bem, especialmente para aplicativos IoT.-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/numpy/numpy/issues/12028#issuecomment-431584123 ou mudo
o segmento
https://github.com/notifications/unsubscribe-auth/ABKS1k_IkrJ2YmYReaDrnkNvcH2X0-ZCks5umyuogaJpZM4W3kSC
.
Acho que escrever um script contendo import numpy as np
, adicionar uma instrução sleep e rastrear a memória do processo deve ser suficiente. https://superuser.com/questions/581108/how-can-i-track-and-log-cpu-and-memory-usage-on-a-mac
Qualquer outro desenvolvedor central deseja dar uma olhada rápida (na verdade, inclui apenas duas funções!) Em https://github.com/numpy/numpy/pull/12163? É o último PR adicionando array_function_dispatch
às funções numpy internas.
Para referência, aqui está a diferença de desempenho que vejo ao desativar __array_function__
:
before after ratio
[45718fd7] [4e5aa2cd]
<master> <disable-array-function>
+ 72.5±2ms 132±20ms 1.82 bench_io.LoadtxtCSVdtypes.time_loadtxt_dtypes_csv('complex128', 10000)
- 44.9±2μs 40.8±0.6μs 0.91 bench_ma.Concatenate.time_it('ndarray', 2)
- 15.3±0.3μs 13.3±0.7μs 0.87 bench_core.CountNonzero.time_count_nonzero_multi_axis(2, 100, <type 'object'>)
- 38.4±1μs 32.7±2μs 0.85 bench_linalg.Linalg.time_op('norm', 'longfloat')
- 68.7±3μs 56.5±3μs 0.82 bench_linalg.Linalg.time_op('norm', 'complex256')
- 80.6±4μs 65.9±1μs 0.82 bench_function_base.Median.time_even
- 82.4±2μs 66.8±3μs 0.81 bench_shape_base.Block.time_no_lists(100)
- 73.5±3μs 59.3±3μs 0.81 bench_function_base.Median.time_even_inplace
- 15.2±0.3μs 12.2±0.6μs 0.80 bench_core.CountNonzero.time_count_nonzero_multi_axis(3, 100, <type 'str'>)
- 2.20±0.1ms 1.76±0.04ms 0.80 bench_shape_base.Block2D.time_block2d((1024, 1024), 'uint64', (4, 4))
- 388±20μs 310±10μs 0.80 bench_lib.Pad.time_pad((10, 10, 10), 3, 'linear_ramp')
- 659±20μs 524±20μs 0.80 bench_linalg.Linalg.time_op('det', 'float32')
- 22.9±0.7μs 18.2±0.8μs 0.79 bench_function_base.Where.time_1
- 980±50μs 775±20μs 0.79 bench_shape_base.Block2D.time_block2d((1024, 1024), 'uint32', (4, 4))
- 36.6±1μs 29.0±1μs 0.79 bench_ma.Concatenate.time_it('unmasked', 2)
- 16.4±0.7μs 12.9±0.6μs 0.79 bench_core.CountNonzero.time_count_nonzero_axis(3, 100, <type 'str'>)
- 16.4±0.5μs 12.9±0.4μs 0.79 bench_core.CountNonzero.time_count_nonzero_axis(2, 100, <type 'object'>)
- 141±5μs 110±4μs 0.78 bench_lib.Pad.time_pad((10, 100), (0, 5), 'linear_ramp')
- 18.0±0.6μs 14.1±0.6μs 0.78 bench_core.CountNonzero.time_count_nonzero_axis(3, 100, <type 'object'>)
- 11.9±0.6μs 9.28±0.5μs 0.78 bench_core.CountNonzero.time_count_nonzero_axis(1, 100, <type 'int'>)
- 54.6±3μs 42.4±2μs 0.78 bench_function_base.Median.time_odd_small
- 317±10μs 246±7μs 0.78 bench_lib.Pad.time_pad((10, 10, 10), 1, 'linear_ramp')
- 13.8±0.5μs 10.7±0.7μs 0.77 bench_reduce.MinMax.time_min(<type 'numpy.float64'>)
- 73.3±6μs 56.6±4μs 0.77 bench_lib.Pad.time_pad((1000,), (0, 5), 'mean')
- 14.7±0.7μs 11.4±0.3μs 0.77 bench_core.CountNonzero.time_count_nonzero_axis(2, 100, <type 'str'>)
- 21.5±2μs 16.5±0.6μs 0.77 bench_reduce.MinMax.time_min(<type 'numpy.int64'>)
- 117±4μs 89.2±3μs 0.76 bench_lib.Pad.time_pad((1000,), 3, 'linear_ramp')
- 43.7±1μs 33.4±1μs 0.76 bench_linalg.Linalg.time_op('norm', 'complex128')
- 12.6±0.6μs 9.55±0.2μs 0.76 bench_core.CountNonzero.time_count_nonzero_multi_axis(2, 100, <type 'int'>)
- 636±20μs 482±20μs 0.76 bench_ma.MA.time_masked_array_l100
- 86.6±4μs 65.6±4μs 0.76 bench_lib.Pad.time_pad((1000,), (0, 5), 'linear_ramp')
- 120±4μs 90.4±2μs 0.75 bench_lib.Pad.time_pad((1000,), 1, 'linear_ramp')
- 160±5μs 119±8μs 0.74 bench_ma.Concatenate.time_it('ndarray+masked', 100)
- 14.4±0.6μs 10.7±0.3μs 0.74 bench_core.CountNonzero.time_count_nonzero_multi_axis(1, 100, <type 'str'>)
- 15.7±0.4μs 11.7±0.6μs 0.74 bench_core.CountNonzero.time_count_nonzero_multi_axis(2, 100, <type 'str'>)
- 21.8±2μs 16.1±0.7μs 0.74 bench_reduce.MinMax.time_max(<type 'numpy.int64'>)
- 11.9±0.6μs 8.79±0.3μs 0.74 bench_core.CountNonzero.time_count_nonzero_axis(2, 100, <type 'bool'>)
- 53.8±3μs 39.4±2μs 0.73 bench_function_base.Median.time_even_small
- 106±20μs 76.7±4μs 0.73 bench_function_base.Select.time_select
- 168±10μs 122±4μs 0.72 bench_shape_base.Block2D.time_block2d((512, 512), 'uint32', (2, 2))
- 12.5±0.5μs 8.96±0.4μs 0.72 bench_core.CountNonzero.time_count_nonzero_multi_axis(1, 100, <type 'int'>)
- 162±10μs 115±5μs 0.71 bench_function_base.Percentile.time_percentile
- 12.9±1μs 9.12±0.4μs 0.71 bench_random.Random.time_rng('normal')
- 9.71±0.4μs 6.88±0.3μs 0.71 bench_core.CorrConv.time_convolve(1000, 10, 'full')
- 15.1±0.8μs 10.7±0.4μs 0.71 bench_reduce.MinMax.time_max(<type 'numpy.float64'>)
- 153±9μs 108±7μs 0.71 bench_shape_base.Block2D.time_block2d((1024, 1024), 'uint8', (2, 2))
- 109±5μs 76.9±5μs 0.71 bench_ma.Concatenate.time_it('ndarray+masked', 2)
- 34.3±1μs 24.2±0.6μs 0.71 bench_linalg.Linalg.time_op('norm', 'complex64')
- 9.80±0.2μs 6.84±0.5μs 0.70 bench_core.CorrConv.time_convolve(1000, 10, 'same')
- 27.4±6μs 19.1±2μs 0.70 bench_core.CountNonzero.time_count_nonzero_axis(1, 10000, <type 'bool'>)
- 9.35±0.4μs 6.50±0.3μs 0.70 bench_core.CorrConv.time_convolve(50, 100, 'full')
- 65.2±4μs 45.2±1μs 0.69 bench_shape_base.Block.time_block_simple_row_wise(100)
- 12.9±1μs 8.89±0.3μs 0.69 bench_core.CountNonzero.time_count_nonzero_axis(3, 100, <type 'bool'>)
- 19.6±3μs 13.5±0.4μs 0.69 bench_core.CountNonzero.time_count_nonzero_multi_axis(3, 100, <type 'object'>)
- 75.6±2μs 52.1±3μs 0.69 bench_lib.Pad.time_pad((10, 10, 10), (0, 5), 'reflect')
- 12.4±1μs 8.51±0.4μs 0.69 bench_core.CountNonzero.time_count_nonzero_multi_axis(3, 100, <type 'bool'>)
- 172±30μs 117±4μs 0.68 bench_ma.Concatenate.time_it('unmasked+masked', 100)
- 23.1±0.5μs 15.8±0.9μs 0.68 bench_linalg.Linalg.time_op('norm', 'int16')
- 8.18±0.9μs 5.57±0.1μs 0.68 bench_core.CorrConv.time_correlate(1000, 10, 'full')
- 153±5μs 103±3μs 0.68 bench_function_base.Percentile.time_quartile
- 758±100μs 512±20μs 0.68 bench_linalg.Linalg.time_op('det', 'int16')
- 55.4±6μs 37.4±1μs 0.68 bench_ma.Concatenate.time_it('masked', 2)
- 234±30μs 157±5μs 0.67 bench_shape_base.Block.time_nested(100)
- 103±4μs 69.3±3μs 0.67 bench_linalg.Eindot.time_dot_d_dot_b_c
- 19.2±0.4μs 12.9±0.6μs 0.67 bench_core.Core.time_tril_l10x10
- 122±7μs 81.7±4μs 0.67 bench_lib.Pad.time_pad((10, 10, 10), 3, 'edge')
- 22.9±1μs 15.3±0.5μs 0.67 bench_linalg.Linalg.time_op('norm', 'int32')
- 16.6±2μs 11.0±0.3μs 0.66 bench_core.CountNonzero.time_count_nonzero_multi_axis(1, 100, <type 'object'>)
- 9.98±0.3μs 6.58±0.1μs 0.66 bench_core.CorrConv.time_convolve(1000, 10, 'valid')
- 118±6μs 77.9±4μs 0.66 bench_shape_base.Block2D.time_block2d((512, 512), 'uint16', (2, 2))
- 212±50μs 140±8μs 0.66 bench_lib.Pad.time_pad((10, 10, 10), (0, 5), 'mean')
- 21.9±0.7μs 14.4±0.5μs 0.66 bench_linalg.Linalg.time_op('norm', 'int64')
- 131±5μs 85.9±5μs 0.65 bench_lib.Pad.time_pad((10, 10, 10), 3, 'constant')
- 56.8±2μs 37.0±3μs 0.65 bench_lib.Pad.time_pad((1000,), (0, 5), 'constant')
- 58.9±3μs 38.1±1μs 0.65 bench_lib.Pad.time_pad((10, 100), (0, 5), 'reflect')
- 72.1±2μs 46.5±3μs 0.64 bench_lib.Pad.time_pad((10, 100), (0, 5), 'constant')
- 8.66±0.3μs 5.58±0.2μs 0.64 bench_core.CorrConv.time_correlate(50, 100, 'full')
- 300±30μs 193±10μs 0.64 bench_shape_base.Block2D.time_block2d((1024, 1024), 'uint8', (4, 4))
- 15.9±5μs 10.2±0.3μs 0.64 bench_core.CountNonzero.time_count_nonzero_axis(3, 100, <type 'int'>)
- 13.7±0.5μs 8.80±0.1μs 0.64 bench_random.Random.time_rng('uniform')
- 8.60±0.5μs 5.50±0.2μs 0.64 bench_core.CorrConv.time_correlate(1000, 10, 'same')
- 44.7±2μs 28.5±0.7μs 0.64 bench_lib.Pad.time_pad((1000,), 1, 'reflect')
- 72.7±3μs 46.2±2μs 0.64 bench_lib.Pad.time_pad((10, 10, 10), 3, 'wrap')
- 567±50μs 360±40μs 0.63 bench_shape_base.Block2D.time_block2d((512, 512), 'uint64', (2, 2))
- 58.0±3μs 36.7±2μs 0.63 bench_lib.Pad.time_pad((10, 100), 3, 'reflect')
- 219±30μs 138±7μs 0.63 bench_lib.Pad.time_pad((10, 100), 1, 'mean')
- 261±60μs 164±10μs 0.63 bench_lib.Pad.time_pad((10, 100), 1, 'linear_ramp')
- 825±100μs 519±30μs 0.63 bench_shape_base.Block2D.time_block2d((512, 512), 'uint64', (4, 4))
- 121±5μs 75.7±2μs 0.63 bench_lib.Pad.time_pad((10, 10, 10), 1, 'constant')
- 8.16±0.2μs 5.08±0.4μs 0.62 bench_core.CorrConv.time_convolve(50, 100, 'same')
- 66.6±3μs 41.3±2μs 0.62 bench_lib.Pad.time_pad((1000,), 3, 'constant')
- 53.1±3μs 32.9±0.8μs 0.62 bench_lib.Pad.time_pad((10, 100), 3, 'wrap')
- 285±60μs 177±10μs 0.62 bench_lib.Pad.time_pad((10, 10, 10), (0, 5), 'linear_ramp')
- 8.30±0.9μs 5.14±0.1μs 0.62 bench_core.CorrConv.time_correlate(1000, 10, 'valid')
- 115±3μs 71.2±3μs 0.62 bench_shape_base.Block2D.time_block2d((256, 256), 'uint64', (2, 2))
- 19.1±0.5μs 11.8±0.6μs 0.62 bench_linalg.Linalg.time_op('norm', 'float64')
- 95.3±5μs 58.6±2μs 0.62 bench_lib.Pad.time_pad((10, 100), 1, 'constant')
- 44.6±1μs 27.2±0.9μs 0.61 bench_lib.Pad.time_pad((1000,), (0, 5), 'edge')
- 447±20μs 270±10μs 0.61 bench_shape_base.Block2D.time_block2d((1024, 1024), 'uint16', (4, 4))
- 53.9±2μs 32.6±2μs 0.60 bench_lib.Pad.time_pad((10, 100), 1, 'wrap')
- 11.6±1μs 6.97±0.4μs 0.60 bench_reduce.MinMax.time_max(<type 'numpy.float32'>)
- 95.9±5μs 57.7±2μs 0.60 bench_lib.Pad.time_pad((10, 100), 3, 'constant')
- 47.2±2μs 28.2±2μs 0.60 bench_lib.Pad.time_pad((1000,), (0, 5), 'reflect')
- 5.51±0.2μs 3.27±0.07μs 0.59 bench_core.CountNonzero.time_count_nonzero(3, 100, <type 'object'>)
- 74.3±3μs 44.0±2μs 0.59 bench_lib.Pad.time_pad((10, 10, 10), (0, 5), 'wrap')
- 76.2±3μs 45.0±0.8μs 0.59 bench_lib.Pad.time_pad((10, 10, 10), 1, 'reflect')
- 57.1±1μs 33.5±2μs 0.59 bench_lib.Pad.time_pad((10, 100), (0, 5), 'wrap')
- 52.0±2μs 30.4±1μs 0.58 bench_lib.Pad.time_pad((1000,), 1, 'edge')
- 42.6±2μs 24.9±0.9μs 0.58 bench_lib.Pad.time_pad((1000,), 3, 'wrap')
- 15.0±3μs 8.73±0.3μs 0.58 bench_core.CountNonzero.time_count_nonzero_multi_axis(1, 100, <type 'bool'>)
- 16.0±3μs 9.29±0.3μs 0.58 bench_core.CountNonzero.time_count_nonzero_multi_axis(3, 100, <type 'int'>)
- 53.1±1μs 30.9±2μs 0.58 bench_lib.Pad.time_pad((1000,), 3, 'edge')
- 88.0±8μs 51.1±3μs 0.58 bench_lib.Pad.time_pad((10, 10, 10), 3, 'reflect')
- 44.6±2μs 25.9±1μs 0.58 bench_lib.Pad.time_pad((1000,), (0, 5), 'wrap')
- 90.3±5μs 51.9±1μs 0.57 bench_shape_base.Block2D.time_block2d((512, 512), 'uint8', (2, 2))
- 15.6±0.5μs 8.93±0.3μs 0.57 bench_linalg.Linalg.time_op('norm', 'float32')
- 102±6μs 58.3±0.9μs 0.57 bench_lib.Pad.time_pad((10, 10, 10), 1, 'edge')
- 80.1±4μs 45.6±3μs 0.57 bench_lib.Pad.time_pad((10, 100), 3, 'edge')
- 44.2±2μs 24.9±1μs 0.56 bench_lib.Pad.time_pad((1000,), 1, 'wrap')
- 71.6±8μs 39.5±1μs 0.55 bench_lib.Pad.time_pad((10, 10, 10), 1, 'wrap')
- 81.7±10μs 44.8±2μs 0.55 bench_lib.Pad.time_pad((10, 100), 1, 'edge')
- 420±90μs 230±10μs 0.55 bench_shape_base.Block.time_3d(10, 'block')
- 114±20μs 62.3±2μs 0.55 bench_lib.Pad.time_pad((10, 10, 10), (0, 5), 'constant')
- 5.76±0.1μs 3.13±0.08μs 0.54 bench_core.CorrConv.time_convolve(50, 10, 'same')
- 5.30±0.1μs 2.84±0.08μs 0.54 bench_core.CorrConv.time_correlate(50, 100, 'valid')
- 92.5±4μs 49.3±1μs 0.53 bench_shape_base.Block2D.time_block2d((256, 256), 'uint32', (2, 2))
- 13.5±3μs 7.07±0.2μs 0.52 bench_reduce.MinMax.time_min(<type 'numpy.float32'>)
- 7.66±1μs 3.88±0.2μs 0.51 bench_core.CorrConv.time_convolve(50, 100, 'valid')
- 29.0±3μs 14.5±0.8μs 0.50 bench_shape_base.Block.time_no_lists(10)
- 6.62±0.3μs 3.30±0.2μs 0.50 bench_core.CorrConv.time_convolve(1000, 1000, 'valid')
- 74.2±7μs 36.2±0.9μs 0.49 bench_shape_base.Block2D.time_block2d((256, 256), 'uint16', (2, 2))
- 5.55±0.3μs 2.70±0.2μs 0.49 bench_core.CorrConv.time_convolve(50, 10, 'valid')
- 73.9±20μs 35.8±2μs 0.48 bench_lib.Pad.time_pad((10, 100), 1, 'reflect')
- 224±20μs 107±7μs 0.48 bench_shape_base.Block2D.time_block2d((256, 256), 'uint64', (4, 4))
- 3.87±0.1μs 1.83±0.06μs 0.47 bench_core.CountNonzero.time_count_nonzero(2, 100, <type 'str'>)
- 109±30μs 51.5±3μs 0.47 bench_lib.Pad.time_pad((10, 10, 10), (0, 5), 'edge')
- 240±20μs 112±4μs 0.47 bench_shape_base.Block2D.time_block2d((512, 512), 'uint16', (4, 4))
- 337±40μs 158±7μs 0.47 bench_shape_base.Block2D.time_block2d((512, 512), 'uint32', (4, 4))
- 188±8μs 88.0±2μs 0.47 bench_shape_base.Block2D.time_block2d((512, 512), 'uint8', (4, 4))
- 4.39±0.2μs 2.04±0.09μs 0.47 bench_core.CountNonzero.time_count_nonzero(3, 10000, <type 'bool'>)
- 73.2±4μs 33.9±0.5μs 0.46 bench_shape_base.Block2D.time_block2d((128, 128), 'uint64', (2, 2))
- 5.48±1μs 2.44±0.1μs 0.45 bench_core.CountNonzero.time_count_nonzero(2, 100, <type 'object'>)
- 4.46±0.1μs 1.97±0.08μs 0.44 bench_core.CorrConv.time_correlate(50, 10, 'full')
- 30.4±9μs 13.3±0.3μs 0.44 bench_shape_base.Block.time_no_lists(1)
- 7.05±0.2μs 3.05±0.06μs 0.43 bench_reduce.SmallReduction.time_small
- 7.35±1μs 3.12±0.2μs 0.42 bench_core.CorrConv.time_convolve(50, 10, 'full')
- 4.36±0.1μs 1.84±0.07μs 0.42 bench_core.CorrConv.time_correlate(50, 10, 'same')
- 3.51±0.2μs 1.46±0.05μs 0.42 bench_core.CountNonzero.time_count_nonzero(1, 100, <type 'object'>)
- 4.03±0.05μs 1.66±0.1μs 0.41 bench_core.CorrConv.time_correlate(1000, 1000, 'valid')
- 199±10μs 80.1±3μs 0.40 bench_shape_base.Block2D.time_block2d((256, 256), 'uint32', (4, 4))
- 3.98±0.2μs 1.60±0.08μs 0.40 bench_core.CountNonzero.time_count_nonzero(2, 10000, <type 'bool'>)
- 61.8±2μs 24.8±1μs 0.40 bench_shape_base.Block2D.time_block2d((256, 256), 'uint8', (2, 2))
- 4.13±0.1μs 1.62±0.05μs 0.39 bench_core.CorrConv.time_correlate(50, 10, 'valid')
- 61.6±2μs 23.9±1μs 0.39 bench_shape_base.Block2D.time_block2d((128, 128), 'uint32', (2, 2))
- 184±10μs 70.5±3μs 0.38 bench_shape_base.Block2D.time_block2d((256, 256), 'uint16', (4, 4))
- 56.1±4μs 21.0±0.9μs 0.38 bench_shape_base.Block2D.time_block2d((64, 64), 'uint64', (2, 2))
- 40.0±2μs 15.0±0.6μs 0.37 bench_shape_base.Block.time_block_simple_column_wise(10)
- 121±2μs 45.1±2μs 0.37 bench_shape_base.Block.time_nested(1)
- 179±4μs 66.1±4μs 0.37 bench_shape_base.Block2D.time_block2d((128, 128), 'uint64', (4, 4))
- 59.8±2μs 22.0±1μs 0.37 bench_shape_base.Block2D.time_block2d((128, 128), 'uint16', (2, 2))
- 3.19±0.05μs 1.17±0.02μs 0.37 bench_core.CountNonzero.time_count_nonzero(1, 100, <type 'str'>)
- 54.0±3μs 19.7±1μs 0.37 bench_shape_base.Block2D.time_block2d((32, 32), 'uint64', (2, 2))
- 56.9±1μs 20.7±0.7μs 0.36 bench_shape_base.Block2D.time_block2d((64, 64), 'uint32', (2, 2))
- 3.14±0.1μs 1.14±0.04μs 0.36 bench_core.CountNonzero.time_count_nonzero(1, 10000, <type 'bool'>)
- 92.7±2μs 33.7±2μs 0.36 bench_shape_base.Block.time_block_complicated(1)
- 104±4μs 37.8±1μs 0.36 bench_shape_base.Block.time_block_complicated(10)
- 128±5μs 45.5±2μs 0.36 bench_shape_base.Block.time_nested(10)
- 196±100μs 69.4±3μs 0.35 bench_ma.Concatenate.time_it('unmasked+masked', 2)
- 153±5μs 53.9±2μs 0.35 bench_shape_base.Block2D.time_block2d((128, 128), 'uint16', (4, 4))
- 39.4±2μs 13.8±0.5μs 0.35 bench_shape_base.Block.time_block_simple_column_wise(1)
- 53.5±2μs 18.7±1μs 0.35 bench_shape_base.Block2D.time_block2d((32, 32), 'uint8', (2, 2))
- 55.2±2μs 19.3±0.6μs 0.35 bench_shape_base.Block2D.time_block2d((32, 32), 'uint16', (2, 2))
- 16.9±1μs 5.89±0.5μs 0.35 bench_core.Core.time_dstack_l
- 60.6±3μs 21.1±0.6μs 0.35 bench_shape_base.Block2D.time_block2d((128, 128), 'uint8', (2, 2))
- 25.5±0.2μs 8.88±0.3μs 0.35 bench_shape_base.Block.time_block_simple_row_wise(10)
- 54.6±3μs 19.0±0.6μs 0.35 bench_shape_base.Block2D.time_block2d((16, 16), 'uint64', (2, 2))
- 52.6±2μs 18.2±0.7μs 0.35 bench_shape_base.Block2D.time_block2d((16, 16), 'uint16', (2, 2))
- 6.57±2μs 2.25±0.08μs 0.34 bench_core.CountNonzero.time_count_nonzero(3, 100, <type 'str'>)
- 24.3±1μs 8.30±0.6μs 0.34 bench_shape_base.Block.time_block_simple_row_wise(1)
- 148±3μs 50.0±3μs 0.34 bench_shape_base.Block2D.time_block2d((16, 16), 'uint32', (4, 4))
- 171±8μs 57.9±4μs 0.34 bench_shape_base.Block2D.time_block2d((256, 256), 'uint8', (4, 4))
- 159±5μs 53.8±1μs 0.34 bench_shape_base.Block2D.time_block2d((64, 64), 'uint64', (4, 4))
- 171±20μs 57.7±2μs 0.34 bench_shape_base.Block2D.time_block2d((128, 128), 'uint32', (4, 4))
- 3.15±0.3μs 1.06±0.03μs 0.34 bench_core.CountNonzero.time_count_nonzero(3, 100, <type 'int'>)
- 55.7±5μs 18.7±0.2μs 0.34 bench_shape_base.Block2D.time_block2d((16, 16), 'uint8', (2, 2))
- 158±7μs 52.6±3μs 0.33 bench_shape_base.Block2D.time_block2d((128, 128), 'uint8', (4, 4))
- 153±4μs 50.7±1μs 0.33 bench_shape_base.Block2D.time_block2d((32, 32), 'uint64', (4, 4))
- 152±7μs 50.3±1μs 0.33 bench_shape_base.Block2D.time_block2d((16, 16), 'uint8', (4, 4))
- 53.6±3μs 17.7±0.4μs 0.33 bench_shape_base.Block2D.time_block2d((16, 16), 'uint32', (2, 2))
- 156±4μs 51.4±3μs 0.33 bench_shape_base.Block2D.time_block2d((64, 64), 'uint8', (4, 4))
- 148±3μs 48.2±2μs 0.33 bench_shape_base.Block2D.time_block2d((16, 16), 'uint16', (4, 4))
- 160±10μs 52.0±1μs 0.33 bench_shape_base.Block2D.time_block2d((64, 64), 'uint32', (4, 4))
- 159±8μs 51.4±3μs 0.32 bench_shape_base.Block2D.time_block2d((64, 64), 'uint16', (4, 4))
- 59.8±3μs 19.3±1μs 0.32 bench_shape_base.Block2D.time_block2d((32, 32), 'uint32', (2, 2))
- 153±4μs 49.4±2μs 0.32 bench_shape_base.Block2D.time_block2d((32, 32), 'uint32', (4, 4))
- 15.6±0.6μs 5.03±0.3μs 0.32 bench_core.Core.time_vstack_l
- 154±7μs 49.7±2μs 0.32 bench_shape_base.Block2D.time_block2d((32, 32), 'uint8', (4, 4))
- 59.6±6μs 19.1±0.8μs 0.32 bench_shape_base.Block2D.time_block2d((64, 64), 'uint8', (2, 2))
- 3.03±0.4μs 969±30ns 0.32 bench_core.CountNonzero.time_count_nonzero(2, 100, <type 'int'>)
- 120±10μs 38.4±2μs 0.32 bench_shape_base.Block.time_3d(1, 'block')
- 156±5μs 49.3±1μs 0.32 bench_shape_base.Block2D.time_block2d((16, 16), 'uint64', (4, 4))
- 164±10μs 49.3±2μs 0.30 bench_shape_base.Block2D.time_block2d((32, 32), 'uint16', (4, 4))
- 65.7±10μs 19.6±0.7μs 0.30 bench_shape_base.Block2D.time_block2d((64, 64), 'uint16', (2, 2))
- 2.82±0.08μs 732±30ns 0.26 bench_core.CountNonzero.time_count_nonzero(1, 100, <type 'int'>)
- 2.77±0.07μs 664±30ns 0.24 bench_core.CountNonzero.time_count_nonzero(2, 100, <type 'bool'>)
- 2.61±0.1μs 624±20ns 0.24 bench_core.CountNonzero.time_count_nonzero(1, 100, <type 'bool'>)
- 16.8±3μs 3.97±0.2μs 0.24 bench_core.Core.time_hstack_l
- 2.78±0.1μs 637±20ns 0.23 bench_core.CountNonzero.time_count_nonzero(3, 100, <type 'bool'>)
- 2.36±0.2μs 207±5ns 0.09 bench_overrides.ArrayFunction.time_mock_broadcast_to_numpy
- 2.68±0.1μs 221±7ns 0.08 bench_overrides.ArrayFunction.time_mock_concatenate_numpy
- 2.58±0.1μs 201±10ns 0.08 bench_overrides.ArrayFunction.time_mock_broadcast_to_duck
- 3.02±0.2μs 222±6ns 0.07 bench_overrides.ArrayFunction.time_mock_concatenate_duck
- 4.29±0.3μs 216±6ns 0.05 bench_overrides.ArrayFunction.time_mock_concatenate_mixed
- 142±20μs 213±8ns 0.00 bench_overrides.ArrayFunction.time_mock_concatenate_many
SOME BENCHMARKS HAVE CHANGED SIGNIFICANTLY.
consulte também https://docs.google.com/spreadsheets/d/15-AFI_cmZqfkU6mo2p1znsQF2E52PEXpF68QqYqEar4/edit#gid = 0 para uma planilha.
Não surpreendentemente, a maior diferença de desempenho é para funções que chamam outras funções numpy internamente muitas vezes, por exemplo, para np.block()
.
@shoyer - Fiquei um pouco desconcertado com o tempo extra gasto ... Provavelmente, nós realmente deveríamos ter uma implementação C, mas nesse meio tempo fiz um PR com algumas pequenas mudanças que economizam algum tempo para o caso comum de apenas um tipo e para o caso em que o único tipo é ndarray
. Veja # 12321.
@shoyer - Eu levantei duas questões na lista de e-mails que provavelmente também devem ser
types
? (em vez de apenas aqueles de argumentos que fornecem uma substituição.) Parece útil saber para implementações. (consulte # 12327).ndarray.__array_function__
aceitar subclasses mesmo que elas substituam __array_function__
? Isso seria razoável, dado o princípio de substituição de Liskov e dado que a subclasse já teve a chance de sair. Isso implicaria chamar a implementação em vez da função pública dentro de ndarray.__array_function__
. (E algo semelhante em __array_ufunc__
...) Veja # 12328 para um teste de __array_function__
apenas.@shoyer - consulte # 12327 para uma implementação rápida de (1) - se seguirmos esse caminho, acho que também devemos ajustar o NEP.
E # 12328 para um teste de (2), principalmente para ver como fica.
Eu sou +1 em ambas as modificações aqui.
O nome das funções do despachante em mensagens de erro apareceu novamente em https://github.com/numpy/numpy/pull/12789 , onde alguém ficou surpreso ao ver TypeError: _pad_dispatcher missing 1 required positional argument
Além das alternativas descritas acima https://github.com/numpy/numpy/issues/12028#issuecomment -429377396 (atualmente usamos 2), adicionarei uma quarta opção:
Opção 4 : escreva um despachante separado para cada função, com o mesmo nome da função:
def sin(a):
return (a,)
@array_function_dispatch(sin)
def sin(a):
...
def cos(a):
return (a,)
@array_function_dispatch(cos)
def cos(a):
...
Vantagens:
Desvantagens:
pad
recebeu os argumentos errados (mas temos testes para verificar se eles são mantidos em sincronia).Eu acho que para manter o código atual funcionando, a função real deve vir _após_ o despachante.
Certo, mas podemos dar a ele o mesmo nome do despachante. O nome do despachante será sobrescrito.
Seria ótimo poder definir o despacho personalizado para funções como np.arange ou np.empty.
Eu acho que uma opção seria para NumPy despachar em escalares, bem como em matrizes. Isso é incompatível com o NEP? Alguma coisa quebraria com essa mudança?
Para discussões sobre np.arange
, consulte https://github.com/numpy/numpy/issues/12379.
Não vejo como np.empty()
poderia fazer o despacho - não há nada para despachar, apenas uma forma e um tipo de d. Mas certamente np.empty_like()
poderia despachar com uma forma sobrescrita - é exatamente isso que https://github.com/numpy/numpy/pull/13046 tem a ver com o suporte.
Opção 4 : escreva um despachante separado para cada função, com o mesmo nome da função:
Alguma objeção à adoção desta opção? Acho que é provavelmente a escolha mais amigável do ponto de vista do usuário.
Não vejo como np.empty () poderia fazer o despacho - não há nada para despachar, apenas uma forma e um dtype
Você pode querer despachar em qualquer um deles. Por exemplo, aqui está um objeto de forma personalizada, que podemos desejar enviar de forma diferente.
Este exemplo não é muito útil, mas a ideia é que eu tenho um objeto preguiçoso que se comporta como forma, mas não retorna inteiros, ele retorna expressões. Por exemplo, seria bom ser capaz de fazer algo assim:
class ExprShape:
def __getitem__(self, i):
return ('getitem', self, i)
def __len__(self):
return ('len', self)
numpy.empty(ExprShape())
Que eu gostaria de substituir para retornar algo como ExprArray('empty', ExprShape())
.
Sim, em princípio também poderíamos despachar na forma. Isso adicionaria complexidade / sobrecarga adicional ao protocolo. Você tem casos de uso em que usar um array como modelo (como empty_like
com shape
) não seria suficiente?
Os outros casos em que consigo pensar são o argumento size
para np.random.RandomState
métodos, mas observe que atualmente não há suporte para eles - consulte http://www.numpy.org/ neps / nep-0018-array-function-protocol.html # callable -objects-generated-at-runtime
Você tem casos de uso em que usar um array como modelo (como empty_like com forma) não seria suficiente?
Se estivermos usando uma API existente que depende do NumPy e quisermos que funcione de forma transparente em um backend diferente, sem alterar o código-fonte existente.
Por exemplo, digamos que estivéssemos tentando chamar scipy.optimize.differential_evolution
com NP como matrizes, que criam um gráfico de chamadas em vez de serem executados imediatamente.
Você pode ver aqui que seria útil se pudéssemos alterar np.full
para criar uma matriz simbólica em vez de uma matriz numpy padrão, se a entrada passada também fosse simbólica.
Se estivermos usando uma API existente que depende do NumPy e quisermos que funcione de forma transparente em um backend diferente, sem alterar o código-fonte existente.
Em geral, isso não é possível. A construção de array explícito como np.array()
definitivamente precisará ser reescrita para ser compatível com a digitação duck.
Nesse caso, mudar energies = np.full(num_members, np.inf)
para energies = np.full_like(population, np.inf, shape=num_members)
parece uma mudança fácil e legível.
Em geral, isso não é possível. A construção de array explícito como np.array () definitivamente precisará ser reescrita para ser compatível com a digitação duck.
Existe uma proposta em torno de fazer esse tipo de alteração ou você está dizendo que apoiar o envio de np.array
seria muito difícil e, portanto, não poderemos nunca chegar a 100% de suporte?
Neste caso, trocar energias = np.full (num_members, np.inf) para energies = np.full_like (população, np.inf, forma = num_members) parece uma mudança fácil e legível.
Definitivamente. Mas há muitos casos em que você não controla o código-fonte ou deseja oferecer suporte aos usuários no uso das funções que eles conhecem e amam, tanto quanto possível.
Existem outras maneiras de fornecer essa experiência aos usuários, como:
Ambas as opções podem ser necessárias em certos casos (como permitir que os usuários chamem np.full
e retornem um resultado simbólico atualmente), mas se bem entendi, o objetivo do NEP-18 é tentar limitar quando eles são necessários e permitir que as pessoas usem o NumPy original em mais casos.
Eu entendo que há uma compensação de desempenho / complexidade aqui e pode ser um bom motivo para não implementá-los. Mas pode forçar os usuários a explorar outros meios para obter a flexibilidade que desejam.
Existe uma proposta em torno de fazer esse tipo de alteração ou você está dizendo que apoiar o envio de
np.array
seria muito difícil e, portanto, não poderemos nunca chegar a 100% de suporte?
NEP 22 tem alguma discussão sobre as opções aqui. Não acho que possamos alterar com segurança a semântica de np.asarray()
para retornar qualquer coisa diferente de um objeto numpy.ndarray
- precisaremos de um novo protocolo para isso.
O problema é que np.asarray()
é atualmente a forma idiomática de converter para um objeto array numpy, que usa pode e espera corresponder exatamente a numpy.ndarray
, por exemplo, no layout de memória.
Certamente há muitos casos de uso em que esse não é o caso, mas mudar esse comportamento quebraria muitos códigos downstream, portanto, não é um iniciante. Os projetos downstream precisarão aceitar pelo menos esse aspecto da tipagem de pato de matriz.
Eu entendo que há uma compensação de desempenho / complexidade aqui e pode ser um bom motivo para não implementá-los. Mas pode forçar os usuários a explorar outros meios para obter a flexibilidade que desejam.
Sim. NEP 18 não pretende ser uma solução completa para alternativas NumPy drop-in, mas é um passo nessa direção.
Elaborei uma revisão do NEP-18 para adicionar um atributo __numpy_implementation__
:
https://github.com/numpy/numpy/pull/13305
Ocorreu-me que esquecemos de distorcer as funções em numpy.testing
: https://github.com/numpy/numpy/issues/13588
Eu vou fazer isso em breve ...
Há uma revisão que gostaria de ver no NEP, especificamente para esclarecer quais garantias o NEP-18 oferece aos autores de subclasses: https://github.com/numpy/numpy/pull/13633
Marquei as tarefas de usabilidade concluídas desde que o gh-13329 foi corrigido. Decidimos- # 13588 podemos esperar até depois do lançamento do 1.17. Isso deixa as melhorias na documentação e arange
gh-12379 ainda abertas para inclusão no 1.17.
Também há # 13728 - um bug no despachante para histogram[2d]d
Isso deixa as melhorias na documentação e o arange gh-12379 ainda aberto para inclusão no 1.17.
Um problema de documentação estava faltando, então abri gh-13844. Acho que os documentos são muito mais importantes do que a questão aberta arange
.
@shoyer podemos fechar isso?
Comentários muito úteis
NEP 22 tem alguma discussão sobre as opções aqui. Não acho que possamos alterar com segurança a semântica de
np.asarray()
para retornar qualquer coisa diferente de um objetonumpy.ndarray
- precisaremos de um novo protocolo para isso.O problema é que
np.asarray()
é atualmente a forma idiomática de converter para um objeto array numpy, que usa pode e espera corresponder exatamente anumpy.ndarray
, por exemplo, no layout de memória.Certamente há muitos casos de uso em que esse não é o caso, mas mudar esse comportamento quebraria muitos códigos downstream, portanto, não é um iniciante. Os projetos downstream precisarão aceitar pelo menos esse aspecto da tipagem de pato de matriz.
Sim. NEP 18 não pretende ser uma solução completa para alternativas NumPy drop-in, mas é um passo nessa direção.