Julia: Arraypocalypse agora e depois

Criado em 16 set. 2015  ·  276Comentários  ·  Fonte: JuliaLang/julia

Esta edição substitui a 0,4 trabalhos no sentido de nirvana array (# 7941), e faixas as questões que visam concluir durante 0,5 e além - agora atualizado através do trabalho em 0,7. Esta é uma questão abrangente e rastreará tarefas específicas em outras questões. Sinta-se à vontade para adicionar coisas que perdi.

Tecnologias subjacentes necessárias

  • [x] Verificação e remoção de limites nativos de Julia (# 7799). Várias tentativas foram feitas nisso, mas acredito que o plano de ação atual é fazer @inbounds elide blocos de código ocultos dentro de uma macro @boundscheck , propagando para baixo apenas um nível de inlining (https: / /github.com/JuliaLang/julia/issues/7799#issuecomment-117362695). Este é um requisito importante para as etapas subsequentes. (implementado em # 14474)
  • [x] ReshapedArrays (# 10507). Requer melhor desempenho: https://groups.google.com/d/msg/julia-dev/7M5qzmXIChM/kOTlGSIvAwAJ

Principais mudanças de comportamento de quebra de 0,5

  • [x] Dimensões de queda indexadas por um escalar (https://github.com/JuliaLang/julia/issues/4774#issuecomment-81228816; mais geralmente, corte no estilo APL, em que a classificação de uma fatia é a soma das classificações de os índices, veja abaixo). PR em # 13612.
  • [x] Mude para a suspensão de uso da concatenação (# 8599)
  • [x] Remova o comportamento não operacional padrão para (c) transpor (# 13171)
  • [x] Alterar alterar o comportamento de sub para slice (# 16846)

Principais mudanças de 0,6 de quebra de comportamento

  • [x] A transposição do vetor retorna um covetor (https://github.com/JuliaLang/julia/issues/4774#issuecomment-81228816). Implementação em # 19670.
  • [x] A conjugação de vetores retorna um invólucro lento (# 20047)

Possíveis mudanças de quebra futuras

  • [x] Transposição de matriz e conjugação retornam invólucros lazy (# 25364)
  • [] Retorna fatias como visualizações. Ainda não está claro se as possíveis alterações de desempenho são consistentes e grandes o suficiente para compensar a quebra. Consulte https://github.com/JuliaLang/julia/issues/3701.
  • [] As reduções devem diminuir as dimensões? # 16606

Nova funcionalidade

  • [x] Permite a expressão de varargs de comprimento definido (# 11242). Isso nos permite aproveitar ao máximo o # 10525.
  • [x] Descarte a redução especial de Ac_mul_Bt, use dispatch nos envoltórios de transposição preguiçosos. (# 5332, # 25217)
  • [x] Dimensões indexadas por matrizes multidimensionais adicionam dimensões (estilo APL completo: a dimensionalidade do resultado é a soma das dimensionalidades dos índices). (# 15431)
  • [x] Permitir qualquer tipo de índice na indexação não escalar (# 12567). Aperte a indexação escalar para índices <: Integer e amplie a indexação não escalar para <: Union{Number, AbstractArray, Colon} (https://github.com/JuliaLang/julia/pull/12567#issuecomment-170982983). Conversão mais sistemática de índices, de modo que qualquer tipo de índice possa ser convertido em Int ou AbstractArray : # 19730
  • [] Criação mais fácil de arrays imutáveis ​​com tuplas e # 12113.

Outras possibilidades especulativas

  • [] Um tipo de buffer mutável de tamanho fixo, que permitiria uma definição nativa de Julia Array (# 12447); este tipo também pode ser usado para buffers de E / S e armazenamento de strings.
  • [x] Baseie IndexSet IntSet em BitArray ou talvez qualquer AbstractArray{Bool} . (# 20456)
  • [x] Retrabalhe a indexação não escalar para evitar chamar find em matrizes lógicas e simplesmente envolva-a com IndexSet LogicalIndex vez disso? (# 19730)
  • [] Indexação negada com complemento IndexSet (https://github.com/JuliaLang/julia/issues/1032) ou tipo especial Not ? (Talvez em um pacote: https://github.com/mbauman/InvertedIndices.jl)
  • [x] Rejeite a linearização das dimensões finais quando mais de um índice é fornecido (indexação linear parcial). (# 20079)
  • [x] Permitir apenas a indexação em matrizes N-dimensionais com índices 1 ou N, descontinuando a indexação "parcial" e as dimensões de singleton à direita (https://github.com/JuliaLang/julia/issues/5396 e # 14770).
  • [] Encontre uma sintaxe alternativa para matrizes digitadas - indexar em um tipo ( T[...] ) é um trocadilho ruim. Essa sintaxe é especialmente ruim, pois algumas variações são analisadas como indexação em um tipo, enquanto outras são analisadas como formas especiais (hvcat digitado, compreensões digitadas)
  • [x] Altere o hashing para a codificação run-length do diff de arrays, o que permitiria que intervalos inteiros e arrays fossem iguais novamente. (https://github.com/JuliaLang/julia/issues/12226#issuecomment-122952826, # 16401)
  • [x] Mova matrizes esparsas da base para um pacote padrão. (# 25249)
  • [x] Permitir índices não tradicionais (# 16260)
  • [x] @sub @view macro (# 16564)
arrays linear algebra

Comentários muito úteis

A base e ArrayViews podem usar view . Afinal, ImageView também usa view :)

Todos 276 comentários

Parece uma ótima lista, Matt, obrigado. Estou com um pouco de medo do
precipitação, mas será um grande avanço para a linguagem.

Na terça-feira, 15 de setembro de 2015, Matt Bauman [email protected]
escrevi:

Este problema substitui o 0,4 trabalho em direção ao nirvana da matriz (# 7941
https://github.com/JuliaLang/julia/issues/7941), e rastreará o
questões que pretendemos concluir durante 0,5. Esta é uma questão abrangente e
rastrear tarefas específicas em outras questões. Sinta-se à vontade para adicionar coisas que
Eu perdi (pelo menos durante a primeira metade de 0,5).

_Tecnologias subjacentes necessárias_

  • Verificação e remoção de limites nativos de Julia (# 7799
    https://github.com/JuliaLang/julia/issues/7799). Várias tentativas têm
    feito isso, mas acredito que o plano de ação atual é fazer
    @inbounds elide blocos de código ocultos em uma macro @boundscheck ,
    propagando para baixo apenas um nível de inlining (# 7799 (comentário)
    https://github.com/JuliaLang/julia/issues/7799#issuecomment-117362695).
    Este é um requisito importante para as etapas subsequentes.
  • ReshapedArrays (# 10507
    https://github.com/JuliaLang/julia/pull/10507). Requer melhor
    desempenho:
    https://groups.google.com/d/msg/julia-dev/7M5qzmXIChM/kOTlGSIvAwAJ

_Breaking comportamento changes_

  • Dimensões de queda indexadas por um escalar (# 4774 (comentário)
    https://github.com/JuliaLang/julia/issues/4774#issuecomment-81228816)
  • Retorne as fatias como visualizações. Uma primeira tentativa foi em # 9150
    https://github.com/JuliaLang/julia/pull/9150. Atenção especial pode
    ser necessário para BitArrays.
  • Mude a chave para a suspensão de uso da concatenação (# 8599
    https://github.com/JuliaLang/julia/pull/8599)

_Nova funcionalidade_

  • Permitir a expressão de varargs de comprimento definido (# 11242
    https://github.com/JuliaLang/julia/pull/11242). Isso nos permite
    aproveite ao máximo o # 10525
    https://github.com/JuliaLang/julia/pull/10525.
  • Transpor retorna um covetor ou tipo de transposição (# 4774 (comentário)
    https://github.com/JuliaLang/julia/issues/4774#issuecomment-81228816)
  • Vala a redução especial de Ac_mul_Bt, use despacho ao invés.
  • Dimensões indexadas por matrizes multidimensionais adicionam dimensões (completo
    Estilo APL: a dimensionalidade do resultado é a soma do
    dimensionalidades dos índices)
  • Permitir qualquer tipo de índice na indexação não escalar (# 12567
    https://github.com/JuliaLang/julia/pull/12567)
  • Criação mais fácil de matrizes imutáveis ​​com tuplas e # 12113
    https://github.com/JuliaLang/julia/pull/12113.

_Outras possibilidades especulativas_

  • Um tipo de buffer mutável de tamanho fixo, que permitiria um
    Definição de array nativa de Julia (# 12447
    https://github.com/JuliaLang/julia/issues/12447)
  • Base IndexSet em BitArray ou talvez qualquer AbstractArray {Bool}.
  • Retrabalhe a indexação não escalar para evitar a chamada de find em matrizes lógicas
    e simplesmente envolvê-lo com um IndexSet?
  • Indexação negada com complemento IndexSet? (Talvez em um pacote)

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/JuliaLang/julia/issues/13157.

Sim, ótima lista! Vamos arregaçar as mangas!

Há alguma peça disso que não foi reclamada? Eu gostaria de ajudar, mas eu
não quero pisar no pé de ninguém.

Na terça-feira, 15 de setembro de 2015, Jeff Bezanson [email protected]
escrevi:

Sim, ótima lista! Vamos arregaçar as mangas!

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/JuliaLang/julia/issues/13157#issuecomment -140586293.

É uma lista fenomenal. Na verdade, existem apenas algumas mudanças que são muito importantes, o que é bom, mas essas são significativas o suficiente.

Definitivamente, há trabalho mais do que suficiente para todos! Eu não acho que nenhuma dessas tarefas seja reivindicada.

Coisas como descartar dimensões escalares são mudanças bastante simples, mas darão muito trabalho para encontrar e corrigir bugs ... e essa parte é fácil de colaborar. O mesmo vale para as visualizações (se você ignorar os problemas de desempenho com ReshapedArrays e inbounds). Qualquer pessoa é bem-vinda!

Visualizações é difícil, você tem que passar por bootstrap sem: hocho: você mesmo nos: olhos :

Tendo acabado de fazer um monte de trabalho para obter alterações de string através do bootstrap, estou inclinado a acreditar nisso.

Obrigado por fazer isso @mbauman , muito para digerir

Eu adicionei "Remover comportamento no-op padrão para (c) transpor" como um item. Espero muitas reclamações, mas como discutimos antes, é simplesmente errado assumir que <:Any é um escalar e o erro lógico surge toda vez que alguém tenta agrupar e / ou implementar tipos personalizados de matriz / matriz . cc @jakebolewski @andreasnoack

Acho que precisamos pensar nas opções para isso com cuidado. É muito idiomático escrever A' para transpor uma matriz não complexa.

Não é possível que o invólucro de transposição (c) resolva esse problema? Há muito trabalho de design que precisa ser feito, mas:

transpose(A::AbstractVectorOrMatrix) = TransposeType(A) # Will call `transpose` upon indexing, too
transpose(::AbstractArray) = error("cannot take transpose of 3+ dim array") # yeah, error methods aren't the best…
transpose(x) = x

related: Acho que alguns dos problemas de digitação em linalg (e comportamento de transposição) vêm do fato de que representamos um operador linear bloqueado como uma matriz de matrizes. Podemos querer mudar para um novo tipo que conheça os vários tamanhos dentro dele, lembro-me de discutir isso com @andreasnoack. 0,5 pode ser um momento para pelo menos pensar sobre isso.

Não é possível que o invólucro de transposição (c) resolva esse problema?

Talvez; teríamos que pensar sobre isso.

O problema de bloqueio da última vez é que a transposição deve ser recursiva para lidar com casos como Matrix{Matrix} (Exemplo: A = [rand(1:5, 2, 2) for i=1:2, j=1:2]; A' ) corretamente, mas as pessoas querem escrever A' em arrays 2D em não -tipos numéricos (por exemplo, imagens, as Matrix{<:Colorant} ) e esperar que a transposição não se aplique aos elementos escalares. O método autônomo transpose(x::Any) existe para lidar com esses casos. No entanto, esta definição entra em conflito com objetos do tipo matriz, que têm a semântica algébrica de matrizes, mas não são armazenados internamente em qualquer forma semelhante a matriz e, portanto, por # 987 não deve ser um AbstractArray ( QRCompactWYQ é o garoto-propaganda, mas temos muitos desses exemplos). Se você introduzir um novo tipo de matriz, terá que definir explicitamente (c)transpose caso contrário, obterá o fallback autônomo que é uma fonte de muitos bugs.

Para ser claro, o comportamento que quebraríamos explicitamente é a afirmação (que você pode encontrar na ajuda de permutedims ) que

Transpor é equivalente a permutados (A, [2,1]).

Essa equivalência não faz sentido para tipos que não são AbstractArray s e que são AbstractArray s de não escalares, e realmente temos tipos semelhantes a matrizes que precisam desse sentido mais abstrato de transposição.

Acho que supondo que uma Matrix {Matrix} será automaticamente recursiva
transpor os elementos é ruim e perigoso. Eu prefiro ver um tipo especial
BlockMatrix {Matrix} que faz o que você procura.

Na quarta-feira, 16 de setembro de 2015 às 10h30, Jiahao Chen notificaçõ[email protected]
escrevi:

Não é possível que o invólucro de transposição (c) resolva esse problema?

Talvez; teríamos que pensar sobre isso.

O problema de bloqueio da última vez é que a transposição deve ser recursiva para
lidar com casos como Matrix {Matrix} (Exemplo: A = [rand (1: 5, 2, 2) para
i = 1: 2, j = 1: 2]; A ') corretamente. No entanto, as pessoas querem escrever A 'em 2D
matrizes em tipos não numéricos (por exemplo, imagens, como Matrix {<: Corante}) e
espere que a transposição não se aplique aos elementos escalares. O no-op
O método transpose (x :: Any) existe para lidar com esses casos. No entanto, este
a definição conflita com objetos semelhantes a matrizes, que têm o algébrico
semântica de matrizes, mas não são armazenadas internamente em qualquer forma semelhante a matriz,
e, portanto, por # 987 https://github.com/JuliaLang/julia/issues/987 deve
não ser um AbstractArray (QRCompactWYQ é o garoto-propaganda, mas temos
muitos desses exemplos). Se você introduzir um novo tipo de matriz, você deve
definir explicitamente (c) transpor, caso contrário, você obterá o fallback no-op que
é uma fonte de muitos bugs.

Para ser claro, o comportamento que quebraríamos explicitamente é que

Transpor é equivalente a permutados (A, [2,1]).

agora seria falso. Esta equivalência não faz sentido para tipos que não são
AbstractArrays, e realmente temos tipos semelhantes a matrizes que precisam disso
sentido mais abstrato de transpor.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/JuliaLang/julia/issues/13157#issuecomment -140759002.

@tbreloff é exatamente esse o meu ponto. A maioria das pessoas acha estranho que a transposição seja recursiva, mas _deve_ ser uma transposta matematicamente correta, e essa inquietação expõe casos extremos em que a transposição não é simplesmente permutedims(A, [2,1]) . (Embora seja verdade que Matrix{Matrix}} não é realmente um tipo de matriz bloqueada, porque não há absolutamente nenhuma garantia de que as matrizes internas tenham dimensões consistentes com qualquer particionamento de uma matriz maior.)

Ah, sim, esqueci-me de todos os objetos do tipo Tensor que não são AbstractArrays. Não importa o que aconteça, ou os autores de objetos semelhantes a tensores precisarão comunicar a Julia de alguma forma que eles não são escalares (às vezes sendo um AbstractArray funciona, mas nem sempre) ou os autores de objetos semelhantes a escalares precisarão fazer o inverso (às vezes, ser um número funciona, mas nem sempre), ou ambos. Este mesmo tipo de questão escalar ou não surge em todos os lugares ... por exemplo, indexação: https://github.com/JuliaLang/julia/pull/12567.

No momento, exigimos uma mistura de especializações de método para os tensores com alguns fallbacks semelhantes a escalares. Isso significa que alguns são esquecidos e terminamos com métodos escalares sendo chamados, retornando o resultado errado.

Como não podemos comunicar isso por meio de supertipos (https://github.com/JuliaLang/julia/issues/987#issuecomment-31531602), acho que deve ser por meio de uma documentação melhor dos métodos necessários ou por ter esses métodos explicitamente codificados em um sistema baseado em traços. E se removermos todos os fallbacks (o que garante o comportamento correto ao custo de métodos ausentes para todos), acho que precisamos tornar isso o mais simples possível com as características.

+1

Eu realmente acho que devemos começar a enumerar características para AbstractArray. A indexação linear parece ter sido um exemplo incrível do poder de algumas decisões de design cuidadosas envolvendo características.

Uma solução possível seria manter AbstractArray <: Any e introduzir AbstractNonArrayTensor <: Any ao lado dele. Todo o resto seria considerado "escalar" no que diz respeito à indexação e álgebra linear.

Observe que isso é diferente e muito mais bem definido do que uma distinção Atom vs. Collection (https://github.com/JuliaLang/julia/pull/7244#issuecomment-46333261) ; A[:] = (1,2,3) e A[:] = "123" se comportam de maneira muito diferente de A[:] = 1:3 para A = Array(Any, 3) , como deveriam.

Eu realmente acho que devemos começar a enumerar características para AbstractArray.

@johnmyleswhite Por acaso, você quis dizer que a linguagem suporta "traços"? Isso é uma coisa que eu realmente queria ver na linguagem desde JuliaCon.

Sim, eu quis dizer traços suportados pela linguagem.

Você tem alguma ideia / sugestão sobre como os traços podem ser adicionados a Julia, sintaticamente? Eles seriam muito úteis para arrays, strings, codificações, no mínimo.

Prefiro deixar essas decisões para Jeff do que especular.

Visto que se trata de uma questão abrangente, seria bom discutir itens específicos em suas próprias questões.

No entanto, acho que ter traços na linguagem pode mudar substancialmente o design de Arrays, e é por isso que a discussão sobre traços, pelo menos na área de como eles poderiam ser usados ​​para melhores abstrações de Array, seria útil.

Por favor, mova a discussão sobre características para o nº 5, e o (c) problema de transposição para o nº 13171.

Não acho que a sintaxe dos traços precise ser descoberta antes de descobrir quais traços são importantes para os arrays. Na verdade, ter mais exemplos de características de que realmente precisamos é excelente para ajudar a projetar o recurso de linguagem.

Ok, bom ponto, contanto que as características sejam consideradas parte do design.

Superior e inferior para matrizes triangulares? Esses parecem bons candidatos para serem usados ​​como características para mim.

Adicionado item especulativo para substituir a indexação em um tipo como sintaxe para matrizes digitadas.

Eu preferiria não mover matrizes esparsas da base em 0,5. Acho que devemos ter como objetivo ter SparseVectors na Base para 0,5 e ter certeza de que há uma implementação padrão de alta qualidade.

À medida que removemos algumas das outras funcionalidades da base, podemos remover os vários solucionadores esparsos primeiro. Se estruturas de dados esparsas alternativas estão se moldando bem naquele ponto em JuliaSparse , podemos eventualmente mover SparseMatrixCSC para lá também.

Que tal uma pequena tarefa de limpeza aqui também - mover todo o código do array para seu próprio subdiretório e módulo no Base?

Há um monte de coisas marcadas como marco 0,5. Eu gostaria de sugerir que os problemas referenciados aqui deveriam ser marcados como 0.5 blocker , e o resto que é 0,5 é essencialmente 0.5 nice to have . Poderíamos então ter um período de 1-2 semanas para acumular uma lista completa de 0.5 blocker problemas e liberar 0,5 assim que os bloqueadores terminassem. Qualquer bom ter problemas que sejam corrigidos no mesmo período de tempo chegará a 0,5, mas o lançamento não bloqueará em nenhum recurso legal que não esteja pronto.

Eu gostaria de adicionar algumas possibilidades adicionais. Estou extremamente animado com o Arraypocolypse!

Primeiro, acho que faria sentido ter operadores para soma tensorial e produto tensorial. Eles podem ser ++ e ** , ou podem ser símbolos Unicode \otimes e \oplus . O produto tensor pode substituir kron() . A soma do tensor fornece a concatenação de vetores e, à parte, é uma ótima descrição da concatenação de strings (tem havido muitas, muitas conversas sobre isso, e os principais desenvolvedores de Julia parecem preocupados que + corresponda ao monóide incorreto aqui - e então vá para * porque possivelmente parece menos comutativo - enquanto eu argumentaria fortemente que a operação correta é a soma tensorial \oplus que também não é comutativa). Eles também podem ser usados ​​para matrizes / tensores de ordem superior (veja abaixo) e talvez (estou especulando aqui) operações de álgebra relacional em DataFrames ?.

Em segundo lugar, ocorreu-me que Julia pode ser muito, muito boa em álgebra multilinear com uma sintaxe extremamente boa. Tenho brincado com um pacote de atribuição de nomes a cada índice como um array como parte do tipo usando um tipo Tuple{...} . Um array é transformado em um tensor indexando-o como A[Tuple{:index1,:index2}] e se Julia 0.5 usa {} para Tuple{} então é muito melhorado para A[{:index1,:index2}] e obtemos o seguinte maravilha:

  1. Permutação: A[{1,2,3}] = B[{3,2,1}]
  2. Uma nova maneira de fazer multiplicação de matrizes e / ou vetores, A[{:a}] = B[{:b,:a}] * C[{:b}] (contratos acima do índice :b , portanto A = B' * C ).
  3. Da mesma forma, contração tensorial arbitrária e de ordem superior (também conhecida como einsum): por exemplo, A[{:a,:b}] = B[{:a,:c,:d}] * C[{:c,:e}] * D[{:d,:f}] * E[{:e,:f:,:b}] . Os colchetes podem ser usados ​​para escolher a ordem de contração mais rápida.
  4. Soma tensorial e produto tensorial também fazem sentido aqui - então as pessoas podem pegar produtos externos de matrizes de ordem superior e colocar os índices na ordem desejada facilmente, por exemplo.

A sintaxe de permutação pode resolver outro problema - transpose é uma coisa de álgebra linear, mas como é tão simples digitar ' ela é usada em vez de permutedims para, por exemplo, refletir um imagem (uma matriz 2D de valores de pixel). Esses diferentes casos de uso parecem estar causando um problema se transpose é chamado recursivamente, mas eu diria que esta sintaxe de "tensor" para permutedims é clara o suficiente para uso em programação geral e dados / manipulação de array.

Não tenho certeza se as pessoas gostariam dessa abordagem de álgebra multilinear (ela se baseia um pouco nas funções geradas), mas gostaria de receber comentários!

Você poderia elaborar um pouco sobre soma de tensores e concatenação vetorial (e string)? Acho que seria ótimo ter um operador de concatenação "genérico", do qual os matemáticos também gostam.

A soma tensorial (ou soma direta de v = [v_1, v_2, v_3] ew = [w_1, w_2, w_3] é escrita v \ oplus w = [v_1, v_2, v_3, w_1, w_2, w_3]. (Imagine Estou usando LaTeX - \ oplus e \ otimes funciona no Julia REPL já e parece com + (ou vezes) desenhado com um círculo ao redor deles).

O tensor / soma direta é realmente a forma matemática de concatenar vetores (como vcat ) e eu também sugeriria strings. Para matrizes, A \ oplus B é a maior matriz de bloco diagonal [A 0; 0 B], (e é um bom candidato a ser representado pelos BlockedArray s mencionados mais acima). Para vetores transpostos (linha), a soma do tensor é novamente apenas a concatenação dada por hcat , então isso teria que ser tratado de forma diferente dependendo se você tivesse um vetor linha ou uma matriz 1 por n (que esperançosamente deve estar OK em Julia 0,5).

Portanto, se pensarmos em strings como vetores de caracteres, então somas diretas / tensoriais são o caminho a percorrer se os desenvolvedores do núcleo usarem palavras como "monóide" como um princípio de sintaxe orientador. A manipulação de strings pode ser considerada uma categoria monoidal onde setas paralelas são concatenações? Certamente, a álgebra multilinear sobre números reais (ou complexos) é dada por categorias bi-monoidais simétricas (punhal) em que tensor / soma direta (\ oplus) e tensor / produto externo / direto (\ otimes) são os dois "monóides".

@andyferris abra um novo problema ou PR para concatenação de tensores. Esse problema é longo e complicado o suficiente, sem incluir mais recursos.

A relação entre matrizes multidimensionais e álgebra multilinear foi discutida na literatura APL da década de 1970, notadamente por Trenchard More e outros que trabalharam em APL2.

Sim, isso merece uma nova questão, isso é um descarrilamento do trabalho principal aqui.

( @andyferris , consulte também https://github.com/Jutho/TensorOperations.jl, https://github.com/mbauman/AxisArrays.jl, https://github.com/timholy/AxisAlgorithms.jl.)

Eu gostaria de compartilhar com vocês um código que fiz um tempo atrás que eu acho que trata de um ponto no espaço de design para operações de array que refletem localidade e suportam esparsos como primeira classe. Suspeito que a portabilidade exigiria características, mas você pode achar algumas das ideias úteis https://github.com/wellposed/numerical/tree/master/src/Numerical/Array/Layout os módulos de layout especificamente

Obrigado @timholy por esses links; Eu não estava ciente dos dois últimos.

Que tal uma pequena tarefa de limpeza aqui também - mover todo o código do array para seu próprio subdiretório e módulo no Base?

@ViralBShah Parece uma ideia muito boa - mova também os testes de unidade de array para que haja uma boa correspondência 1-1 entre a implementação e os arquivos de teste.

Devemos dar o pontapé inicial ativando o interruptor da suspensão de concatenação (# 8599)?

+1

@mbauman tem um branch onde a maioria das mudanças de transposição foram implementadas. @jiahao vai me corrigir se eu estiver errado aqui, mas o novo comportamento de transposição pode ser separado em uma transposta de matriz e uma transposta de vetor, onde a primeira é menos controversa, para fazer algum progresso aqui e iniciar o processo de ajuste de código, eu estou propondo que comecemos apenas com o tipo de transposição de matriz e adiemos um pouco a transposição vetorial. @mbauman Você acha que isso seria viável e você tem tempo para fazer essa separação se decidirmos que é uma maneira útil de proceder?

Implementar os próprios tipos de transposição é bastante simples - apenas algumas dezenas de LOC. E os próprios tipos de transposição de matriz e vetor não são intrinsecamente acoplados (exceto que criar visualizações de transposição de matriz sem alterar vetores seria ainda mais estranho do que o status quo).

A parte mais difícil sobre a direção que tomei em mb / transpose é que eu _também_ tentei remover o rebaixamento especial para Ax_mul_Bx. Essa parte é uma tonelada de trabalho, com cada operador contendo centenas de permutações de tipos de transposição, tipos de matrizes especiais e tipos de elementos compatíveis com BLAS. Não consegui chegar ao fim do túnel por mul e nem sequer comecei a abordar os outros operadores naquele ramo. Remover o rebaixamento especial também é o ponto em que precisamos tomar uma decisão sobre a transposição do vetor.

Dito isso, não há necessidade de que essas duas alterações sejam acopladas uma à outra. O tipo de transposição _permite_ a remoção dos degraus especiais de abaixamento Ax_op_Bx, e até simplifica parte dessa bagunça, mas remover Ax_op_Bx não é necessário para introduzir vistas de transposição. Acho que o caminho a seguir aqui é dar passos menores. Embora tenha sido muito informativo tentar lidar com os dois juntos (funciona! E pode tornar as coisas mais simples!), É demais para morder de uma vez.

Acho que deveria ser possível adicionar fallbacks simples, por exemplo, *(A::Transpose, B::AbstractArray) = At_mul_B(untranspose(A),B) , a fim de obter visualizações de transposição funcionando com o sistema atual sem tocar em centenas de métodos. Meu tempo é muito limitado atualmente, então não posso prometer que vou encontrar tempo para fazer isso sozinho. Mas acho que isso poderia ser feito em um PR relativamente pequeno. Qualquer pessoa é bem-vinda para pegar a tocha aqui.

Não poderíamos começar definindo os métodos Ax_mul_Bx em termos de
transpor tipos?

Na quinta-feira, 5 de novembro de 2015, Matt Bauman [email protected] escreveu:

Implementando os próprios tipos de transposição
https://github.com/JuliaLang/julia/blob/335200c142e368cad9aba885df5539d2755195ad/base/transpose.jl
é bastante simples - apenas algumas dezenas de LOC.

A parte mais difícil sobre a direção que tomei em mb / transpor é
que _também_ tentei remover o rebaixamento especial para Ax_mul_Bx. que
parte é um _ton_ de trabalho, com cada operador contendo centenas de
permutações de tipos de transposição, tipos de matriz especial e compatível com BLAS
tipos de elemento. Eu não consegui chegar ao fim do túnel para mul,
e eu nem comecei a lidar com os outros operadores naquele ramo.
Remover o rebaixamento especial também é o ponto em que precisamos fazer um
decisão sobre a transposição do vetor.

Dito isso, não há necessidade de que essas duas alterações sejam acopladas a cada
de outros. O tipo de transposição _permite_ para a remoção do Ax_op_Bx
etapas especiais de redução e até mesmo simplifica parte dessa bagunça, mas removendo
Ax_op_Bx não é necessário para introduzir vistas de transposição. eu acho que
que o caminho a seguir aqui é dar passos menores. Enquanto era muito
informativo para tentar lidar com os dois juntos (funciona! e pode tornar as coisas
simper!), é demais para morder de uma vez.

Acho que deveria ser possível adicionar fallbacks simples, por exemplo, * (A :: Transpose,
B :: AbstractArray) = At_mul_B (não transpor (A), B). Meu tempo é muito limitado
atualmente, não posso prometer que encontrarei tempo para fazer isso sozinha. Mas eu
acho que isso poderia ser feito em um PR relativamente pequeno. Qualquer um é bem vindo a
pegue a tocha aqui.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/JuliaLang/julia/issues/13157#issuecomment -154101995.

Como estão as coisas com Arraypocalypse? O plano original é ter um lançamento 0.5 até o final de janeiro com todas as coisas acima ainda nos livros?

Muitas outras coisas estão acontecendo no 0.5, mas AFAICT pouco está acontecendo nos arrays desta lista específica. Alguns de nós que contribuíram muito no passado para os arrays de Julia estão ocupados (em parte, colhendo os benefícios de nosso trabalho anterior: wink :). Para que isso aconteça, é possível que outros tenham que assumir o papel (dica). Mas o recém-postado # 14474 é talvez a coisa mais empolgante nesta frente que surgiu em muito tempo - em muitos aspectos, é O gargalo crucial.

Se outras coisas estiverem prontas, podemos atrasar os arrays para uma versão posterior.

Sim, muitos outros trabalhos interessantes em andamento, mas esta lista é um pouco lenta com o progresso. Minha ideia é que revisemos isso quando o depurador estiver em boa forma, talvez por volta do final de janeiro. Temos um novo LLVM, thread safety, fast closures, Juno-Atom e muitos outros itens chegando. Espero que possamos também obter as visualizações de array, se não esta lista inteira.

Aqui está um voto para atrasar 0,5 até que todas as alterações importantes em torno dos arrays sejam implementadas. Meu entendimento é que o fatiamento de estilo APL (que quebra a compatibilidade com versões anteriores) já está no mestre, portanto, os autores de pacotes terão que atualizar uma grande quantidade de código para 0,5. Se a outra mudança principal (retornando visualizações em vez de cópias de [] ) viesse apenas em algum lançamento pós-0,5 julia, todos teriam que revisar / atualizar todas as mesmas linhas de código novamente, o que seria realmente um MUITO trabalho extra que poderia ser totalmente evitado se essas duas mudanças importantes fossem entregues em uma versão.

Além disso, da perspectiva do usuário, seria ótimo se as principais mudanças na linguagem ocorressem mais cedo ou mais tarde ... Do meu ponto de vista, seria muito preferível ter coisas como um depurador, compatibilidade ARM, fechamentos mais rápidos , tópicos, etc., todos vêm depois que as alterações de linguagem são feitas. Todo o código que eu escrevo hoje em dia terá que ser atualizado assim que essas mudanças importantes chegarem ao mestre, enquanto todas as outras coisas boas mencionadas (provavelmente) não afetariam nenhum código que as pessoas estão escrevendo agora ... O "iminente" coisas como Arraypocalypse são a principal razão pela qual muitas pessoas que conheço estão ficando longe de Julia por enquanto ...

É claro que não tenho ideia se sobrou alguém que tem capacidade para trabalhar nisso, parece que algumas das pessoas que fizeram isso antes seguiram em frente ... Mas talvez Julia Computing possa dedicar alguns recursos para terminar este trabalho?

Em qualquer caso, alguém poderia atualizar a data de vencimento na etapa 0,5? Pelo menos se minha interpretação do comentário de @ViralBShah acima estiver correta, não haverá uma versão de 0,5 julia em 31/01, que é a informação atual associada ao marco 0,5, certo?

Este não é realmente um problema de dedicação de recursos. Há um bom número de questões em aberto e muitas pessoas ainda têm dúvidas sobre as visualizações de array. Teremos que fazer e experimentar e ver como é. Como mencionado acima, o PR de @blakejohnson é o primeiro passo e vamos fazer uma fusão. Certamente não há planos para lançar 0,5 em 31/01. Estou removendo essa data.

@davidanthoff : Devolver visualizações em vez de cópias é realmente um sub para obter uma visão em um array.

Embora eu concorde que é sempre melhor fazer grandes mudanças antecipadamente, há coisas que simplesmente levam tempo e, na minha perspectiva, a equipe de lançamento da Julia fez um trabalho incrível ao julgar qual recurso está pronto para inclusão no lançamento e qual não está. 0.3 e 0.4 são bastante estáveis ​​no meu ponto de vista.

Eu estava com a impressão de que a ideia era um lançamento rápido de 0,5 _ precisamente_ por causa das mudanças de interrupção do Array (estas deveriam estar em um único lançamento) que viriam em breve. Se vier nos próximos meses, 0,5 deve ser adiado?

@ViralBShah O marco para este problema ainda é 0,5, não é? Não pode ser 0.4.x?

Há algum tempo, adicionei este benchmark para rastrear como o desempenho do nosso array era em relação ao do Numpy (que é mais rápido do que estamos neste benchmark). O benchmark é uma fatoração LU com pivotamento completo e aloca deliberadamente muitos arrays temporários trabalhando em vetores em vez de percorrer os elementos da matriz. A versão Julia tem uma versão _copy_ e uma _view_ do benchmark, então isso também dá uma indicação de quanta velocidade obteríamos de uma mudança para _views por padrão_. Na minha máquina, os resultados são

➜  arrayalloc git:(master) ✗ make
python lucompletepiv.py
size  100 matrix factorized in  0.010 seconds
size  250 matrix factorized in  0.047 seconds
size  500 matrix factorized in  0.246 seconds
size 1000 matrix factorized in  2.330 seconds

Julia version with copy slices
size  100 matrix factorized in  0.016 seconds
size  250 matrix factorized in  0.093 seconds
size  500 matrix factorized in  0.517 seconds
size 1000 matrix factorized in  4.126 seconds

Julia version with view slices
size  100 matrix factorized in  0.004 seconds
size  250 matrix factorized in  0.078 seconds
size  500 matrix factorized in  0.453 seconds
size 1000 matrix factorized in  3.555 seconds

portanto, o uso de visualizações melhora um pouco os tempos, mas não o suficiente para corresponder ao Numpy.

É muito fácil mudar um algoritmo baseado em _copy_ para um algoritmo baseado em _view_ e essa mudança pode ser ainda mais fácil se introduzirmos uma sintaxe especial para visualizações. Portanto, pode não valer a pena alterar a indexação da matriz para _visualizações por padrão_.

Este é um ótimo ponto de partida. A próxima etapa óbvia parece ser descobrir o que precisamos fazer para tornar os subarrays mais rápidos. Talvez @timholy ou @mbauman já saibam.

Precisamos ter o compilador elide a construção de subarray completamente. Acho que o código do subarray já é adequado para isso, então é provavelmente "apenas" um trabalho árduo no codegen que é necessário.

@andreasnoack a diferença com matrizes pequenas faz com que isso valha totalmente a pena para minhas cargas de trabalho.

Concordo, e observe que somos mais rápidos do que python para matrizes pequenas.

Hmm, eu me pergunto se há algo mais acontecendo --- isso pode não ser uma referência para subarrays. O que acontece se você substituir https://github.com/JuliaLang/julia/blob/6d7a50b880fe2189b1efa34eb47d4dfeb181b674/test/perf/arrayalloc/lucompletepiv.jl#L39 por uma chamada para BLAS.syrk2! ?

@blakejohnson Meu ponto principal é que você já está livre para usar visualizações e podemos tornar isso ainda mais simples com a sintaxe.

@timholy Seria mais rápido, mas o ponto do benchmark é que ele aloca temporários. A implementação Numpy não usa syrk2 .

@ViralBShah Eu interpretei o comentário de @timholy como um pedido de mais recursos (ou seja, mão de obra) para este problema porque os desenvolvedores que pretendiam fazer o trabalho nisso seguiram em frente. Mas talvez eu tenha entendido mal, ou talvez novas pessoas já tenham se oferecido para assumir.

@tknopp Não é a falta de visualizações de [] que está impedindo as pessoas de usarem o julia, é a expectativa de grandes mudanças de última hora em 1-2 lançamentos. Pelo menos estou cercado de pessoas para quem o julia seria perfeito, mas que simplesmente não estão dispostas a usar uma linguagem em que possam ter que atualizar seu código para que ainda funcione corretamente no próximo lançamento do julia.

Não estou dizendo que qualquer decisão de design deve ser precipitada por causa desse grupo, prefiro ver as coisas sendo resolvidas para sempre e levar um pouco mais de tempo. Principalmente, eu queria apenas dar uma olhada neste tópico e ver se alguém ainda está trabalhando nas coisas aqui :)

@andreasnoack Além disso, sua versão de visualização não tem, por exemplo, @blakejohnson trabalho de remoção de limites incorporado, certo? Minha sensação era que um dos principais pontos do problema geral aqui era que existem problemas de desempenho conhecidos que precisam ser resolvidos para tornar as visualizações mais rápidas, mas que não foram resolvidos. Se as coisas ainda não forem mais rápidas depois de feitas, eu entenderia esse argumento para ficar apenas com as cópias por padrão. Mas antes que essa chamada seja feita, o trabalho de desempenho discutido no início desta edição deve ser concluído, certo?

simplesmente não estão dispostos a usar uma linguagem em que possam ter que atualizar seu código para que ainda funcione corretamente no próximo lançamento do julia.

Isso é porque eles não usariam nenhuma linguagem anterior à 1.0, ou eles ouviram especificamente que em breve haverá alterações de array interrompidas (após o que eles usariam julia)?

@hayd Não tenho 100% de certeza, mas as coisas de array são especialmente assustadoras para as pessoas porque afetam muito código (embora, eu acho que a indexação de estilo APL que já está no master ainda mais do que a coisa de visualizar / copiar )

O NumPy e a Julia estão usando o mesmo BLAS aqui? Posso obter um aumento substancial (> 1/3) na caixa da matriz de tamanho 1000 chamando Base.disable_threaded_libs() .

@davidanthoff :
Se isso for uma preocupação, provavelmente Julia é jovem demais para essas pessoas. Do meu ponto de vista, o número de mudanças na linguagem central nos últimos 3 anos não foi tão drástico.
É claro que existem linguagens bastante estáveis, mas se eu pensar em C ++ 11 ou Python3, existem até mesmo grandes mudanças nas linguagens maduras.

@davidanthoff , sim, é uma chamada para mais pessoas contribuírem com este esforço --- contribuidores anteriores provavelmente não "terminaram" (eu não estou), mas a lista é longa e os indivíduos têm várias prioridades.

Um pouco de material de array não é tão difícil de trabalhar, porque é uma parte focada de Julia. Ou talvez eu seja apenas tendencioso por ter colaborado nisso com um monte de pessoas superinteligentes: sorria :

Especificamente em relação à remoção da verificação de limites: atualmente, as visualizações são bastante rápidas, mas não são seguras porque não há verificação de limites. A fim de torná-los geralmente utilizáveis, precisamos _add_ verificação de limites, mas até que o trabalho de @blakejohnson seja integrado , não podemos fazer isso sem destruir o desempenho.

@andreasnoack , você pode abrir uma edição separada sobre seu benchmark lu? Há mais a ser discutido, mas acho que está distraindo das questões maiores aqui.

Que tal permitir campos em tipos abstratos (https://github.com/JuliaLang/julia/issues/4935) qualquer coisa nova nessa direção?

Que tal roubar a sintaxe {} dos tipos de tupla planejada e fazer A{:, 1} criar uma visão enquanto A[:, 1] ainda cria uma cópia?

@JeffBezanson sugeriu A@[:,1] para visualizações e A[:,1] para cópias também.

Pergunta ingênua por que A [:, 1] não é uma visualização por padrão e se você quiser uma cópia, chame 'cópia' na visualização?

Para tornar isso conciso em um uso relacionado, sobrecarregamos '*', por exemplo * (A [:, 1]) - se unário * estivesse no analisador, ele se tornaria o * A [:, 1] conciso. Não estou defendendo fortemente o uso de *, mas mapeia em minha mente para o uso de C.

Eu ainda sou a favor. @JeffBezanson tinha algumas reservas sobre isso - vou deixá-lo explicar.

Existem dois problemas principais com visualizações por padrão IMO

1) é uma alteração importante, um dos piores tipos, uma vez que introduz bugs de aliasing (basicamente, exceto em descontinuar a indexação de intervalo para uma versão completa, você teria que auditar cada bit de código que a usa)

2) imutáveis ​​contendo referências julia são bastante ineficientes por enquanto, então se as pessoas começarem a usar visualizações por conveniência (ou seja, para evitar cálculos manuais de índices), provavelmente verão regressões de desempenho

Admito que (2) é algo que devemos trabalhar de qualquer maneira, mas pode ser importante fazê-lo antes do uso generalizado de visualizações. Não vejo como (1) poderia ser resolvido sem ter uma sintaxe separada.

editar: pensando nisso, uma transição menos problemática para (1) poderia ser retornar visualizações somente leitura para uma versão, evitando alguns bugs de aliasing (mas não aqueles em que você escreve no array original) e permitir a escrita por meio de uma visualização mais tarde

Embora eu aprecie os esforços para minimizar alterações significativas e / ou fornecer caminhos de suspensão de uso ... antes de 1.0 não deveríamos estar dispostos a interromper algumas coisas?

Gosto da ideia de ter uma nova sintaxe para visualizações de array. O comportamento de indexação atual (que gera muitos temporários) deve ficar mais rápido, pois podemos fazer uma melhor análise de escape e reutilização de memória. Eu pessoalmente não sei se gosto de visualizações de array por padrão, e gosto da ideia de pelo menos explorar sintaxe potencialmente diferente para visualizações.

Em geral, sou a favor de quebrar as coisas antes do 1.0, mas este não parece ser um caso em que esteja claro que o novo comportamento é uma chance certa de vitória.

Para dar pelo menos mais alguns passos à frente, quais são os candidatos potenciais para sintaxe de visualizações de array?

Há algo interessante sobre a sintaxe A@[:,1] , que é A at the location of the following indices .

Há algo interessante sobre a sintaxe A @ [:, 1], em que ela lê A no local dos seguintes índices.

Concordo, parece surpreendentemente intuitivo.

Além disso, está crescendo ainda mais em mim porque é muito fácil experimentar visualizações em um código com essa sintaxe.

Minha opinião (ba-ding!) Em A@[:,1] é que é confuso introduzir o símbolo de macro @ em um contexto completamente diferente.

Essa foi a minha primeira reação, mas superei pelos motivos que escrevi acima.

Mas da mesma forma, o {} introduz uma coisa semelhante a um tipo em um contexto completamente diferente.

visualizar (A,:, 1)?

@kkmann : views precisam de sintaxe especial para que recursos como end possam funcionar.

A@[:,1] tem potencial. 1 para testá-lo.

Eu diria que na maioria dos casos você deseja visualizações e apenas ocasionalmente deseja cópias. Haverá uma infinidade de tópicos sobre códigos que rodam lentamente, para os quais a resposta será 'encher seu código com Array @ [...]' As visualizações de array não podem ser imutáveis ​​por padrão? Então você teria que solicitar explicitamente uma visão mutável.

Visualizações imutáveis ​​por padrão são uma ideia interessante. A sintaxe A@[...] poderia ser para obter uma visão mutável e copy(A[...]) seria como obter uma fatia sem visão.

além de descontinuar a indexação de intervalo para uma versão completa, você teria que auditar cada bit de código que a usa

Acho que não temos escolha aqui. Se prosseguirmos com essa alteração importante, teremos que suspender a indexação de intervalo para uma versão completa, talvez com uma opção de desativar os avisos. Se você não fizer isso, o @tkelman receberá ligações sobre veículos autônomos saindo da estrada porque o código parou de funcionar como planejado.

Mas visualizações imutáveis ​​(somente leitura) transformariam qualquer bug em potencial em um erro, certo? Porque ler a partir de visualizações é seguro.

@ mauro3 , nem todos os bugs em potencial.

de @carnaval :

editar: pensando nisso, uma transição menos problemática para (1) poderia ser retornar visualizações somente leitura para uma versão, evitando alguns bugs de aliasing ( mas não aqueles em que você escreve no array original ) e permitir a escrita por meio de uma visualização mais tarde

O significado pretendido do código muda com a versão julia, e esse tipo de atualização é algo que as pessoas estão esperançosamente sendo mais cautelosas do que um Pkg.update.

Visualizações por padrão ainda parecem a escolha certa de longo prazo para mim. A maneira menos arriscada de chegar lá pode ser uma transição via visualizações somente leitura por padrão.

+1 para um período de transição de visualização imutável seguido por visualização mutável

Eu entendo totalmente a hesitação em fazer uma mudança tão radical, mas acho que esse argumento depende de subestimar outro risco que não está sendo muito enfatizado: a perda de credibilidade para Julia como uma linguagem se reverter o curso de uma mudança que está sendo planejada há vários anos e anunciado publicamente repetidamente. Para mim, abandonar essa mudança planejada parece uma traição substantiva de confiança sobre o curso de longo prazo do idioma. Não acho que a perda de credibilidade implícita na reversão dessa decisão deva ser subestimada.

Que tal um argumento / instrução para fazer todas as tentativas de mutação de uma visão gerar um erro para fins de depuração? De qualquer forma, as pessoas precisam passar por uma fase de portabilidade para dar suporte a uma nova versão do Julia: eles podem executar seu código uma vez com esse sinalizador para identificar casos problemáticos e estar seguros depois disso.

Eu voto apenas por fazer visualizações por padrão. No meu ponto de vista, isso é absolutamente consistente com o comportamento atual de setindex!

julia> a= zeros(3,3)
3x3 Array{Float64,2}:
 0.0  0.0  0.0
 0.0  0.0  0.0
 0.0  0.0  0.0

julia> a[:,1] = 1
1

julia> println(a)
[1.0 0.0 0.0
 1.0 0.0 0.0
 1.0 0.0 0.0]

Este comportamento é inconsistente para o caso em que a[:,1] é um valor R e silenciosamente copiado. Portanto, também sou contra tornar as visões imutáveis ​​(o que, aliás, não seria o mesmo que um tipo imutável).

Claro que tudo isso é uma decisão de design e há prós e contras. Mas vamos decidir isso cedo e não adiar para outro lançamento.

@johnmyleswhite , acho que não fornecer visualizações por padrão (mas ainda torná-las fáceis de acessar com algo como A@[...] ) não seria uma traição à confiança. Pode haver razões válidas para não tornar a indexação mutável por padrão.

Por exemplo, à medida que avançamos em direção ao multithreading, visualizações por padrão significam que de repente tenho que me preocupar se os dados com os quais estou trabalhando podem ser alterados por _outro_ thread (mesmo se minha visualização deles for imutável). Portanto, posso precisar adicionar semáforos ou copiar os dados explicitamente, e meu programa e meu raciocínio sobre isso de repente se tornam mais complicados.

Não leia meus comentários como visões opostas por padrão, meu ponto principal é que precisamos estar cientes do que deve ser esperado da transição.

Também seria interessante quanto código isso realmente quebra. Tivemos grandes discussões de que Int8+Int8 -> Int8 quebraria muitos códigos, mas no final a mudança entrou em ação e não houve uma quebra massiva.

@kmsquire : O multithreading é, na minha opinião, totalmente ortogonal a esse recurso. Sim, há cenários em que pode fazer sentido copiar dados em código mutithread para que cada thread tenha sua própria cópia, mas geralmente isso será feito particionando um array e operando em partes desse array. E exatamente isso vai exigir visualizações. Ninguém quer iniciar um monte de threads para copiar o código (o que [:,:] atualmente faz) em um loop "quente".

Seria bom ter uma sintaxe que forneça sempre uma visualização e que funcione de forma idêntica em 0.4 e 0.5. Idem para uma sintaxe que faz uma cópia (sem copiar duas vezes). Se estiver preocupado com a compatibilidade com versões anteriores, você pode usar esta sintaxe e evitar a sintaxe que muda o significado.

Esse tipo de coisa não é sempre feito ao fazer uma alteração significativa? A discussão aqui combina duas questões - qual deveria ser o estado futuro de longo prazo e como proceder para fazer essa mudança.

Não tenho uma opinião forte sobre qual deve ser a solução final, poderia facilmente aceitar qualquer uma das sugestões feitas aqui.

A única coisa que realmente me preocupa é que seria ótimo ter todas essas alterações de matriz de quebra em uma versão, ou seja, 0,5, e não estendê-las em vários lançamentos. Eu terei que revisar todo o meu código de array já para acomodar a indexação de estilo APL para 0,5, e seria uma grande ajuda se qualquer mudança significativa em termos de visualizações viesse na mesma versão, de modo que eu só tenho que mudar meu código de array uma vez. Meu palpite é que um bom número de desenvolvedores / usuários de pacotes se sentiria da mesma maneira.

Eu acho que você pode encontrar alguns bugs em que uma visão muda porque você escreveu no array original fazendo a indexação retornar um objeto que envolve uma cópia e uma visão, e verifica se a cópia corresponde à visão em getindex (e não tem setindex! ). Mas isso não seria um padrão sensato, já que é mais lento do que cópias ou visualizações.

Mas, além dos pontos de @carnaval acima, um terceiro ponto contra as visualizações padrão é que elas nem sempre representariam um ganho de desempenho, mesmo se implementadas de forma eficiente. Mesmo para casos como a indexação de um vetor com Vector{Int} , se o array estiver sendo percorrido várias vezes, é provável que seja mais rápido criar uma cópia. Uma visão em uma matriz esparsa com um vetor de inteiros como linhas parece difícil de fazer em qualquer lugar perto da velocidade de uma cópia para a maioria das operações. Dado que se uma visualização é preferível a uma cópia depende do que está sendo indexado e do que está sendo feito com isso, acho que deve haver maneiras fáceis de fazer isso.

Dado que se uma visualização é preferível a uma cópia depende do que está sendo indexado e do que está sendo feito com isso, acho que deve haver maneiras fáceis de fazer isso.

Este!! As visualizações não devem ser consideradas uma vitória de desempenho definitiva. As visualizações mutáveis ​​em particular são um recurso muito poderoso e elaborado. Acho que é muito difícil prometer que a indexação sempre retornará uma referência aos dados subjacentes em vez de copiar. Não podemos dizer a todos que já definiram getindex que agora devem providenciar o retorno de uma visualização.

Aposto que ninguém deseja que a indexação escalar retorne uma visualização, nem parece provável que seja implementável de forma eficiente. No entanto, com uma sintaxe separada, poderíamos ter até isso, ou seja, A@[1,1] fornece uma visão 0-d que pode ser escrita com v[] = x .

Acho que já foi dito antes, mas há muitos casos como matrizes esparsas, matrizes distribuídas, estruturas de dados de matriz de núcleo, em que as visualizações de matriz por padrão não são uma boa ideia ou são difíceis de ter um bom desempenho.

A sintaxe proposta para visualizações de array me permite usar visualizações quando eu quero e manter meu código existente da maneira que está, sem quebras - tudo com a adição / exclusão de um caractere.

Há claramente casos em que a modificação de visualizações pode ser preferível e casos em que você deseja uma cópia. Muitas pessoas acreditam que as visualizações são uma boa escolha padrão para desempenho e, embora eu tendo a concordar, não tenho certeza se isso é muito claro.

Vale a pena reunir alguns benchmarks, conforme fazemos essa mudança? Isso talvez possa até ser uma coisa boa.

O argumento de que criar visões de submatrizes em matrizes densas levaria à expectativa de que são visões de todas as estruturas de dados, mesmo quando considerar uma visão mutável em uma matriz esparsa é geralmente uma ideia terrível, é um argumento bastante convincente contra isso. Se Julia ainda busca o princípio da menor surpresa, então tornar as visualizações opt-in com uma sintaxe agradável parece bastante razoável.

Desculpe me atrasar aqui.

@eschnett ,

Seria bom ter uma sintaxe que forneça sempre uma visualização e que funcione de forma idêntica em 0.4 e 0.5.

Já temos isso: as duas funções slice e sub .

@johnmyleswhite ,

uma traição substantiva de confiança

No mínimo, concordo que é constrangedor. No entanto, sem mencionar exemplos, tenho a impressão de que outras línguas conceituadas mudaram de ideia sobre uma iniciativa importante. Seria pior persistir diante de problemas óbvios e forçar uma estratégia que não funcione aos usuários.

Além disso, alguns de nós periodicamente apareciam na lista de discussão para dizer "não há necessidade de esperar pelo Arraypocalypse, temos visualizações eficientes agora. Qual é o grande problema de sintaxe, afinal?" Portanto, pelo menos esse ponto de vista, defendido várias vezes no ano passado, é relativamente consistente com a direção em que esta conversa está se movendo.

@JeffBezanson ,

Acho que é muito difícil prometer que a indexação sempre retornará uma referência aos dados subjacentes em vez de copiar.

Depois de passar um bom tempo pensando em como compor dois tipos de visualização diferentes, slice e reshape , não poderia estar mais de acordo. Não que eu ache que não possamos fazer isso, mas está longe de ser óbvio que alcançaremos um desempenho tão bom quanto você obteria fazendo uma cópia. (Ainda temos que implementá-lo, para que seja possível, mas não é óbvio que queremos torná-lo o padrão).

Eu tenho uma vaga lembrança de alguns colchetes de caracteres que estavam sendo propostos para adição à base (encontrei - # 8892). Coisas como [| ... |] , {| ... |} , etc. Não poderíamos pegar um desses para construir uma visualização? Acho que o primeiro poderia funcionar bem sem introduzir um uso para @ que não signifique macro.

Minha proposta é:

  • @ está reservado para macros
  • { } está reservado para tipos de tupla
  • x[1,:] significa indexar e criar uma cópia
  • x[|1,:|] significa indexar e criar uma visão

Pelo que eu posso dizer, essa sintaxe é gratuita ( | não é um operador unário) e é improvável que surpreenda as pessoas. A desvantagem é que é um pouco mais trabalhoso digitar [| e |] que [ e ] .

A@[1,:] parece muito mais intuitivo para uma visão do que x[|1,:|] . Eu pessoalmente não sinto nenhuma confusão com a sintaxe macro.

Distinguir visões mutáveis ​​e imutáveis ​​é vantajoso não apenas no curto prazo, mas indefinidamente?

+1 para A@[1,:]

O comportamento de indexação atual (que gera muitos temporários) deve ficar mais rápido, pois podemos fazer uma melhor análise de escape e reutilização de memória.

Alguma idéia de quais ganhos podem ser obtidos com esse tipo de coisa?

Além disso, acho os argumentos de simplicidade para manter o comportamento atual bastante convincentes. Parece muito mais fácil raciocinar sobre cópias do que visualizações para iniciantes, portanto, ter uma sintaxe um pouco mais complicada para visualizações para aqueles que entendem as diferenças parece uma boa ideia.

No comentário de que @ está atualmente reservado para macros, gostaria também de salientar que Julia _já_ tem dois símbolos que são definidos (ou usados ​​de forma idiomática) para alterar o comportamento de operadores e funções (e ainda estou falando [] como um operador, _a la_ C ++). Estes são os símbolos . e ! , e estou preocupado em adicionar um terceiro símbolo @ ao mix adiciona confusão (possivelmente desnecessária).

As pessoas consideraram A.[1,:] ou mesmo A![1,:] ou A[1,:]! como sintaxe possível? Ou seja, mudar o operador [] para .[] ou []! ? Intuitivamente, o primeiro pode ser interpretado como uma indexação elemento-sábio (o que pode ser interpretado como forçar uma cópia ??) e o último um tipo de indexação mutante (que parece semelhante a visualizações, onde a mutação da visualização resultante resulta em mutações à matriz original).

A![1,:] já é uma sintaxe válida para indexação em uma matriz chamada A! . As outras sintaxes estão disponíveis.

Além disso, @[] ainda poderia ser disponibilizado para definição para fazer magia semelhante a macro com os índices (estou pensando em algo parecido com o TensorOperations.jl de @jutho , aqui, possivelmente até Devectorize .. . apenas adivinhando aqui)

@StefanKarpinski Sim, pensei nisso depois, []! faz mais sentido para mim pessoalmente.

Consulte https://github.com/JuliaLang/julia/issues/7721#issuecomment -170942938 para obter uma proposta sintaticamente semelhante a ser considerada aqui: f(x)! por chamar f(x) e finalizar automaticamente o resultado saída do escopo.

Eu realmente não gosto de usar ! para qualquer coisa, exceto operações de mutação. Por exemplo, reshape! foi proposto para a reformulação do compartilhamento de dados. Acho que isso cria muita confusão sobre o que ! significa. Ter uma visão não é uma mutação; na verdade, funciona especialmente bem em programas que nunca usam mutação!

( @JeffBezanson : isso inclui a oposição à sintaxe f(x)! proposta em # 7721?)

@JeffBezanson Esse é um ponto justo ... Às vezes, já fico confuso com ! . A visão permite um tipo de mutação possivelmente atrasada, mas não modifica o array imediatamente, então admito que é um pouco exagerado na interpretação de ! .

f(x)! de # 7721 não me incomoda tanto, talvez porque também envolva algum tipo de efeitos colaterais, ao passo que retornar a visualização de um array é apenas uma função pura.

OK, e o outro operador, .[] ? Posso ver duas alternativas:

Em um mundo onde [] retorna uma visão (onde definido, por exemplo, Array , ou então uma cópia onde faz mais sentido), .[] pode ser conveniente para _force_ uma cópia. Claro, conforme discutido, torna-se difícil prever o que você receberá de [] .

Ou talvez .[] pudesse ser considerado uma alternativa para @[] para visualizações, caso isso pareça intuitivo para alguém? Como as pessoas interpretariam a "indexação por elemento"?

Como as pessoas interpretariam a "indexação por elemento"?

A indexação pontual é uma possibilidade. A.[1:3,1:3] retornaria os três primeiros elementos ao longo da diagonal. # 2591

Eu realmente gostaria que houvesse uma resposta certa e clara aqui ... mas nem a semântica nem o desempenho das visualizações tornam essa decisão óbvia. Talvez apoiar dois operadores seja a coisa certa a fazer.

OK, eu concordo, # 2591 faz muito mais sentido para .[]

+1 para A@[1,:]

Eu também acho que ter a capacidade de tornar um array temporariamente imutável seria muito útil para rastrear bugs onde um array é alterado e você não consegue descobrir o porquê (ou apenas para verificar se ele não está sendo alterado dentro de uma função). Isso não precisaria necessariamente fazer parte de slice mas poderia ser. Existe uma maneira de fazer isso agora?

Eu realmente gostaria de ter mais dados sobre o impacto no desempenho aqui. Eu simplesmente não tenho tempo para perseguir todas as mudanças que são necessárias para fazer este trabalho, mas aqui está um começo pouco funcional que contorna o problema de bootstrap com views como padrão: mb / viewdefault . Veja as advertências na mensagem de confirmação. Mas pelo menos permite executar os testes de desempenho do array ... contanto que você passe o nome do arquivo completo. Observe também que ele não conta toda a história, já que a verificação de limites ainda não foi incorporada às visualizações.

Seria absolutamente maravilhoso ver alguém pegar a tocha aqui e atirar em alguns números preliminares com a infraestrutura de CI de referência.

@andyferris Os nomes de macro não precisam sempre ser identificadores (pelo menos agora)? Acho que um trabalho especial precisaria ser feito para permitir que caracteres como [ , ( ou { sejam usados ​​como nomes de macro e para que o caractere de fechamento correspondente seja tratado corretamente .
Acho que a sintaxe A[ind]! pode ser ambígua. Por exemplo, o que A[ind]!=5 significa?
Acho que a sintaxe f(x)! proposta também pode gerar problemas de ambigüidade. (sim, você pode exigir um espaço entre ! e = , mas parece potencialmente confuso)

Que tal simplesmente <strong i="5">@view</strong> A[1,:] ? (consulte https://github.com/JuliaLang/ArrayViews.jl/pull/14)

Acho que é muito prolixo quando você deseja usar todas as visualizações em um algoritmo, por exemplo.

não poderia uma macro @view ter o escopo para substituir getindex dentro de um bloco begin ... end inteiro?

eu não vejo porque não

Outra coisa a considerar se introduzirmos uma nova sintaxe para visualizações (por exemplo, @[...] ): qual deve ser a variante setindex!

A@[1,:] = x

Faz? Já que parece meio confuso, pode ser melhor não permitir, mas então você deve se lembrar de excluir @ quando fizer a atribuição.

Idéia legal @tkelman , seria semelhante a @inbounds .

não exatamente: @inbounds é um pouco mais mágico (consulte https://github.com/JuliaLang/julia/blob/9bfd27bd380124174a5f37c342e5c048874d71a4/test/boundscheck.jl). Esta seria uma transformação de sintaxe direta.

Similar no uso, não na implementação. Enfim, eu gosto: P

Mas problemático para este caso de uso: A é uma matriz esparsa (então vamos dizer que é mais rápido copiar do que construir uma visualização), B é densa e tem visualizações rápidas. O que <strong i="7">@view</strong> c += A[1:10,2:15]*B[:,j] fazer?

Não poderia ser escrito algo como c += A[1:10,2:15]*(<strong i="5">@view</strong> B[:,j]) onde @view só funciona no que está entre parênteses. Não sei o suficiente sobre macro para saber se é viável.

Mas talvez perca a verbosidade agora ..

Acho que seria melhor ter as duas sintaxes. Em seguida, use A2@[] se um controle refinado for necessário. (editar: adicionado @ )

@KristofferC , sim, isso funciona, mas a crítica "verbosa" se aplica se você tem que escolher e escolher regularmente.

Dito isso, no geral eu gosto. Observe que a implementação é bastante simples: trata-se de substituir Expr(:ref, :A, args...) por Expr(:call, :slice, :A, args...) (embora você também tenha que procurar end e substituí-lo manualmente).

Não me importo em pelo menos experimentar @view no master por algum tempo e ver como é antes de fazermos A@[] .

Caso alguém queira tentar, aqui está a lógica atual para substituir end .

A@[] está indo na direção do sigilo perl de ser difícil de ler e entender para novos usuários. Pelo menos a macro ou função explicam o que eles fazem ...

Por que uma vista esparsa teria que ser cara para construir? Pode ser preguiçoso e essencialmente não fazer nada além de referenciar a matriz e os índices pai, e só ser caro na hora do uso. Ele permitiria que você negociasse a alocação versus a indireção, dependendo de quantas vezes você espera usar o resultado indexado. No entanto, há um grande problema de método ausente.

Oferecer duas sintaxes tem vantagens, mas não resolve todos os casos de uso. Na verdade, posso ver três deles:
1) Você quer uma cópia que pode ser modificada sem afetar o array original -> [] .
2) Você deseja que uma visão modifique o array original -> @[]
3) Você deseja a maneira mais rápida / barata de extrair um subconjunto de um array, mas não precisa modificá-lo. Você prefere uma visualização (possivelmente imutável) para combinações de tipo de índice / array onde é mais rápido e uma cópia em outro lugar. Isso é semelhante a usar eachindex(a) vez de 1:length(a) ao iterar em uma matriz. -> nenhuma sintaxe proposta

Oferecer três sintaxes é um pouco demais ... Mas parece-me que a maior parte do ganho que esperamos (ed) do uso de visualizações por padrão corresponde ao caso 3.

Portanto, concordo com @tkelman que a questão de saber se as visualizações podem ser tornadas mais eficientes para todos os tipos Base merece investigação: isso permitiria efetivamente a fusão dos casos 2 e 3 e, possivelmente, roubaria a sintaxe do caso 1 (ou seja, visualizações por padrão). Com sintaxe de gerador generalizada e funções anônimas rápidas, as visualizações parecem um ótimo recurso para evitar alocações e garantir um alto desempenho.

Um problema em usar @view para agrupar expressões / blocos: e se eu tiver um bloco agrupado em @view e precisar adicionar uma indexação de cópia dentro? Então eu teria que refratar todo o bloco. E se eu não perceber? E se eu colar o código nesse bloco? Eu me sentiria muito melhor se ter uma visão ou não dependesse apenas da operação, e não do contexto do código ao redor.

Por que vale a pena:

Eu lido muito com visualizações em Aprendizado de Máquina. Muitos algoritmos percorrem as observações de algum conjunto de dados, que por si só é frequentemente representado como uma matriz. Em casos como SVMs, não se pode formular isso em termos de multiplicações de matrizes, mas em vez disso, é necessário fingir que cada observação é um vetor. Nestes casos, uma sintaxe legível simples seria apreciada, uma vez que estou visando pseudo-código semelhante a implementações. Não acho que A@[1,:] seja confuso; ele até lê "A em [1 ,:]".

Eu concordo com @toivoh que usar @view parece que força alguém a decidir entre usar apenas visualizações ou truques mais detalhados. @inbounds parece muito mais claro a esse respeito

Será que view(arr, dims) e view!(arr, dims) funcionariam para retornar uma visão imutável e mutável, respectivamente?
Isso permitiria parametrizá-lo com base em se arr é denso, esparso ou algo totalmente diferente, certo?

@tkelman Em A@[x,y] , por que @ um sigilo? Eu li @[] como um operador, ao invés de @ como um sigilo anexado a A .

É um operador de aparência engraçada que não tem muitos precedentes. Ter que controlar a sintaxe para diferentes tipos de indexação não parece ideal. É um pouco parecido com a forma como a ferrugem tem diferentes tipos de indicadores (e costumava ter mais), não é intuitivo e tem que ser muito monitorado ao tentar ler o código pela primeira vez. Eu provavelmente poderia viver com A@[] mas parece estranho para mim.

editar: se as visualizações fossem padrão e construídas preguiçosamente, então seria de se esperar que copy(A[1,:]) fosse exatamente equivalente à cópia de A[1,:] mas não tenho certeza de quão próximos estaríamos na prática usando o compilador de hoje.

edit2: Eu acho que @[] é tão bom ou melhor do que qualquer uma das outras propostas de "dois tipos de colchetes". A alternativa do meu ponto de vista é fazer [] sempre retornar uma visão (somente leitura para uma transição de lançamento) e chamar copy em uma expressão de indexação para obter um comportamento equivalente ao de hoje. Vou explorar o branch de @mbauman .

Se olharmos para o precedente, A(1,:) poderia ser sempre cópia, uma vez que é isso que é no matlab AFAIK, e A[1,:] sempre vê como é quase o caso no NumPy. Eu acho que () é uma má ideia, apenas jogando-a no brainstorm. Também não gosto de A@[1,:] .

Se a principal objeção a @view é como lidar com casos excepcionais (por exemplo, uma cópia dentro de um bloco @view ou uma visualização normalmente), as pessoas podem usar o existente sub / slice no último caso, e talvez possamos adicionar métodos à função copy que manipula o primeiro? Atualmente, não parece que copy tenha qualquer método com vários argumentos, então poderia ser basicamente um nome mais curto para o que getindex faz agora.

então copy(A, idx...) sempre forneceria uma cópia, sub(A, idx...) sempre forneceria uma visualização e A[idx...] copiaria por padrão, mas poderia ser embrulhado em um @view para alterar comportamento (ou veja por padrão com uma macro @copy para mudar o comportamento, IDK que é melhor)

Não sou um grande fã de dicas sintáticas sutis que mudam o comportamento sutil (como os tipos de colchetes determinando o comportamento de cópia / visualização) porque parece que é muito fácil cometer um erro que parece funcionar até não funcionar. Parece que seria uma armadilha comum para novos usuários, e também ao refatorar código ou apenas deslizar o código para ver o que ele faz.

@timholy A implementação não é tão simples, porque se você simplesmente substituir :ref Exprs, <strong i="7">@view</strong> X[:, 1] += 2 se tornará slice(X, :, 1) += 2 . Acho que você poderia usar o ajuste do analisador custom_ref de # 8227 para obter o comportamento desejado, incluindo end .

@ssfrr Acho que @view poderia ser pelo menos tão obscuro quanto @[] ao refatorar código. Sem dúvida, pessoas diferentes colocarão @view torno de blocos de tamanhos diferentes, e talvez algumas pessoas até mesmo o coloquem em torno de arquivos inteiros.

Para interpor como um aparte: @tkelman e eu passamos um pouco de tempo na última primavera
conversando sobre quais técnicas podem ser usadas para fazer matriz esparsa
manipulações e cortes muito eficientes. A notação de fatia geralmente está em
probabilidades com notação de fatia geral, mas a visão amigável esparsa / não
copiar versões restritas do slice são super fáceis de definir para
praticamente qualquer layout denso padrão.

Não tenho certeza se há interesse?

Na sexta-feira, 29 de janeiro de 2016, Simon Kornblith [email protected]
escrevi:

@timholy https://github.com/timholy A implementação não é bem
tão simples, porque se você simplesmente substituir: ref Exprs e @view X [:, 1]
+ = 2 se tornará a fatia (X,:, 1) + = 2. Acho que você poderia usar o custom_ref
ajuste do analisador de # 8227 https://github.com/JuliaLang/julia/pull/8227 para
obtenha o comportamento desejado incluindo o fim.

@ssfrr https://github.com/ssfrr Acho que @view poderia ser pelo menos tão
obscuro como @ [] ao refatorar código. Pessoas diferentes, sem dúvida,
coloque @view em blocos de tamanhos diferentes, e talvez algumas pessoas
iria até mesmo colocá-lo em torno de arquivos inteiros.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/JuliaLang/julia/issues/13157#issuecomment -176787527.

@cartazio Estou interessado em revisitar essas discussões de design e ver o que é possível se adaptar ao sistema de tipos de Julia, mas este problema está ficando muito longo e pode ser melhor mover algumas partes desta discussão para seus próprios problemas separados ou lista de discussão / direta tópicos de e-mail.

Se você considerar A[1,:] como um atalho para construir um 'tipo de fatia de cópia' que é aplicado ao método de chamada - A( copy_slice(1, :) )

Não seria razoável ter uma visão mutável como A( view_slice( 1, : ) ) ?

Adicionar uma sintaxe para construir diretamente copy_slice e view_slice torna isso mais fácil de usar em linha. Isso é muito semelhante aos construtores de alcance existentes

1:3 -> UnitRange{Int64}

Isso basicamente funciona hoje se afirmarmos que `view_slice é um apelido para tupla (infelizmente é o oposto de NumPy / matlab), mas você tem a opção de ser claro e explícito

1,:,1:2 -> (1,Colon(),1:2)

então

A( 1,: ) -> A( view_slice( 1,: ))

É tão prolixo para escrever

A( view( 1, : ) )

Isso também permite a extensão para visualizações imutáveis

A( immutable_view( 1,: ) )

Você pode implementar isso hoje sem alterações do analisador (e provavelmente de uma forma compatível com versões anteriores, despachando para getindex et al), exceto o suporte para end .

Se o analisador for modificado para substituir end por: end na forma abreviada geral de dois-pontos, podemos confiar no despacho para fazer a coisa correta.

1:end -> 1 : :end -> colon( ::Int64, ::Symbol )

[editar: embora eu prefira Type {End}]

Antes de me preocupar com a sintaxe, eu realmente gostaria de ver mais esforços de engenharia colocados em visualizações e uma análise mais detalhada das compensações de desempenho. Uma coisa que seria de grande ajuda e é bastante mecânica é atualizar os métodos de indexação do SubArray para usar o novo idiomático <strong i="5">@boundscheck</strong> checkbounds(A, I...) . (Em geral, todos os usos de unsafe_getindex podem ser substituídos por <strong i="8">@inbounds</strong> A[...] e todas as suas definições de método removidas.)

@nalimilan escreveu:

3) Você deseja a maneira mais rápida / barata de extrair um subconjunto de um array, mas não precisa modificá-lo. Você prefere uma visualização (possivelmente imutável) para combinações de tipo de índice / array onde é mais rápido e uma cópia em outro lugar. Isso é semelhante a usar eachindex (a) em vez de 1: length (a) ao iterar em uma matriz. -> nenhuma sintaxe proposta

Eu gostaria que fosse assim tão simples. Mesmo sem se preocupar com a mutabilidade, o problema é que a opção mais rápida depende não apenas da estrutura na qual você está indexando, mas também de quantos dados estão envolvidos e como você acaba usando o objeto retornado. Há também a dificuldade de que estruturas de array especializadas de repente se tornem não especializadas. Atualmente, contornamos isso para arrays densos compatíveis com BLAS e visualizações com StridedArray typealias, mas levará muito tempo para encontrar todos os métodos ausentes e incompatibilidades de tipo que os subarrays irão atingir em geral. Pense nos LinSpace métodos ausentes ... mas muito mais abrangentes.

E há o problema da classe de complexidade para arrays especializados. Não é caro construir visualizações esparsas, por exemplo, mas o problema com elas é que os SubArrays retornados não atingem mais as estruturas esparsas especializadas. Portanto, sum(slice(S, :, 1) atinge o fallback abstrato e não é mais O (nnz).

@mdcfrancis - um dos usos mais poderosos da palavra-chave end é usá-la em aritmética, por exemplo, A[(end+1) ÷ 2] ou A[mod1(x, end)] .

-1 para A @ []

É muito parecido com a introdução de contextos Perl, e o símbolo @ sugere que há macros envolvidas. Gosto da ideia A [| x, y |] ou de algum outro tipo de colchete novo.

Acho que o que as pessoas estão procurando não é "visualizações por padrão", mas estreitando a diferença de desempenho entre os benchmarks apresentados por https://github.com/JuliaLang/julia/issues/13157#issuecomment -168378436. Seria bom que o comportamento padrão fizesse isso, mas um ajuste fácil que pode ser encontrado na seção Dicas de desempenho é quase tão bom.

@mbauman - faz com que o tipo de sentinela final atue como um número. A árvore de expressão é então passada para o método de chamada e pode ser avaliada com o comprimento do array?

END + 1 -> EndExpr( length(A))

@ mauro3 trabalhou nisso em um pacote de array irregular . Não é fácil.

-1 para A@[] . A[[index]] disponível para sintaxe? Isso chamaria algo como getindexview(A, index) ? Acredito que isso expressa que estamos obtendo um nível de indireção apropriado para uma visão.

@GlenHertz Isso já significa algo:

julia> a = ["pablo","martín","garcía"] ; a[[1,2]]
2-element Array{UTF8String,1}:
 "pablo" 
 "martín"

@ lucasb-eyer escreveu:

Se olharmos para o precedente, A (1, :) poderia ser sempre cópia, uma vez que é isso que é no matlab AFAIK, e A [1 ,:] sempre vê como é quase o caso no NumPy. Eu acho que () é uma má ideia, apenas jogando-a no brainstorm. Também não gosto de A @ [1 ,:].

Esta sugestão sobre () para cópias e [] para visualizações parece-me a solução mais concisa e pode ser a mais intuitiva para novos usuários de MATLAB e Python (aumentaria a quantidade de executáveis Código MATLAB). Parece "certo" que o C [] faça algo pontudo e o () executa uma função para retornar um novo objeto (e isso naturalmente não faz sentido no lado esquerdo -lado ... mas teríamos que adicionar um aviso para evitar erros lógicos aqui). No geral, eu gosto disso.

Infelizmente, perdemos a capacidade de usar call para objetos do tipo array ...

Usar () para indexar elimina a principal vantagem de ter uma sintaxe para indexar - ou seja, permitir que coisas como end funcionem.

Realmente me incomoda dizer a todos que primeiro mudem toda a indexação para usar ( ) , no futuro, usar [ ] para visualizações quando você quiser.

A coisa verdadeiramente semelhante ao C seria usar &A[x,y] para visualizações, o que atualmente é um erro de sintaxe e, portanto, pode realmente ser possível.

@JeffBezanson Isso também não seria ambíguo, com o operador & e? Ter uma sintaxe onde o significado muda com ou sem espaços torna as coisas mais difíceis de entender. Julia não deveria ser melhor do que C também?

Ser um idioma melhor não significa necessariamente que você precise alterar a grafia de cada operador.

Usar () para indexação elimina a principal vantagem de ter uma sintaxe para indexação - ou seja, permitir coisas como o fim de trabalho.

Por curiosidade, end não poderia ser implementado como : / Colon e deixar que o compilador lide com isso em vez do analisador? Ou seja, torná-lo um recurso totalmente tipado e sobrecarregável, em vez de um recurso de sintaxe-açúcar? Eu entendo que teria que suportar um monte de operadores aritméticos ( end - 1 , etc), mas deveria estar na terra do viável, não?

EDIT: Desculpe, vejo agora a discussão sobre isso acima, por exemplo, Ragged.jl.

A coisa verdadeiramente semelhante ao C seria usar & A [x, y] para visualizações, o que atualmente é um erro de sintaxe e, portanto, pode ser possível.

Podemos imaginar o uso de & para fazer alterações nas tuplas, NTuple s, arrays imutáveis, etc? (ou seja, útil para matrizes na pilha, como # 11902)

&A[x,y] também não parece tão ruim! Parece intuitivo e eu absolutamente preferiria que fosse ()

Ter uma sintaxe onde o significado muda com ou sem espaços torna as coisas mais difíceis de entender.

Embora não esteja errado, acredito que seja um pouco forçado neste caso. Podemos pensar em uma declaração sensata e ambígua em que ambas as interpretações façam sentido? Tudo o que consigo pensar é no açúcar de multiplicação.

Isso funciona em 0.4:

A = rand(Int, 10,10)
3&A[1:2,3:5]
2x3 Array{Int64,2}:
 1  0  3
 3  0  2

@andyferris : fazer end funcionar em total generalidade com essa abordagem requer avaliação atrasada; sem ele, você pode, na melhor das hipóteses, implementar uma versão incompleta com um tipo que armazena em cache todas as operações nele.

Aqui está um pensamento aleatório que ainda não pensei bem. Eu apenas trago aqui para termos um brainstorm.

Talvez seja uma boa hora agora para repensar o significado semântico de array (ou tensor) e a relação entre array e função.

Eu gostaria de pensar em array desta maneira: como objetos abstratos, array não é diferente de uma função. Ambos recebem alguma entrada (índice para array) e fornecem saída (valor). Array v = A (i) é uma função, o parâmetro de entrada é o índice. A função y = f (x) é uma matriz de dimensão infinita, onde o índice é x. Isso também é verdade matematicamente, os vetores no espaço euclidiano não são muito diferentes das funções no espaço de Hilbert.

Eu gostaria de pensar que o significado semântico de A [1] é que, A é algum armazenamento, usamos [] para acessar o valor particular do armazenamento. Esta é uma semântica de baixo nível.

No entanto, podemos usar () para modelar a abstração de alto nível. A (1) é função, você insere o índice 1 e obtém algo, então podemos usar o mapa e outras funções de alto nível para manipular A. Para avaliar uma função em um intervalo de valores (ou um contêiner), podemos definir um intervalo versão de f que leva um intervalo como parâmetro, ou poderíamos mapear f ao longo do intervalo. Podemos fazer a mesma coisa com um array também.

Para mim, view é um mecanismo de avaliação preguiçoso de array como a continuação de uma função. Portanto, poderíamos construir algumas funções de alto nível para o array produzir visualização. Assim como obter uma mônada de continuação para a função f. Não tenho uma ideia clara de como fazer isso.

Talvez seja um bom momento para unificar o comportamento da função e do array. Não sei se isso é viável em teoria (não sou especialista em idiomas). Mas vai ser tão legal, se isso puder ser feito na Julia. Mais uma vez, sei que estou pedindo algo que é uma mudança muito grande para a Julia atual. Se isso for irrelevante, apenas me ignore.

+1 para [] visualizações
+/- 1 por () cópias
-1 por &A[x,y] visualizações
-1 por A@[x,y] visualizações

Raciocínio:
Existe um idioma comum em que o caso comum deve ser rápido e fácil (para codificar, neste caso), enquanto os casos incomuns devem ser possíveis. Atualmente, as cópias são tratadas como o caso comum por meio da sintaxe [] (que é essencialmente açúcar para getindex / setindex! ); as visualizações são tratadas como casos incomuns por meio de sub / slice e sem açúcar. Acho que o fato de tantos terem falado sobre ter uma abordagem melhor para as visualizações indica que as visualizações devem ser tratadas como o caso comum com sintaxe rápida / fácil otimizada.

Aqui está minha resposta a algumas preocupações pendentes levantadas por

Realmente me incomoda dizer a todos que primeiro mudem toda a indexação para use () e, no futuro, usem [] para visualizações quando você quiser.

É realmente necessário fazer a troca de todos? Minha impressão é que a maior parte do código provavelmente não depende de nenhuma cópia automática feita atualmente por [] e provavelmente se beneficiaria com o uso de visualizações em termos de desempenho automático e mágico. Uma vez que já existem grandes mudanças nos arrays wrt v0.5, não acho que a mudança seja um grande problema.

A coisa verdadeiramente semelhante ao C seria usar & A [x, y] para visualizações, o que atualmente é um erro de sintaxe e, portanto, pode ser possível.

Acho este comentário um tanto estranho. C realmente não tem a noção de submatriz em N dimensões gerais, e ponteiros finais ou contagens de elementos (ou sentinelas!) São usados ​​ad-hoc para (sub) matrizes unidimensionais, e nenhum deles realmente usa & . Independentemente disso - como mencionei acima - acho que a maior parte do código de array Julia provavelmente deseja visualizações por motivos de desempenho, o que resultaria em um dilúvio feio de e comercial com a sintaxe &A[x,y] .

Finalmente, realmente precisamos de uma sintaxe especial para cópias de submatrizes? Já temos deepcopy e convert . Se as cópias são realmente importantes, certamente não me oponho a ter uma sintaxe especial como A(x,y) , ou algum outro pequeno ajuste de A[x,y] por exemplo, com seu símbolo favorito. Só não acho que as cópias são tão importantes quanto as visualizações

Se alguém tiver evidências de que as cópias devem ser o caso mais comum / fácil de usar, como um pacote grande que seria difícil de mudar, entre em contato conosco.

É realmente necessário fazer a troca de todos? Minha impressão é que a maioria do código provavelmente não depende de nenhuma cópia automática feita atualmente por [], e provavelmente se beneficiaria de forma automática em termos de desempenho com o uso de visualizações. Uma vez que já existem grandes mudanças nos arrays wrt v0.5, não acho que a mudança seja um grande problema.

Não estou dizendo que não devemos fazer isso, mas correndo o risco de me repetir, a questão não é se muitas pessoas confiam ou não no comportamento. O ponto é que a diferença entre os dois comportamentos é basicamente, AFAICT, impossível de detectar (ok, factível, mas tornaria cada operação de array extremamente lenta).

Portanto, não é como se as pessoas tivessem que corrigir alguns avisos irritantes, o modo de falha (embora raro) é: o código funciona, mas retorna a resposta errada. Novamente, não dizer que isso torna a mudança um não-iniciante, apenas que, entre as incompatíveis com versões anteriores, esse tipo requer um pensamento cuidadoso.

Acho que a maior parte do código de array Julia provavelmente deseja visualizações por motivos de desempenho

Aha! Esta é uma distinção absolutamente crucial: você deseja apenas que os programas sejam executados mais rapidamente ou deseja um objeto A tal que a modificação de A modifique outro array B?

Se você não se importa em ter a _semântica_ de visualizações, então a melhor abordagem é retornar um objeto de visualização copy-on-write, e a biblioteca pode decidir com base em quaisquer critérios que desejar se deve retornar uma visualização. Ele pode retornar exibições apenas para certas regiões contíguas que acredita serem eficientes. Você obtém acelerações sem precisar pensar sobre quando algo é uma visualização ou não.

Em contraste, com a semântica da visão, estaríamos _prometendo_ que essas duas funções são equivalentes:

function f1(A)
    A[1,:] = 2
end

function f2(A)
    row = A[1,:]
    row[:] = 2
end

Se eu pensar _apenas_ sobre a semântica disso (ignorando qualquer consideração de desempenho), acho que realmente prefiro cópias em vez de visualizações como o padrão. Em geral, as cópias parecem muito mais fáceis de raciocinar e provavelmente têm muito menos potencial para introduzir bugs sutis no código.

Ponto justo, Jeff. Você está certo de que a semântica também importa muito. Eu
acho que quando eu disse desempenho, também estava pensando em quão rápido e
facilmente o programador pode fazer o trabalho, pelo menos em média.

Seu snippet hipotético poderia facilmente ir de qualquer maneira em termos de cópia / visualização
semântica. Mas, no código real, acho que a necessidade de semântica de visualização surge
mais frequentemente do que a necessidade de semântica de cópia. Por exemplo, alguém postou
anteriormente neste tópico sobre visualização de vetores de coluna em uma matriz de dados para
códigos de aprendizado de máquina, etc. Acho que quase todo mundo discutindo sobre a sintaxe
também tem exemplos concretos de código que precisa de semântica de visualização.

Se eu estiver certo sobre a existência de mais código que deseja visualizações (em termos de
desempenho ou semântica), então acho que meu ponto de vista representa essa visão
a sintaxe deve ser mais fácil, e o código existente que não depende de cópia
a semântica receberia um aumento de desempenho gratuito.

Sobre o ponto muito bom de Oscar, sim, haveria bugs sutis em alguns
casos com certeza. Mas a maioria dos mantenedores de pacotes devem estar bem cientes do
mudanças significativas chegando em 0,5, então deve acabar sendo relativamente simples
correções de bugs, eu acho. Mais uma vez, estou aberto à persuasão se alguém tiver uma ideia concreta
exemplo de uma base de código que seria difícil de mudar para visualizar a semântica.
Em 5 de fevereiro de 2016, 14h16, "Jeff Bezanson" [email protected] escreveu:

Acho que a maior parte do código de array Julia provavelmente deseja visualizações por motivos de desempenho

Aha! Esta é uma distinção absolutamente crucial: você só quer programas
para correr mais rápido, ou você quer um objeto A tal que a modificação de A modifique
outra matriz B?

Se você não se importa em ter a _semântica_ de visualizações, então o melhor
abordagem é retornar um objeto de visualização copy-on-write, e a biblioteca pode
decidir com base em quaisquer critérios que deseja retornar uma visualização. isto
poderia retornar visualizações apenas para certas regiões contíguas que ele pensa que irão
seja eficiente. Você obtém acelerações sem precisar pensar sobre quando
algo é uma visão ou não.

Em contraste, com a semântica da visão estaríamos _prometando_ que esses dois
funções são equivalentes:

função f1 (A)
A [1 ,:] = 2
fim

função f2 (A)
linha = A [1 ,:]
linha [:] = 2
fim

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/JuliaLang/julia/issues/13157#issuecomment -180506892.

Acho que a abordagem mais conservadora (e provavelmente correta) é reter a funcionalidade de cópia atual com colchetes. É seguro e não deve arruinar a vida das pessoas (não subestime os insetos sutis que podem surgir). O problema é que eu, pessoalmente, desejarei usar visualizações com frequência e, portanto, elas devem ter uma sintaxe curta e dedicada.

Mais uma vez, estou aberto à persuasão se alguém tiver uma ideia concreta
exemplo de uma base de código que seria difícil de mudar para visualizar a semântica.

Acho que a melhor pergunta é se alguém tem um exemplo concreto de uma base de código que não seja difícil de mudar. O problema é que o mantenedor do pacote pode nem saber que há um problema, mesmo com uma boa cobertura de teste. Essa é a natureza dos bugs sutis e difíceis de detectar.

Podemos usar colchetes para visualizações?

julia> x = rand(1,5)
1x5 Array{Float64,2}:
 0.877481  0.18628  0.739978  0.306893  0.037569

julia> x{:,2:3}
ERROR: TypeError: Type{...} expression: expected Type{T}, got Array{Float64,2}

Ele fornece um erro, o que significa que pode ser viável. (Acho que foi sugerido antes, mas não me lembro de ter visto um motivo pelo qual é uma má ideia)

Visto que não podemos usar A [[2,21]]. Que tal algo não simétrico: A [[2,21]? Todas as sugestões com os símbolos & e @ fariam o código parecer realmente estranho. Eu sei que a sugestão não é simétrica, mas é fácil de digitar e parece mais familiar.

@nstiurca Sim, as semânticas de visão são muito úteis, mas para mim isso ainda não é suficiente para resolver o caso. A[1,1] uma visualização? Se não, então (1) colchetes são uma construção estranha que às vezes retorna uma referência e às vezes não retorna, (2) se você deseja um ponteiro para um único elemento, você precisa alternar para alguma outra sintaxe. Isso é piorado pelo fato de que alguns tipos, por exemplo, matrizes esparsas, não implementam visualizações ou possuem visualizações muito lentas. Tornar as visualizações _1 caractere_ mais fáceis de obter não parece valer a pena adicionar toda essa confusão ao humilde operador de colchetes. Precisaríamos de algum outro operador para ser o operador de "indexação sã", para o qual alguns propuseram A( ) mas isso _não vai acontecer_. A operação nova e mais complicada terá uma nova sintaxe.

Que tal algo não simétrico

Porque isso deixaria muitos programadores loucos: https://xkcd.com/859/

@JeffBezanson A[1,1] é uma visão se está no lado esquerdo de = , e uma cópia de outra forma atualmente ... Para mim, isso é na verdade _mais_ complicado do que "sempre uma visão" para uma sintaxe e " sempre uma cópia "para outra sintaxe. Mas você já se decidiu, tanto faz.

@Evizero @musmo
oh-god-why

Em A[1,1] = z , talvez seja mais preciso considerar [1,1] um modificador para = , não A . Por exemplo (e para inserir parênteses onde eles não são realmente permitidos), é A ([1,1]=) z (a representação real, entretanto, é setindex!(A, z, [1, 1] ), semelhante a como A (+=) z analisa A = A + z vez de (a construção não existente) (A+) = z .

O resultado de &x[3] já pode ser inserido: Ref(x, 3)

Isso mesmo, não faz sentido dizer que A[1,1] à esquerda de = é uma visualização --- o que significaria se fosse uma cópia?

Concordo plenamente que a coisa certa é ter uma sintaxe que signifique "sempre uma visão" e outra signifique "sempre uma cópia". A única discordância é sobre qual deve ser a sintaxe de cada um. Acho que mudar A[ ] é muito drástico. Que tal, digamos, pesquisa no dicionário? Isso oferecerá suporte a visualizações também?

Nesse ponto, acho que A[...] sempre significa copiar, enquanto A@[...] sempre significa que vale a pena experimentar ver como é. A sintaxe exata não é tão importante quanto experimentá-la. Para valer a pena, no entanto, não estou totalmente convencido do que chamarei de "a posição de Jeff" por alguns motivos:

  1. Fazer um tipo de visão preguiçosa genérica que pode ser o tipo de resultado para fatiar recipientes gerais não parece tão difícil para mim. O fato de arrays densos terem uma implementação de visão mais eficiente é uma boa otimização, mas apenas uma otimização - você poderia simplesmente retornar uma visão preguiçosa e o comportamento seria o mesmo. Construir uma visão preguiçosa seria barato e se copy de uma visão reduzir a visão preguiçosa para não ser mais uma visão, então você tem uma maneira consistente de expressar as visões e cópias: A[...] é uma visão e copy(A[...]) é uma fatia sem visualização. Nenhuma sintaxe estranha precisa ser introduzida. Isso também tem a vantagem sintática de que a coisa mais curta e óbvia é barata (e freqüentemente rápida de usar também), e a coisa cara - isto é, fazer uma cópia - é muito explícita; você definitivamente sabe que uma cópia está sendo feita.
  2. Eu não aceito o argumento da indexação escalar. Isso porque a maior diferença permanece em ambos os sentidos: fatias não escalares - por exemplo, A[1,:] e v[1:5] - recipientes de retorno; fatias escalares - por exemplo, A[1,2] e v[1] - retornam valores únicos. Em comparação com esta diferença semântica ENORME, a diferença entre uma fatia não escalar sendo uma visualização e uma fatia escalar sendo uma não visualização parece trivial. Como já precisamos entender essa diferença, não vejo como esse argumento seja convincente.

Eu sei que em # 8892 discutimos os colchetes angulares baseados em Unicode, mas e quanto aos colchetes angulares ASCII ( A<:,3> )? Eu reconheço que eles são os menos legíveis de todas as opções de colchetes, mas eles se parecem com "v" de lado para "visualizar": smile :

Um problema óbvio: atualmente

julia> 2<3>1
true

julia> 1<2>3
false

Eu acho que pessoalmente, eu estaria disposto a mudar para 1 < 3 && 3 > 2 em troca de outro colchete de um caractere. Em algum contraste com a impossibilidade de emitir um aviso para a semântica copy-> view, eu me pergunto se seria possível emitir um erro de analisador informativo para essa mudança na sintaxe.

Ou poderíamos ir totalmente em C ++ e usar A<<:,3>> . Já estou tremendo com pensamentos de grandiosidade!

Para cópia na gravação: 'COW' (U + 1F404)
Para fatias: 'FATIA DE PIZZA' (U + 1F355)

Ok, concordo que o argumento da indexação escalar não é forte. O aspecto mais importante é como isso afeta os implementadores de getindex (dos quais existem muitos) em geral. A orientação é que você não deve implementar getindex , em vez de deixá-lo voltar ao padrão que constrói uma visualização, e implementar copy(::View{MyType}) vez disso?

Fazer um tipo de visão preguiçosa genérica que pode ser o tipo de resultado para fatiar recipientes gerais não parece tão difícil para mim.

Conseguimos, e foi _foi_ difícil. @timholy Lembro que o maior problema era a indexação linear; Isso está certo? Embora seja verdade que os casos especiais para arrays densos são "apenas otimizações", já sabemos que não há implementação eficiente, por exemplo, para arrays esparsos no horizonte. E que tal algo como DataFrames?

Eu diria que DataFrames são difíceis de usar como um argumento claro para ambos os lados: indexar em um DataFrames pandas geralmente produz uma visualização; indexar em um R data.frame sempre produz uma cópia copy-on-write.

Implementar a indexação linear não é difícil - implementá-la _eficientemente_ é difícil. Mas a indexação linear só é eficiente para algumas estruturas de dados de qualquer maneira (arrays que por acaso são armazenados contiguamente) - e gostaríamos de abandoná-la em geral. Se você implementar a indexação escalar, implementar uma visão V = A[I,J] é apenas uma questão de se agarrar a A , I e J e definir V[i,j] = A[I[i],J[j]] . Se houver uma maneira mais eficiente de fazer isso, você poderá sobrecarregar a indexação não escalar, mas, em geral, acho que isso deveria funcionar _menos_ para a maioria dos implementadores de contêiner. Observe que a composição das visualizações pode ser genericamente eficiente: V[I,J][K,L] = V[I[K],J[L]] . Visto que fatiar uma faixa com uma faixa produz uma faixa, isso funciona muito bem.

Adendo ao meu último comentário: embora o caso de indexação escalar não funcione realmente como um argumento de "consistência semântica", ele funciona melhor como um argumento de "generalidade futura". Embora não tenhamos ou desejemos isso agora, se algum dia quiséssemos a capacidade de retornar um ponteiro para um único elemento de array, a sintaxe de visão especial já oferece isso.

@johnmyleswhite Posso obter uma resposta mais opinativa --- qual você prefere? :)

Bem jogado, @JeffBezanson. :)

Para clareza de nível pedante, devo salientar que não me importo particularmente com qual dessas duas opções acaba sendo escolhida para meu próprio código, já que quase sempre uso sub agora.

Como eu disse antes, o que realmente me preocupa sobre esse tópico é a reversão de um plano de longa data de mudar para visualizações como a semântica padrão para fatiar - uma que atraiu fortemente algumas pessoas (por exemplo, alguns dos desenvolvedores do Torch). A inversão me parece particularmente estranha, dada a complexidade do assunto e a ausência de uma resposta certa e clara: ninguém provou que as cópias foram a decisão certa o tempo todo, apenas ficou claro que nem as cópias nem as visualizações dominam estritamente um ao outro.

Com relação aos DataFrames, cheguei a pensar que fornecer qualquer forma de indexação é um erro: você realmente precisa de um nível mais alto de abstração como o fornecido pelo dplyr de R.

Eu não acho que uma reversão aconteceu; Eu posso muito bem perder este. Acontece que o problema não foi totalmente discutido --- lendo # 3701 novamente, a discussão era principalmente sobre desempenho, e demorou muito para que o problema de semântica e o problema de desempenho fossem separados de forma clara. Acho que o que precisamos agora acima de tudo é terminar # 9150, e se este debate terminar em impasse, siga o plano original.

@johnmyleswhite Concordo que as visualizações realmente A@[] para conseguir exatamente isso? Se eles não gostam de fazer cópias, podem simplesmente evitar a sintaxe A[] .

Eu pessoalmente preferiria que A[] tivesse uma semântica de cópia, onde a implementação pode escolher usar cópia na gravação em alguns casos por motivos de desempenho. A semântica da visualização é então garantida com a sintaxe A@[] .

@timholy , eu concordo, seria bom poder usar <> mas há alguns problemas, por exemplo, a indexação lógica seria complicada com <> também:

A[x.<1]
A<x.<1>

Provavelmente existem muitos casos extremos / regras extras para fazer <> funcionar.

A[...] é uma visualização e copy(A[...]) é uma fatia sem visualização

Eu gosto disso porque sempre que alguém se preocupa em obter uma visualização ou uma cópia por motivos semânticos (em oposição ao desempenho), normalmente não estará no meio de uma expressão, então a verbosidade extra de copy() parece muito mais são do que a magia extra de @[] para mim.

@ lucasb-eyer Acho que o problema é que o conceito de vista nem sempre faz sentido. Portanto, A[...] teria um significado conceitual diferente para diferentes tipos de A . Alguém me corrija se eu entendi mal.

Além disso, existe a preocupação de que mudar para visualizações por padrão introduziria uma série de bugs sutis no ecossistema existente.

A propósito, ainda sou a favor de A@[...] .

Conseguimos e foi difícil. @timholy Lembro que o maior problema era a indexação linear; Isso está certo?

A indexação linear foi responsável por 2 dos 3 desafios principais, mas também é justo dizer que sub vs slice responsável por 2 dos 3 desafios:

  • _inferir_ se um SubArray tem indexação linear eficiente é o problema mais difícil. Isso é mais difícil no caso de quando você está construindo uma visão de uma visão, porque com sub convertemos índices inteiros em intervalos. Isso é relevante porque sub(A, 3, :) pode ser inferido como tendo indexação linear eficiente, mas sub(A, 3:3, :) não (mude para sub(A, 3:4, :) e você interromperá a indexação linear), ainda que internamente durante o armazenamento nós efetivamente convertemos 3-> 3: 3. Apesar dessa conversão, conseguimos fazer isso:
julia> A = rand(5,5);

julia> B = sub(A, 3, :);

julia> Base.linearindexing(B)
Base.LinearFast()

julia> C = sub(B, :, 2:4);

julia> Base.linearindexing(C)
Base.LinearFast()

e também este:

julia> A = rand(5,5,5);

julia> B = sub(A, :, 2:3, 2:3);

julia> Base.linearindexing(B)
Base.LinearSlow()

julia> C = sub(B, :, :, 1);

julia> Base.linearindexing(C)
Base.LinearFast()

porque somos simplesmente incríveis. (O truque se resume ao parâmetro LD final e ao raciocínio sobre como você deve ter obtido o que quer que fosse para o SubArray pai.) Se eu fizesse novamente, evitaria tentar ser incrível e preserve as dimensões "não fatiadas" com um novo tipo de índice, NoSlice(3) , que torna a inferência direta.

  • construção ou indexação com dimensionalidade inferior:
A = rand(5,5,5)
B = sub(A, :, 3:17)  # constructs a 2d view from a 3d array

ou

A = rand(5,5,5)
B = sub(A, 2:4, 2:4, 2:4)
B[1, 7]

Se nos livrarmos do último e substituirmos o anterior por uma composição de dois tipos de visualização, SubArray{ReshapedArray{Array}} , as coisas ficarão mais limpas (mas possivelmente com desempenho inferior).

  • Até termos gerado funções, eu não via uma maneira de sobrecarga zero para lidar com esse problema:
S1 = slice(A, :, 5, 2:6)
S2 = slice(A, 5, :, 2:6)
S1[i,j] -> A[i,5,(2:6)[j]]
S2[i,j] -> A[5,i,(2:6)[j]]

Agora que estou mais acostumado com estilos de codificação "lisp-y", suspeito que poderemos dispensar as funções geradas se os vários problemas de desempenho de splatting (# 13359) puderem ser resolvidos.

@ mauro3 , bom ponto.

Acho que o problema é que o conceito de vista nem sempre faz sentido. Portanto, A teria um significado conceitual diferente para diferentes tipos de A. Alguém me corrija se eu não entendi isso.

@Evizero você poderia explicar o que você quis dizer com isso? A já tem diferentes significados conceituais para diferentes tipos de A.

  1. Eu não aceito o argumento da indexação escalar. Isso porque a maior diferença permanece em ambos os sentidos: fatias não escalares - por exemplo, A [1 ,:] ev [1: 5] - recipientes de retorno; fatias escalares - por exemplo, A [1,2] e v [1] - retornam valores únicos. Em comparação com esta diferença semântica ENORME, a diferença entre uma fatia não escalar sendo uma visualização e uma fatia escalar sendo uma não visualização parece trivial. Como já precisamos entender essa diferença, não vejo como esse argumento seja convincente.

Eu concordo com @StefanKarpinski e @ lucasb-eyer aqui. Sim, copy(A[...]) é mais detalhado, mas é muito mais legível, intuitivo e explícito. A@[...] é provavelmente impenetrável para pessoas que vêm de Python, Matlab, C, etc. para Julia

O aspecto mais importante é como isso afeta os implementadores de getindex , em geral.

E se apenas mudássemos A[…] para diminuir para slice vez de getindex e mantivéssemos a semântica de getindex como está atualmente? Então copy(::SubArray) poderia ser escrito assim:

copy(S::SubArray) = getindex(S.parent, S.indexes...)

Implementadores de getindex ainda precisam apenas lidar com o caso escalar. A biblioteca base ainda fornece (cópia) fallbacks não escalares para getindex. Se houver uma otimização disponível para eles, eles podem se especializar getindex (como fazem atualmente). Ou se eles podem implementar um tipo de visão mais eficiente, eles podem especializar slice . Isso também significa que copy(A[…]) é efetivamente igual ao bom e velho getindex(A, …) . É apenas transferir o privilégio de sintaxe para visualizações, sem perda de funcionalidade.

(Isso não funciona exatamente como está, já que slice(A, 1, 1) é uma visualização 0-dimensional agora, mas isso ou o nome do getindex de alocação pode mudar.)

você poderia explicar o que você quis dizer com isso? A já tem diferentes significados conceituais para diferentes tipos de A

Eu acho que meu ponto é que seria estranho se A [1 ,:] retornasse uma visualização para matrizes densas e uma cópia para matrizes esparsas. Em outras palavras, para alguns tipos, a manipulação do valor de retorno mudaria de fato o objeto original, enquanto para outros tipos não.

Mas por que A[1,:] definido para matrizes esparsas? Parece-me que isso só está definido para cumprir a interface AbstractArray (seja ela qual for). É claro que os tipos que não têm os dados de uma fatia diretamente disponíveis têm que calculá-la em tempo real e, portanto, criar uma cópia naturalmente.

Eu também me preocupo com isso, mas @StefanKarpinski abordou isso propondo a implementação de um fallback getindex que sempre retorna um tipo de visualização genérico. Este tipo simplesmente faria indexação indireta, ou seja,

getindex(v::View, i) = v.a[v.idx[i]]

e, portanto, funciona para qualquer coisa que tenha indexação escalar. Pode ser lento às vezes, mas isso é melhor do que ter uma cópia de indexação (e, portanto, ser lento) no caso comum, e melhor do que introduzir uma sintaxe estranha (tentando fazer o caso de Stefan para ele aqui :)).

Já que estamos trocando de papéis aqui, devo apontar que soletrar fatias de cópia como copy(A[...]) tem a desvantagem de, pelo menos semanticamente e na ausência de otimizações inteligentes, significar que você terá que pagar para criar a visualização objeto _e_ então copiá-lo. Esse custo adicional poderia ser otimizado (especialmente plausível se conseguirmos obter a alocação de pilha de objetos que contêm referências a outros objetos), mas sem trabalho de otimização, isso seria mais lento do que nossas fatias de cópia atuais.

Devo salientar que soletrar fatias de cópia como cópia (A [...]) tem a desvantagem de, pelo menos semanticamente e na ausência de otimizações inteligentes, significar que você tem que pagar para criar o objeto de visualização e, em seguida, copiá-lo .

Talvez isso pudesse ser resolvido da mesma forma que atualmente temos sub com copy(A, ...) ?

Talvez isso pudesse ser resolvido da mesma forma que temos atualmente sub com cópia (A, ...)?
: +1:

Talvez isso pudesse ser resolvido da mesma forma que atualmente temos sub com copy(A, ...) ?

Isso teria o mesmo problema que sub tem agora, ou seja, você não pode usar end como uma expressão de indexação.

copy(A[...]) tem muito apelo para a versão de cópia: ela lê de forma totalmente direta, não esconde duas operações dentro de uma e se parece com copy(foo(A)) para qualquer outra operação foo() .

Agora, para uma sugestão um tanto maluca como um paliativo na ausência de otimizações suficientemente inteligentes: e se copy(A[...]) fosse apenas açúcar sintático para uma cópia combinada e getindex? Eu sei que as pessoas ficarão felizes em se livrar de coisas como Ac_mul_B , mas essas também serviram como um substituto útil até que algo melhor aparecesse.

copy(A[...]) realmente um problema? Se a construção do subarray for barata, a operação combinada terá apenas uma pequena sobrecarga.

@ c42f Acho que para o açúcar sintático deveria ser possível implementar uma macro @copy que faça isso. Fazer copy(A[...]) açúcar sintático exigiria mudanças no analisador.
@tknopp Eu também estive me perguntando isso. O custo de cópia deve tornar o custo de criação do subarray insignificante.

E se A for de um tipo que não se presta a visualizações (ou seja, A[...] tem uma cópia para A qualquer maneira). copy(A[...]) então executa duas cópias? Parece uma fonte sutil de problemas de desempenho.

Podemos obter um caso de uso em que a cópia seja desejada? No meu ponto de vista, devemos nos concentrar na maioria dos casos de uso, em vez de olhar para os casos extremos que podem tornar [...] mais genérico. Eu realmente não compro o argumento SparseMatrix já que [...] realmente não deveria ser fornecido para este tipo.

Sobre o argumento sutil do bug: Há pessoas que pensam que o seguinte é surpreendente:

julia> a = [1,2,3];
julia> b = a;
julia> b[1] = 0;
julia> a
3-element Array{Int64,1}:
 0
 2
 3

Parece ser muito semelhante a uma matriz que retorna uma visão para mim.

A razão para favorecer as exibições de array é que ele minimiza o número de alocações de memória ocultas. É verdade que em alguns casos (código multithread) pode ser mais rápido criar cópias para melhor utilizar os caches. No entanto, este caso de uso é melhor programado explicitamente, pois requer a troca de memória para um melhor rendimento.

@tknopp Você está propondo que A[:, 1:2] simplesmente não deveria funcionar para SparseMatrices? Isso parece muito estranho.

Então você acha que scipy é bem estranho?

Na verdade, não me importo se esses métodos são definidos para matrizes esparsas. Mas tomar esses métodos como um argumento para não fazer visualizações de array não é tão convincente para mim. Ou você tem um caso de uso em que os métodos são usados?

Para o scipy, depende do formato da sua matriz. Para CSC você pode fazer o fatiamento, mas apenas com um tamanho de passo de 1.

Então você acha que scipy é bem estranho?

IMHO python não é um bom modelo quando se trata de sintaxe para álgebra linear

Mas tomar esses métodos como um argumento para não fazer visualizações de array não é tão convincente para mim.

Não acho que _qualquer pessoa_ seja contra a visualização de matrizes. A questão é como eles deveriam ser.

O que quero dizer é que A[:, 1:2] é um método de conveniência para matrizes esparsas. E eu queria saber se as pessoas acham que este é um caso de uso muito importante que deve conduzir a discussão de sintaxe ou não. Aprendemos com este tópico que podemos ser mais genéricos ao copiar [...] mas isso por si só IMHO não é suficiente para justificar uma sintaxe especial. Também é importante que a sintaxe seja o que as pessoas realmente desejam na maioria das situações. Acho que é uma visão, mas essa é minha preferência pessoal e, portanto, acho que isso deve ser discutido aqui.

Suspeito que o SciPy funcione dessa forma precisamente porque a indexação retorna visualizações. Você nunca iria querer usar uma visualização de matriz esparsa ineficiente, então você não pode tê-la. Mas ainda me parece muito bizarro se você pode fazer A[1, 2] mas não A[:, 1:2] . Você ainda precisaria de alguma maneira de expressar a última operação, e não pode ser copy(...) se você não puder construir uma visualização. Também parece um desastre total se você deseja escrever um código que pode operar em matrizes densas e esparsas.

Um tema deste tópico é que existem dois componentes para "o que as pessoas realmente querem": a semântica que elas desejam e o que é rápido agora. Eu prefiro copiar semântica, e quando eu uso sub é geralmente para desempenho e não porque eu realmente quero ver semântica. Acho que a abordagem ideal pode ser visualizações copy-on-write como @JeffBezanson proposto em https://github.com/JuliaLang/julia/issues/13157#issuecomment -180506892.

Suspeito que o SciPy funcione dessa forma precisamente porque a indexação retorna visualizações. Você nunca iria querer usar uma visualização de matriz esparsa ineficiente, então você não pode tê-la. Mas ainda me parece muito bizarro se você pode fazer A [1, 2], mas não A [:, 1: 2]. Você ainda precisaria de alguma forma de expressar a última operação, e ela não pode ser copiada (...) se você não puder construir uma visualização. Também parece um desastre total se você deseja escrever um código que pode operar em matrizes densas e esparsas.

  • sim, a indexação escalar não faz muito sentido para matrizes esparsas. Portanto, isso deve ser tratado de forma semelhante
  • Se você quiser escrever código genérico que opere em matrizes esparsas e densas, você usaria a indexação de intervalo? É claro que entendi seu argumento, mas seria interessante se isso fosse algo que as pessoas estivessem usando dessa forma ou se fosse um caso de uso hipotético.

Um tema deste tópico é que existem dois componentes para "o que as pessoas realmente querem": a semântica que elas desejam e o que é rápido agora. Eu prefiro copiar semântica, e quando eu uso sub é geralmente para desempenho e não porque eu realmente quero visualizar semântica. Acho que a abordagem ideal pode ser visualizações copy-on-write como @JeffBezanson proposto em # 13157 (comentário).

Não tenho certeza se é tão simples separar a semântica do desempenho. No meu ponto de vista, [...] deve ser o mais leve possível e deve evitar qualquer alocação de heap.

Com relação ao copy-on-write: Na verdade, fiquei surpreso ao ver essa sugestão de @JeffBezanson, já que complicaria todo o sistema de array. Com o multithreading em andamento, não é óbvio para mim como isso poderia ser implementado de maneira eficiente.

Cópia WRT na gravação, você pode fazer o kernel fazer isso, mas apenas no OS X AFAICT e provavelmente ainda teria muitos dos custos associados à alocação de heap. Se não usarmos o kernel, parece que você pode precisar fazer algumas coisas sofisticadas para tornar a cópia na gravação eficiente para loops que leem e depois gravam em um array, mas se é mais fácil tornar o compilador mais inteligente do que tornar os usuários mais espertos , Pode valer a pena.

Threading já é meio complicado com a semântica do array que temos agora, porque é possível mudar o ponteiro do array usando push! etc. e então alguns threads podem estar acessando o buffer errado. Não tenho certeza se / como isso é tratado atualmente, mas parece um problema semelhante.

Se a cópia na gravação não estiver na mesa, a questão é essencialmente se devemos construir nossa semântica de linguagem em torno do que produz o melhor desempenho para o caso mais comum (matrizes densas) ou se aceitaremos uma penalidade de desempenho para código não otimizado em o interesse da generalidade. A principal advertência para a escolha do "melhor desempenho" é que isso não ajuda muito. Fazer [...] retornar visualizações não o livrará de pensar sobre como está alocando memória, a menos que esteja usando apenas operações escalares, uma vez que a adição, multiplicação, etc. de matrizes ainda alocará a saída. Portanto, você ainda terá que otimizar manualmente sua alocação de memória quando se preocupa com o desempenho, e reutilizar a memória em todos os outros lugares que você pode alocar costuma ser muito mais doloroso do que mudar a indexação de sub [...] para sub . Dado esse fato, bem como o fato de que alterar [...] para retornar visualizações quebrará uma grande proporção do código Julia existente, realmente não acho que seja uma boa ideia.

Lembre-se de que podemos fazer visualizações de arrays esparsos a um custo relativamente baixo, simplesmente indexando por meio dos objetos de fatia: isto é, indexar em S[1, 3:25] requer apenas um número inteiro adicional adicionado por acesso de elemento. Se a fatia for um vetor, precisamos nos agarrar a esse vetor, mas não vejo isso como um impedimento - ele pode ser construído em tempo constante e é apenas um pouco lento para trabalhar.

É verdade que se você estiver fazendo indexação escalar em uma visão de uma matriz esparsa, a sobrecarga será mínima em comparação com o custo da indexação escalar para começar, porque a indexação escalar de matrizes esparsas não é muito eficiente. Mas, na ausência de métodos / algoritmos especializados, muitas coisas que escalam apenas com nnz para uma MatrizSparse escalariam com o número total de elementos (zeros e não zeros). Dado que matrizes esparsas são esparsas, isso parece muito ruim.

SparseMatrices são o pior caso, mas basicamente para qualquer subtipo de AbstractMatrix que não seja StridedMatrix, bem como StridedMatrices com passo não unitário entre elementos adjacentes, provavelmente seria mais rápido alocar uma nova matriz antes de tentar fazer a multiplicação da matriz. As visualizações só podem ser usadas diretamente para tipos onde não há maneira mais eficiente de fazer nada do que realizar a indexação escalar, ou para o subconjunto de casos em que é possível escrever algoritmos eficientes para operar em (potencialmente apenas certos tipos de) visualizações e alguém implementou esses algoritmos.

A opção menos ruim para visualizações de outros tipos é provavelmente copiá-los antes de realizar a multiplicação de matrizes, etc. Isso ainda é mais caro do que copiar na indexação ou copiar na gravação se você for usá-los mais de uma vez, mas pelo menos a complexidade assintótica não deve mudar.

É por isso que não tenho certeza se vale a pena ficar muito preocupado com as questões de desempenho sobre exibições de array esparsas; sim, os algoritmos ingênuos são penalizados com uma visão, mas é bastante simples corrigi-los. Presumivelmente, alguns algoritmos poderiam ser reescritos usando

for M in (SparseMatrixCSC, SubArray{,...,SparseMatrixCSC, ...})
    <strong i="6">@eval</strong> begin
        ....
    end
end

A ideia de otimizar visualizações de BitArrays, por outro lado, é positivamente assustadora para mim.

Desculpe interromper aqui como um usuário puro que está ansiosamente assistindo este debate:

A sugestão de fazer slice _copy on write_ é a melhor notícia que já ouvi sobre Julia em muito tempo. Eu mesmo, e virtualmente todos os meus colaboradores em nossos códigos Julia, ficaremos emocionados se a notação de fatiamento padrão A[...] se comportar semanticamente como uma cópia.

PS: Também gosto da ideia de ter uma notação simples, como A@[...] para visualizações.

@cortner : Você poderia apoiar esta opinião, por que você ficaria emocionado? Quero dizer: atualmente é sempre uma cópia, então por que você está emocionado?

@tknopp : desculpe se não fui claro. Na verdade, não me importo particularmente se A[2:5, 6:12] retorna uma cópia ou uma matriz abstrata de cópia na gravação, desde que se comporte como uma cópia. Até certo ponto, pode ser apenas preferência pessoal, mas no final, este debate não é essencialmente sobre a melhor opção para o maior número de usuários, ou seja, preferências pessoais? Para mim, pessoalmente, a abordagem de visão que o Python adota é algo que me irrita toda vez que escrevo código Python, então eu temia o dia em que Julia mudou para esse comportamento em 0,5.

Eu acredito fortemente que o "comportamento padrão" deve ser o mais elementar e seguro possível. Nesta questão em particular, eu apenas acredito que esse comportamento de estilo de cópia (seja copiar ou copiar na gravação) consegue isso.

E para ter certeza: me irritaria imensamente ter que escrever copy(A[2:5, 6:12]) assim como me incomoda ter que escrever slice(A, 2:5, 6:12) a no momento se eu quiser descartar as dimensões do singleton. (Isso é apenas para provar que eu não sou _ apenas_ um evangélico Matlab.)

Talvez um último ponto a acrescentar a isso: embora eu não tenha seguido inteiramente as dificuldades com matrizes esparsas, acho que é _paramount_ que podemos dividir matrizes esparsas e obter o mesmo comportamento que para matrizes densas. Para um código de produção, onde você pode ajustar e ajustar isso pode não importar, mas Julia quer ser útil para códigos de protótipos sujos. Aqui, é extremamente útil ter esse comportamento.

Alguém sabe de um artigo ou discussão de desenvolvedores Matlab ou NumPy sobre sua decisão de copiar por padrão e visualizar por padrão? Não acredito que esta seja a primeira vez na história das linguagens de programação que isso é discutido, e aqueles que já passaram por isso podem levantar pontos relevantes.

As visualizações no NumPy remontam ao Numérico 1. Você pode ver alguma discussão em torno do PEP209 retirado de 2001 para um redesenho proposto, em que citam o desempenho. Na lista de e-mails, eles discutem a composição e sintaxe :

Sim, o desempenho foi o principal motivo. Mas há outro: se
o fatiamento retorna uma visão, você pode fazer uma cópia com base nela, mas se
o fatiamento retorna uma cópia, não há como fazer uma visualização. Então, se você
mudar isso, você deve fornecer alguma outra maneira de gerar uma visualização, e
por favor, mantenha a sintaxe simples (há muitos casos práticos onde um
visualização é necessária).

Soa familiar. Sua escolha também pode ter sido influenciada pelo suporte para a abstração de lista de listas para matrizes multidimensionais; A[0] é uma referência à primeira linha de uma matriz.

Curiosamente, o Matlab só parece usar o compartilhamento copy-on-write para a sintaxe A(:) e remodelagem. Todas as outras sintaxes de indexação são cópias imediatas (testado em 2014a). Eu ficaria muito surpreso se houvesse alguma discussão pública sobre isso ... mas as visualizações seriam um grande desvio da semântica de cópia na gravação para ligações.

Como um usuário Julia, aqui estão algumas das minhas preocupações sobre como tornar o array views o padrão:

  • A mudança pode quebrar os pacotes existentes e o código do usuário de uma forma sutil, sem um período de depreciação ou uma forma de avisar automaticamente os usuários (visualizações somente leitura atenuam isso um pouco, mas não totalmente). Isso pode ter consequências catastróficas para programadores Julia desavisados ​​ou pessoas normais cujas vidas são afetadas por software escrito em Julia.
  • O suporte para a mudança gira em torno do desempenho, mas melhorias de desempenho semelhantes podem ser alcançadas sem alterar a semântica [] (por meio de melhorias do compilador).
  • Se as visualizações foram feitas o padrão para a indexação do estilo A [:, 1], eles também deveriam ser o padrão para a indexação do estilo A [bool_vec, 2]? No NumPy, a indexação com um vetor booleano ou vetor de índice produz uma cópia (presumivelmente por motivos de desempenho), enquanto a indexação com um intervalo produz uma visualização. Isso confunde os usuários, torna a semântica [] complexa e introduz uma fonte de bugs sutis.
  • Se as visualizações fossem escolhidas como o padrão para vetor booleano e indexação de vetor de índice, que efeito isso teria no desempenho do código existente (e futuro) que usa esses tipos de indexação? Além de ter que armazenar o vetor de índice durante o tempo de vida da visualização, essas visualizações presumivelmente exigiriam duas verificações de limite para cada acesso (uma para o vetor de índice, depois uma para a matriz subjacente).
  • O que acontecerá com uma visualização se a forma / tamanho da matriz subjacente mudar? A visão deve ser invalidada imediatamente, com os acessos futuros gerando um erro? Ou o acesso à visão só deve gerar um erro se o índice traduzido estiver fora do array subjacente? Ambas as abordagens provavelmente introduziriam sobrecarga de desempenho e complexidade que também afetaria 99% dos casos em que a matriz subjacente nunca muda de tamanho.
  • Para arrays contendo objetos imutáveis ​​(o caso mais comum), A [1, 1] retorna uma cópia do escalar. Para mim, pareceria inconsistente se a construção visualmente semelhante A [1, 1: 2] retornasse uma visão em vez disso.

@annalam : obrigado por esta ótima lista.

A mudança quebra quantidades significativas de pacotes existentes e código de usuário de uma forma sutil, sem um período de depreciação ou qualquer forma de avisar os usuários automaticamente.

Você poderia indicar em que baseou seu julgamento de que a mudança quebra quantidades significativas de pacotes existentes de uma forma sutil? Este é um pedido honesto. Por favor, forneça links para os pacotes importantes da Julia que quebram e onde eles quebram.

O que acontece a uma vista se a forma / tamanho da matriz subjacente muda

Matrizes com mais de uma dimensão não podem ser redimensionadas. Vector pode ser redimensionado, então alguns limites de verificação de ineficiência podem se infiltrar lá.

@tknopp : Você está absolutamente certo. Eu não deveria ter escrito "quantidades significativas de", isso era injustificado. Eu editei a frase para dizer "pode ​​quebrar os pacotes existentes e o código do usuário" e adicionei uma frase apontando que um período de visualizações somente leitura pode mitigar esse problema um pouco (embora não totalmente).

Meu humilde pedido a todos aqui é que não pesem mais prós e contras. Acho que quase tudo está coberto aqui, e o que realmente precisamos é de um conjunto de códigos de usuário que sirvam como referência para ajudar a tomar esta decisão mais informada.

Fico feliz em criar um repositório e gerenciá-lo, se todos que participaram aqui estiverem dispostos a enviar alguns códigos autocontidos e casos de teste, que possamos avaliar.

Estou feliz por criar um repo e gerenciá-lo

Por favor, faça @ViralBShah. Acho que algum código real ajudará muito bem a discussão. Coisas que eu gostaria de ver:

  • Com que frequência os arrays são lidos e escritos?
  • Com que freqüência as cópias são necessárias?
  • É fácil detectar erros quando os dados são substituídos silenciosamente devido a uma cópia esquecida?
  • Quais são as verdadeiras diferenças de velocidade entre visualizar / copiar / copiar na gravação?

Acho que com alguns exemplos "reais", alguns deles podem se tornar mais óbvios.

Esta é uma ótima sugestão Viral. Posso contribuir com um caso de uso em que o comportamento de cópia é enganoso. Devemos resumir todos os subtle bugs lá.

Em relação ao COW, existe alguma implementação que _não_ retarde o acesso? Estou pensando nisso:

type COWArray{T,N,A<:AbstractArray} <: AbstractArray
    iscopy::Bool
    parent::A
end

function setindex!(A::COWArray, val, indexes...)
    if !A.iscopy
        A.parent = copy(A.parent)
        A.iscopy = true
    end
    A.parent[indexes...] = val
end

Agora, _talvez_ previsão de ramificação significará que a verificação não diminui o acesso não-SIMD, mas testar isso seria a primeira prioridade.

COW pode não ser tão ruim no Matlab porque a linguagem não foi inicialmente projetada para padrões de acesso elemento por elemento --- se a única maneira razoável de usar arrays for por meio de sintaxe vetorial, você efetivamente suspende essa verificação COW.

@timholy Se pudermos estipular que só nos importamos com a sobrecarga em loops, acho que o desenrolamento de capangas pode funcionar. A cópia aconteceria na primeira iteração desenrolada e a verificação poderia ser eliminada do loop principal (com o TBAA apropriado). Isso só ajuda em loops com padrões de ramificação simples, mas esses são provavelmente os únicos loops em que a sobrecarga importaria.

Acho que isso ainda não tem a mesma semântica da cópia, pois você verá mutações do array original

observe que se escolhermos copiar _semânticas_, então podemos talvez fazer truques onde copiamos pequenos arrays e usamos o sistema de memória virtual para arrays grandes o suficiente para serem alinhados por página. Não estou convencido de que seria muito mais rápido, mas pelo menos você pode usar carregamentos / armazenamentos normais no código compilado.

@carnaval : Em uma implementação COW apropriada, o array "mãe" precisaria de um backlink para todos os seus filhos e iniciaria uma cópia se ele mudar. IMHO muito complexo e ainda mais complexo (-> lento) quando se considera o multi-threading.

Eu gostaria de fornecer um exemplo concreto para as pessoas considerarem. Suponha que eu queira implementar LU completamente pivotado, um algoritmo que você verá em livros descritos dessa forma (Golub e Van Loan 4 / e, p. 132, Algoritmo 3.4.3):

screenshot 2016-02-23 11 17 40

Este algoritmo específico é bastante rico em comportamentos de indexação e fracionamento.

Um usuário ingênuo gostaria de escrever para este algoritmo algo como

function lucompletepiv!(A)
  n=size(A, 1)
  rowpiv=zeros(Int, n-1)
  colpiv=zeros(Int, n-1)
  for k=1:n-1
    As = abs(A[k:n, k:n])
    μ, λ = ind2sub(size(As), indmax(As))
    μ += k-1; λ += k-1
    rowpiv[k] = μ
    A[[k,μ], 1:n] = A[[μ,k], 1:n]
    colpiv[k] = λ
    A[1:n, [k,λ]] = A[1:n, [λ,k]]
    if A[k,k] ≠ 0
      ρ = k+1:n
      A[ρ, k] = A[ρ, k]/A[k, k]
      A[ρ, ρ] = A[ρ, ρ] - A[ρ, k] * A[k, ρ]
    end
  end
  return (A, rowpiv, colpiv)
end

mas por causa de todas as cópias que são feitas pelas expressões de indexação, este código Julia em 0.4 é cerca de duas vezes mais lento que o código Python equivalente

import numpy as np

def lucompletepiv(A):
    assert np.size(A, 0) == np.size(A, 1)
    n = np.size(A, 1)
    rowpiv = np.zeros(n-1, dtype=int)
    colpiv = np.zeros(n-1, dtype=int)
    for k in range(n-1):
        Asub = abs(A[k:n, k:n])
        mu, lam = np.unravel_index(np.argmax(Asub), np.shape(Asub))
        mu, lam = mu + k, lam + k
        rowpiv[k] = mu
        A[[k, mu], :n] = A[[mu, k], :n]
        colpiv[k] = lam
        A[:n, [k, lam]] = A[:n, [lam, k]]
        if A[k, k] != 0:
            rho = slice(k+1, n)
            A[rho, k] /= A[k, k]
            A[rho, rho] -= np.dot(np.reshape(A[rho, k], (n - (k + 1), 1)),
                                  np.reshape(A[k, rho], (1, n - (k + 1))))
    return (A, rowpiv, colpiv)

Eu ficaria curioso para ver o quão mais rápido as pessoas podem tornar esse código Julia alterando apenas as operações de indexação.

: +1:

Para efeito de comparação, isso é o que teríamos que escrever em 0,4 para um melhor desempenho:

function lucompletepiv3!(A)
    n = size(A, 1)
    lda = stride(A, 2)
    rowpiv = zeros(Int, n - 1)
    colpiv = zeros(Int, n - 1)
    <strong i="7">@inbounds</strong> begin
        for k = 1:n - 1
            offsetk = (k - 1)*lda
            μ, λ = idxmaxabs2(A, k:n, k:n)
            rowpiv[k] = μ
            swaprows!(A, k, μ)
            colpiv[k] = λ
            swapcols!(A, k, λ)
            if A[k,k] ≠ 0
                ρ = k + 1:n
                scale!(1/A[k + offsetk], sub(A, ρ, k))
                for j in ρ
                    offsetj = (j - 1)*lda
                    Akj = A[k + offsetj]
                    <strong i="8">@simd</strong> for i in ρ
                        A[i + offsetj] -= A[i + offsetk] * Akj
                    end
                end
            end
        end
    end
    return (A, rowpiv, colpiv)
end

Tenho um código aqui com o qual ficaria feliz em contribuir com o repositório de teste. O código é um modelo simplificado de uma parte do solucionador CFD no qual estou trabalhando (calculando o fluxo de Euler para um pedaço da malha). Ele testa o trabalho com um grande número de pequenos arrays.

Uau - os exemplos vieram rápido. Acho que precisamos de muitos mais códigos que estão mais próximos de cargas de trabalho ou aplicativos reais. Talvez os pacotes sejam uma boa fonte.

Acabei de criar este repositório: https://github.com/JuliaLang/IndexingBenchmarks

Se todos aqui que enviaram snippets puderem enviá-los ao repositório acima ou fornecer um PR, assumirei a tarefa de organizar e preparar um arnês para executar as coisas, etc.

Problema de permissões com o repo:

remote: Permission to JuliaLang/IndexingBenchmarks.git denied to JaredCrean2.
fatal: unable to access 'https://github.com/JuliaLang/IndexingBenchmarks.git/': The requested URL returned error: 403

@ JaredCrean2 Você deve conseguir escrever para esse repo agora. Em geral, para pessoas que não têm acesso de commit, eles terão que enviar PRs, mas ficarão felizes em adicionar qualquer pessoa que esteja contribuindo / ajudando lá.

Em vez disso, enviei um PR (não vi seu comentário até agora).

O que é um conjunto mínimo de testes para ser útil? Uma versão julia (0.4) de um algoritmo usando visualizações e outra usando cópias?

Duas caixas de seleção em dois dias. Nesse ritmo, terminaremos em nenhum momento: smile :

Vários exemplos foram postados nesta edição, demonstrando que simplesmente retornar visualizações por padrão é insuficiente para resolver muitos problemas de desempenho - muitos deles seriam mais bem resolvidos pela "devectorização automática". Isso nos leva ao reino do compilador suficientemente inteligente. No entanto, em https://github.com/JuliaLang/julia/pull/6837#issuecomment -213617933 me ocorreu que, desde a fusão de jb/functions , agora temos o maquinário para, pelo menos, representar essas operações em uma maneira eficiente.

Claro, ainda há uma enorme quantidade de magia do compilador que precisa ser feita.

Eu acrescentaria que também não sabemos com que rapidez a abordagem de visualizações por padrão pode ser feita depois que os objetos de visualização podem ser alocados na pilha. Como a capacidade de fazer isso está à vista antes de 1.0, essa decisão ainda pode ser de qualquer maneira - daí a decisão de não decidir ainda.

Quais itens aqui ainda estão no escopo de 0,5?

Acho que encontrei uma maneira de evitar a grande desaceleração nos tempos de teste ao introduzir o tipo Transpose então "Ditch special rebaixamento de Ac_mul_Bt" ainda deve estar no escopo. Trabalhando nisso.

Dos itens "principais", tenho a impressão de que decidimos não "Retornar fatias como visualizações". O que @andreasnoack está trabalhando fechará a maior parte do resto. Parece que a perspectiva de "Criação mais fácil de matrizes imutáveis" é fraca? O "qualquer tipo de índice" provavelmente poderia ser concluído rapidamente.

Estou esperando pelo # 16260, mas sem promessas. No meu branch local (que está bem à frente do que eu empurrei), eu tenho um erro de inferência estranho que estou tendo problemas para rastrear.

Alguém pode apontar para a discussão "retornar fatias como visualizações"? Eu sei que provavelmente foi feito o hash indefinidamente, mas existe pelo menos uma maneira no Base de fazer algo como view(A, i:j) (como fornecido pelo pacote ArrayViews)?

Acho que foi mais aqui, mas o github acaba com as longas discussões agora. Há um botão no meio da exibição de comentários ocultos.

Eu caracterizaria a decisão de "fatias como visualizações" como "ainda não, talvez nunca". No estado atual de nossa tecnologia de compilador, retornar visualizações em vez de cópias é uma lavagem em termos de desempenho: às vezes é melhor, mas frequentemente não é e às vezes é pior. No futuro, quando pudermos alocar objetos na pilha que se referem ao heap, isso pode mudar, momento em que podemos reavaliar a compensação de desempenho. É possível que, com essa capacidade de empilhar e alocar visualizações, retornar as visualizações seja uma vitória de desempenho tal que decidimos fazê-lo, mas também é possível que não seja e decidimos contra. Dada a incerteza e a natureza extremamente (sutil) perturbadora da mudança, parecia melhor esperar por enquanto.

Obrigado pelo resumo @StefanKarpinski; no entanto, temos pelo menos a capacidade de retornar uma visão? Acho que esse seria o recurso mínimo para que, em meu próprio código, eu soubesse que posso fazer view(A, i:j) quando sei que isso é tudo que preciso / desejo para desempenho.

Nós temos isso há anos ; veja sub e slice , que ficou rápido a tempo de julia-0.4. Agora temos alguns tipos de visualização adicionais também (pelo menos ReshapedArray e os não exportados PermutedDimsArray ).

Obrigado @timholy , desculpe pela minha ignorância aqui. :)

O "qualquer tipo de índice" provavelmente poderia ser concluído rapidamente.

Não acho mais que devamos oferecer suporte a qualquer tipo de índice devido à dificuldade em obter despacho para fazê-lo - eu editei esse ponto de marcador. Acho que duas coisas devem acontecer para esse item:

  • Fallbacks de indexação escalar precisam ser restritos a Integer . Isso causará um erro de A[1.0] com uma mensagem de que a indexação com Float64 não é suportada. E a descontinuação em to_index pode então ser removida.
  • Os métodos de indexação não escalar e SubArray podem ser alargados para suportar todos os Number s. Eles só precisam saber realmente que esses índices são escalares e, em seguida, passá-los para os métodos de indexação escalar. Se o array personalizado adicionar suporte para ponto flutuante ou outros índices mais complicados, ele simplesmente funcionará * ⁱˢʰ. Caso contrário, ocorrerá o erro como acima.

Existem duas partes complicadas aqui:

  • O que fazer com to_index ? Agora ele realmente faz apenas duas coisas: chamar find em matrizes lógicas e converter inteiros em Int . Seria estranho que SubArrays e métodos de indexação não escalares passassem todos os escalares intocados, exceto inteiros - eles seriam convertidos em Int primeiro por to_index . Eles provavelmente não deveriam mexer com nenhum escalar. Poderíamos dividir to_index em duas partes: to_nonscalar_index (chama find e permite um gancho para, por exemplo, NullableArrays para otimizar seu uso como índices) e to_scalar_index (apenas converte em Int )? Enquanto estamos refatorando aqui, também queremos permitir que o array que está sendo indexado tenha uma palavra a dizer sobre seus índices, a la https://github.com/JuliaLang/julia/pull/15750/files#diff -a21a7fc275830bf9efb5b7c17c4fb98eR439 ?
  • O sonho aqui é fazer com que isso funcione para matrizes customizadas sofisticadas como Interpolations. Isso nos leva 99% do caminho até lá, mas a parte -ish é que ele terá problemas para fazer indexação não escalar com tipos que os fazem retornar um eltype (como interpolação com números duais). Eles ainda alocarão um array de eltype s para a saída, mas irão encontrar um erro ao tentar atribuir a ele. Por enquanto, está tudo bem e, no futuro, um indivíduo motivado pode possivelmente implementar uma especialização promote_eltype_op por getindex .

Fallbacks de indexação escalar precisam ser restritos a Integer. Isso fará com que A[1.0] cometa um erro com a mensagem de que a indexação com Float64 não é suportada.

O problema é permitir que flutuadores sejam usados ​​para indexação técnica ou filosófica?

Nós temos isso há anos; veja sub e slice, que ficaram rápidos a tempo para julia-0.4. Agora temos alguns tipos de visualização adicionais também (pelo menos ReshapedArray e o PermutedDimsArray não exportado).

Houve alguma consideração de renomear sub para view ? sub realmente não indica muito bem que retornará uma visualização ...

view é usado pelo pacote ArrayViews que atualmente tem vantagens significativas de desempenho em certos casos (pequenas visualizações contíguas).

Seguindo @davidanthoff , acho que devemos descontinuar sub ou slice e provavelmente deve ser sub agora que o comportamento de getindex corresponde a slice .

O problema é permitir que flutuadores sejam usados ​​para indexação técnica ou filosófica?

É uma mistura de ambos, mas se desembaraçarmos to_index escalar / não-escalar como sugeri acima, isso remove (o último dos?) Os motivos técnicos. A indexação linear requer matemática sobre os índices, o que requer números inteiros. Muita coisa mudou desde que mesclamos essa suspensão de uso (https://github.com/JuliaLang/julia/pull/10458).

Ah, eu pensava que a capacidade básica agora é um superconjunto completo das coisas ArrayViews e sempre preferi ... É uma pena que o nome mais intuitivo seja usado no pacote e a base seja deixada com algo menos claro ...

Parece lamentável e um tanto invertido que um nome usado em um pacote bloqueie a escolha de um nome muito mais claro para uma função no Base. Talvez o caminho a seguir seja eliminar a vantagem de desempenho de ArrayViews, descontinuar esse pacote e alterar o nome da função para Base.view.

Minha suposição / esperança é que a diferença de desempenho vai embora quando somos capazes de pilha pilha-alocar recipientes com referências ... e eu sou cético de que há alguma coisa que podemos fazer sobre isso sem isso. O wrapper ArrayView é apenas menor, então ser capaz de embutir e eliminar a criação do wrapper deve resolver.

É por isso que a diferença só aparece com a criação de pequenos arrays --- na maioria dos outros benchmarks, SubArray é igual ou supera ArrayViews .

Ah, e eu concordo em suspender sub .

A base e ArrayViews podem usar view . Afinal, ImageView também usa view :)

Suponho que poderia funcionar porque ArrayViews define métodos apenas para Array mas Base define métodos para AbstractArray (eu acho?). Como isso funcionaria quando existem escopos diferentes:

module A
  function myfunc(a::AbstractMatrix)
     av = view(a, :, 1)
     # do something with av
   end
end

module B 
  using ArrayViews
end

typeof(av) muda dependendo se o módulo B foi carregado (assumindo que a é um Array )?

ArrayViews teria que parar de exportar view , e sempre que você quisesse usar a versão ArrayViews' diria ArrayViews.view(A, :, 1)

ImageView também teria que parar de exportar view , mas estou bem com essa ideia.

Das edições restantes, apenas # 5332 e # 16846 permanecem por 0,5; movendo este problema para 0,6.

Também estou planejando fazer https://github.com/JuliaLang/julia/pull/16260#discussion_r67140179 :

  • transicionalmente (apenas julia-0.5) fazer size e length lançar um erro para matrizes com indexação não convencional
  • introduza @arraysafe para reescrever chamadas para size e length para algo que não gere um erro
  • mesclar allocate_for em similar

Provavelmente não será feito até depois da JuliaCon, infelizmente. Na mesma discussão, @eschnett defendeu a introdução de um tipo separado para indexação linear e apontou que a introdução de linearindices é um momento oportuno para fazê-lo; Eu não discordo, mas não acho que terei tempo para resolver isso sozinho, então esse está à sua disposição.

Contanto que você esteja nisso, @timholy - precisa ser concluído na próxima semana para que possamos marcar um RC. Se você quiser abrir um problema e colocá-lo no marco 0.5.0 para que possamos rastreá-lo, você pode.

O título não deveria ser adaptado, se o marco for movido?

Eu acredito que as 0,6 partes disso se refletem em questões mais específicas e PRs; movendo para 1.0.

Consulte também # 20164 para ativar mais facilmente as visualizações de um grande bloco de código.

Tudo nesta edição está feito, tem seu próprio problema ou não vai acontecer (eu verifiquei).

Esta página foi útil?
0 / 5 - 0 avaliações