Go: matemática / bits: uma biblioteca de twiddling de bits inteiros

Criado em 11 jan. 2017  ·  168Comentários  ·  Fonte: golang/go

Discussões anteriores em https://github.com/golang/go/issues/17373 e https://github.com/golang/go/issues/10757.

Resumo

Esta proposta apresenta um conjunto de API para twiddling de bits inteiros.

Fundo

Esta proposta apresenta um conjunto de API para twiddling de bits inteiros. Para esta proposta, estamos interessados ​​nas seguintes funções:

  • ctz - conta zeros à direita.
  • clz - conta os zeros à esquerda; log_2.
  • popcnt - população de contagem; distância de hamming; paridade inteira.
  • bswap - inverte a ordem dos bytes.

Essas funções foram escolhidas pela pesquisa:

Nós nos limitamos a essas quatro funções porque outros ajustes
truques são muito simples de implementar usando a biblioteca proposta,
ou construções Go já disponíveis.

Encontramos implementações para um subconjunto das funções de torção selecionadas
em muitos pacotes, incluindo tempo de execução, compilador e ferramentas:

| pacote | clz | ctz | popcnt | bswap |
| --- | --- | --- | --- | --- |
| matemática / grande | X | X | | |
| runtime / interno / sys | X | X | | X |
| ferramentas / container / intsets | X | | X | |
| cmd / compilar / interno / ssa | X | | X | |
| code.google.com/p/intmath | X | X | | |
| github.com/hideo55/go-popcount | | | X (asm) | |
| github.com/RoaringBitmap/roaring | | X | X (asm) | |
| github.com/tHinqa/bitset | X | X | X | |
| github.com/willf/bitset | X | | X (asm) | |
| gopl.io/ch2/popcount | | | X | |
| Construções GCC | X | X | X | X |

Muitos outros pacotes implementam um subconjunto dessas funções:

Da mesma forma, os fornecedores de hardware reconheceram a importância
de tais funções e suporte de nível de máquina incluído.
Sem suporte de hardware, essas operações são muito caras.

| arco | clz | ctz | popcnt | bswap |
| --- | --- | --- | --- | --- |
| AMD64 | X | X | X | X |
| ARM | X | X | ? | X |
| ARM64 | X | X | ? | X |
| S390X | X | X | ? | X |

Todas as funções de bit twiddling, exceto popcnt, já são implementadas por runtime / internal / sys e recebem suporte especial do compilador para "ajudar a obter o melhor desempenho". No entanto, o suporte do compilador é limitado ao pacote de tempo de execução e outros usuários do Golang precisam reimplementar a variante mais lenta dessas funções.

Proposta

Apresentamos uma nova biblioteca std math/bits com a seguinte API externa, para fornecer implementações otimizadas de compilador / hardware das funções clz, ctz, popcnt e bswap.

package bits

// SwapBytes16 reverses the order of bytes in a 16-bit integer.
func SwapBytes16(uint16) uint16
// SwapBytes32 reverses the order of bytes in a 32-bit integer.
func SwapBytes32(uint32) uint32
// SwapBytes64 reverses the order of bytes in a 64-bit integer.
func SwapBytes64(uint64) uint64

// TrailingZeros32 counts the number of trailing zeros in a 32-bit integer, and if all are zero, then 32.
func TrailingZeros32(uint32) uint
// TrailingZeros64 counts the number of trailing zeros in a 64-bit integer, and if all are zero, then 64.
func TrailingZeros64(uint64) uint

// LeadingZeros32 counts the number of trailing zeros in a 32-bit integer, and if all are zero, then 32.
func LeadingZeros32(uint32) uint
// LeadingZeros64 counts the number of trailing zeros in a 64-bit integer, and if all are zero, then 64.
func LeadingZeros64(uint64) uint

// Ones32 counts the number of bits set in a 32-bit integer.
func Ones32(uint32) uint
// Ones64 counts the number of bits set in a 64-bit integer.
func Ones64(uint64) uint

Justificativa

As alternativas a esta proposta são:

  • As funções de bit twiddling são implementadas em uma biblioteca externa não suportada pelo compilador. Essa abordagem funciona e é o estado atual das coisas. O tempo de execução usa os métodos suportados pelo compilador, enquanto os usuários do Golang continuam a usar as implementações mais lentas.
  • A biblioteca externa é suportada pelo compilador. Dado que esperamos que esta biblioteca substitua runtime / internal / sys, significa que esta biblioteca deve estar travada com o compilador e viver na biblioteca padrão.

Compatibilidade

Esta proposta não altera ou quebra nenhuma API stdlib existente e está em conformidade com as diretrizes de compatibilidade.

Implementação

SwapBytes, TrailingZeros e LeadingZeros já estão implementados. A única função que falta são as que podem ser implementadas de forma semelhante às outras funções. Se esta proposta for aceita, ela pode ser implementada a tempo para Go1.9.

Questões em aberto (se aplicável)

Os nomes são difíceis, o galpão de bicicletas está nos comentários.

Por favor, sugira funções adicionais a serem incluídas nos comentários. Idealmente, inclua onde tal função é usada em stdlib (por exemplo, matemática / big), ferramentas ou pacotes populares.

Até agora, as seguintes funções foram propostas e estão sendo consideradas:

  • RotateLeft / RotateRight

    • Prós: & 63 não é mais necessário compilar uma rotação com argumento não const para uma única instrução em x86, x86-64.

    • Contras: muito curto / simples de implementar e embutido.

    • Usado: criptografa / usa a rotação constante que é tratada adequadamente pelo compilador.

  • ReverseBits

    • Prós:?

    • Contras:?

    • Usado: ?

  • Adicionar / Sub com retorno de transporte

    • Prós: caro de outra forma

    • Contras:?

    • Usado: matemática / grande

História

14.Jan: Esclarecido a saída de TrailingZeros e LeadingZeros quando o argumento é 0.
14.Jan: Métodos renomeados: CountTrailingZeros -> TrailingZeros, CountLeadingZeros -> LeadingZeros, CountOnes -> Ones.
13.Jan: Nome da arquitetura fixa.
11.Jan: Proposta inicial aberta ao público.

FrozenDueToAge Proposal-Accepted

Comentários muito úteis

Pessoas que escrevem código querem girar para a esquerda ou para a direita. Eles não querem girar para a esquerda ou direita, dependendo. Se fornecermos apenas girar para a esquerda, qualquer pessoa que quiser girar para a direita terá que escrever uma expressão para converter sua rotação para a direita em uma rotação para a esquerda. Este é o tipo de expressão que os computadores podem fazer trivialmente, mas os programadores cometerão erros. Por que fazer os programadores escreverem eles mesmos?

Todos 168 comentários

@brtzsnr talvez você deva enviar este documento para o etapas do processo de propostas ?

Uma vez que já é um markdown seguindo o modelo, deve ser fácil copiar e colar em um CL criando um arquivo design / 18616-bit-twiddling.md (ou qualquer outro).

@cespare de https://github.com/golang/proposal "se o autor quiser escrever um documento de design, ele pode escrever um". Tudo começou como um documento de design, se houver um forte sentimento de que devo enviá-lo, estou totalmente bem.

Eu ficaria bem com isso, é uma funcionalidade bastante comum usada em muitas bibliotecas algorítmicas e matemática / bits parece um lugar apropriado.

(Por exemplo, math / big também implementa nlz (== clz).)

Provavelmente há algum derramamento de bicicletas sobre os nomes. Eu, por exemplo, preferiria que as funções dissessem o que eles retornam ao invés do que eles fazem; o que, por sua vez, pode levar a nomes mais curtos. Por exemplo:

bits.TrailingZeros64(x) vez de bits.CountTrailingZeros64(x)

e assim por diante.

A proposta parece bastante clara e mínima - um doc de design parece um exagero. Acho que um CL seria mais apropriado neste momento.

(Este é um CL com API e implementação básica - para fins de discussão no lugar de um documento de design. Ainda precisamos decidir se esta proposta deve ser aceita ou não.)

@brtzsnr já escreveu o documento de design: está na descrição do problema e segue o modelo . Presumi que havia algum valor em ter todos esses documentos em um único local.

O último arco listado na tabela de suporte de hardware é "BSWAP" - typo?

Obrigado por escrever isso.

A string doc para ctz e clz deve especificar o resultado quando passado 0.

Eu também prefiro (por exemplo) TrailingZeros32 a CountTrailingZeros32. Eu também ficaria feliz com o Ctz32. É conciso, familiar para a maioria e facilmente pesquisável para o resto.

Obrigado pela proposta.
Só quero acrescentar que provavelmente também queremos nos concentrar no uso, em vez de nos concentrar apenas nas instruções. Por exemplo, @ dr2chase uma vez encontrado, existem várias funções log2 no compilador / montador. É perto de CLZ, mas não é o mesmo. Funções como essa provavelmente também deveriam estar no pacote de bit twiddling. E talvez também inclua funções úteis que não estão relacionadas a essas instruções.

Que tal fornecermos um pacote para todas as primitivas de bit twiddling definidas por
a delícia do hacker?

Ao projetar o pacote, não precisamos considerar se a função
pode ser intrinsecado ou não. A otimização pode acontecer mais tarde. Isso é,
não deixe a implementação de baixo nível controlar o pacote de nível superior
interface. Queremos uma boa interface de pacote, mesmo que alguns deles não possam
ser mapeado para uma única instrução.

@minux , felizmente, todas as funções de

Seguir o Hacker's Delight tem a vantagem de não perder tempo discutindo sobre os nomes.

Eu gostaria de adicionar o seguinte:

ReverseBits (para uint32 e uint64)
RotateLeft / Right (pode ser expandido em linha com dois turnos, mas o compilador
nem sempre pode fazer a transformação devido a problemas de intervalo de mudança)

Talvez também duas formas de resultados de adição e substrato? Por exemplo

função AddUint32 (x, y, carryin uint32) (carryout, sum uint32) // carryin must
seja 0 ou 1.
E da mesma forma para uint64.

E SqrtInt.

Muitas das minhas sugestões não podem ser implementadas em uma única instrução, mas
esse é o ponto: queremos um bom pacote de twiddling, não um mero
pacote intrísico. Não deixe o hardware limitar o pacote de alto nível
interface.

Da mesma forma, funções para verificar se uma adição / multiplicação irá estourar.

Um ponto de dados relacionado: vários problemas foram registrados para o cgo mais rápido. Em um exemplo (proposta nº 16051), o fato de que a implementação rápida de bsr / ctz / etc. pode acontecer foi mencionado como, esperançosamente, minando o conjunto de casos de uso em que as pessoas que escrevem vão ficam tentadas a usar cgo.

@aclements comentou em 1 de julho de 2016:

@eloff , houve alguma discussão (embora nenhuma proposta concreta que eu saiba) para adicionar funções para coisas como popcount e bsr que seriam compiladas como intrínsecas onde suportadas (como math.Sqrt é hoje). Com o 1.7, estamos testando isso no tempo de execução, que agora tem um ctz intrínseco disponível no amd64 SSA. Obviamente, isso não resolve o problema geral, mas eliminaria mais uma razão para usar cgo em um contexto de baixa sobrecarga.

Muitas pessoas (inclusive eu) são atraídas para ir por causa da performance, então coisas como esta proposta atual de bit twiddling ajudariam. (E sim, o cgo agora é mais rápido no 1.8, o que também é bom).

RotateLeft / Right (pode ser expandido em linha com dois turnos, mas o compilador
nem sempre pode fazer a transformação devido a problemas de intervalo de mudança)

@minux Você pode explicar qual é o problema?

@brtzsnr : Eu acho que o que o Minux está se referindo é que quando você escreve (x << k) | (x >> (64-k)) , você sabe que está usando 0 <= k < 64 , mas o compilador não pode ler sua mente, e não é obviamente derivável do código. Se tivéssemos a função

func leftRot(x uint64, k uint) uint64 {
   k &= 63
   return (x << k) | (x >> (64-k))
}

Então, podemos ter certeza (por meio do & 63) que o compilador sabe que o intervalo de k é limitado.
Portanto, se o compilador não puder provar que a entrada é limitada, precisamos de um AND extra. Isso é melhor do que não gerar a montagem giratória.

Na sexta-feira, 13 de janeiro de 2017 às 22h37, Keith Randall [email protected]
escreveu:

@brtzsnr https://github.com/brtzsnr : Eu acho que o Minux está se referindo
para é quando você escreve (x << k) | (x >> (64-k)), você sabe que está usando 0
<= k <64, mas o compilador não pode ler sua mente, e não é obviamente
derivável do código. Se tivéssemos a função

função leftRot (x uint64, k uint) uint64 {
k & = 63
return (x << k) | (x >> (64-k))
}

Então, podemos ter certeza (por meio do & 63) que o compilador sabe que o intervalo
de k é limitado.
Então, se o compilador não pode provar que a entrada é limitada, então precisamos de um extra
E. Isso é melhor do que não gerar a montagem giratória.

Direito. Se definirmos as funções RotateLeft e RotateRight, podemos formalmente
definir a função girar k bits para a esquerda / direita (não importa o que seja k). Isto é
semelhante à forma como nossas operações de turnos são definidas. E esta definição também
mapeia para a instrução de rotação real muito bem (ao contrário dos turnos, onde nosso mais
a definição intuitiva requer uma comparação em certas arquiteturas).

E quanto às funções de embaralhamento (e descompressão) de bytes e bits que são usadas pela biblioteca de compactação blosc ? Os slides (o embaralhamento começa no slide 17). Essas funções podem ser aceleradas por SSE2 / AVX2.

Na sexta-feira, 13 de janeiro de 2017 às 23h24, opennota [email protected] escreveu:

E as funções de embaralhamento de bytes e bits que são usadas pelo blosc
https://github.com/Blosc/c-blosc biblioteca de compressão? Os slides
http://www.slideshare.net/PyData/blosc-py-data-2014 (o embaralhamento
começa no slide 17). Essas funções podem ser aceleradas por SSE2 / AVX2.

SIMD é um problema maior e está fora do escopo deste pacote. Isso é

17373.

As funções propostas atualmente têm uma implementação nativa Go muito maior e desproporcionalmente mais cara do que a ideal. Por outro lado, girar é fácil de escrever embutido de uma forma que o compilador possa reconhecer.

@minux e também todos os outros: Você sabe onde girar para a esquerda / direita é usado com um número não constante de bits girados? crypto / sha256 usa rotate, por exemplo, mas com número constante de bits.

rotate é fácil de escrever embutido de uma forma que o compilador possa reconhecer.

É fácil para aqueles que estão familiarizados com os componentes internos do compilador. Colocá-lo em um pacote de matemática / bits torna isso fácil para todos.

Você sabe onde girar para a esquerda / direita é usado com um número não constante de bits girados?

Aqui está um exemplo de # 9337:

https://play.golang.org/p/rmDG7MR5F9

Em cada invocação, é um número constante de bits girados a cada vez, mas a função em si não está embutida no momento, portanto, é compilada sem nenhuma instrução de rotação. Uma função de biblioteca matemática / bits definitivamente ajudaria aqui.

No sábado, 14 de janeiro de 2017 às 5h05, Alexandru Moșoi [email protected]
escreveu:

As funções propostas atualmente têm uma implementação nativa Go muito maior
e desproporcionalmente mais caro do que o ideal. Por outro lado, gire
é fácil de escrever embutido de uma forma que o compilador possa reconhecer.

Como frisei muitas vezes nesta edição, esta não é a maneira correta de
projetar um pacote Go. Ele está muito vinculado ao hardware subjacente. O que nós
want é um bom pacote de twiddling que geralmente é útil. Se
funções podem ser expandidas em uma única instrução é irrelevante, desde que
já que a interface da API é bem conhecida e geralmente útil.

@minux https://github.com/minux e também todos os outros: Você sabe
onde girar para a esquerda / direita é usado com um número não constante de bits girados?
crypto / sha256 usa rotate, por exemplo, mas com número constante de bits.

Mesmo se no problema real o número de bits de rotação for constante, o
o compilador pode não conseguir ver isso. Por exemplo, quando a contagem de turnos é armazenada
em uma matriz, ou esconder no contador de loop ou até mesmo chamador de um não embutido
função.

Um exemplo simples de uso de número variável de rotação é um interessante
implementação de popcount:
// https://play.golang.org/p/ctNRXsBt0z

func RotateRight(x, k uint32) uint32
func Popcount(x uint32) int {
    var v uint32
    for i := v - v; i < 32; i++ {
        v += RotateRight(x, i)
    }
    return int(-int32(v))
}

@josharian O exemplo parece uma má decisão inliner se o podridão não estiver embutido. Você tentou escrever a função como func rot(x, n) vez de rot = func(x, n) ?

@minux : Eu concordo com você. Não estou tentando amarrar a API a um conjunto de instruções específico; o suporte de hardware é um bom bônus. Meu foco principal é encontrar usos no código real (não no código de brinquedo) para entender o contexto, qual é a melhor assinatura e o quão importante é fornecer a função favorita de todos. A promessa de compatibilidade vai nos prejudicar mais tarde, se não fizermos isso corretamente agora.

Por exemplo: Qual deve ser a assinatura de add com retorno de transporte? Add(x, y uint64) (c, s uint64) ? Olhando para math/big , provavelmente precisamos de Add(x, y uintptr) (c, s uintptr) também.

O exemplo parece uma decisão ruim no inliner se o podridão não estiver no inliner.

sim. É parte de um bug que reclama sobre inlining. :)

Você tentou escrever a função como func rot (x, n) em vez de rot = func (x, n)?

Não é meu código - e isso é parte do ponto. E de qualquer maneira, é um código razoável.

Seria bom garantir (na documentação do pacote?) Que as funções de rotação e troca de bytes são operações de tempo constante para que possam ser usadas com segurança em algoritmos de criptografia. Possivelmente algo em que pensar também para outras funções.

Em quinta-feira, 19 de janeiro de 2017 às 11h50, Michael Munday [email protected]
escreveu:

Seria bom garantir (na documentação do pacote?) Que o
as funções de rotação e troca de bytes são operações de tempo constante para que
pode ser usado com segurança em algoritmos de criptografia. Possivelmente algo para pensar
para outras funções também.

implementação trivial de troca de bytes é um tempo constante, mas se o subjacente
arquitetura não fornece instruções de deslocamento variável, será difícil
para garantir a implementação de rotação de tempo constante. Talvez Go nunca vá correr
entretanto, nessas arquiteturas.

Dito isso, também há uma chance não desprezível de que o
microarquitetura usa um shifter multi-ciclo, e não podemos garantir
rotação de tempo constante nessas implementações.

Se o tempo constante estrito for necessário, talvez a única maneira seja escrever o assembly
(e mesmo nesse caso, faz fortes suposições de que todos os usados
as instruções são em si um tempo constante, o que depende implicitamente do
microarquitetura.)

Embora eu entenda a necessidade de tais garantias, mas está realmente além
nosso controle.

Estou inclinado a concordar com @minux. Se você quiser cripto primitivos de tempo constante, eles devem viver em cripto / sutis. cripto / sutil pode redirecionar facilmente para matemática / bits em plataformas onde essas implementações foram verificadas. Eles podem fazer outra coisa se uma implementação mais lenta, mas em tempo constante, for necessária.

Parece que vale a pena fazer isso. Partindo para @griesemer e @ randall77 para o veterinário. A próxima etapa parece um documento de design registrado no lugar usual (vejo o esboço acima).

Agora que esta proposta pode avançar, devemos concordar com os detalhes da API. Perguntas que vejo no momento:

  • quais funções incluir?
  • quais tipos lidar ( uint64 , uint32 apenas, ou uint64 , uint32 , uint16 , uint8 , ou uintptr )?
  • detalhes da assinatura (por exemplo, TrailingZeroesxx(x uintxx) uint , com xx = 64, 32, etc., vs TrailingZeroes(x uint64, size uint) uint onde o tamanho é um de 64, 32, etc. - o último levaria a uma API menor e pode ainda ser rápido o suficiente dependendo da implementação)
  • algum tipo de mudança de nome (gosto da terminologia "Hacker's Delight", mas pode ser mais apropriado soletrá-la)
  • existe um lugar melhor do que matemática / bits para este pacote.

@brtzsnr Você gostaria de continuar liderando esse esforço? Tudo bem se você quiser pegar seu design inicial e iterar sobre ele nesta edição ou se preferir configurar um documento de design dedicado. Como alternativa, posso pegar e empurrar para frente. Gosto de ver isso acontecendo durante a fase 1.9.

Eu prefiro os nomes xx escritos, pessoalmente: bits.TrailingZeroes (uint64 (x), 32) é mais incômodo e menos legível do que bits.TrailingZeroes32 (x), imo, e esse esquema de nomenclatura funcionaria com a plataforma de tamanho variável uintptr mais facilmente (xx = Ptr?).

Também ficaria mais aparente se você, digamos, não incluísse uint8 na primeira versão, mas o adicionasse depois - de repente, haveria um monte de novas funções xx = 8 em vez de um monte de comentários de documentos atualizados que adicionam 8 à lista de tamanhos válidos com uma nota nos documentos de lançamento que é fácil de perder.

Se os nomes soletrados forem usados, acho que os termos do prazer do Hacker devem ser incluídos nas descrições como "também conhecido como" para que as pesquisas (na página ou por meio do mecanismo de pesquisa) encontrem o nome canônico facilmente se você pesquisar a grafia errada .

Acho que matemática / bits é um bom lugar - é matemática com bits.

Devo observar que SwapBytes{16,32,64} é mais consistente com funções em sync/atomic que SwapBytes(..., bitSize uint) .

Embora o pacote strconv use o padrão ParseInt(..., bitSize int) , há mais relação entre bits e atomic do que com strconv .

Três razões pelas quais não gosto de SwapBytes (uint64, size uint):

  1. as pessoas podem usá-lo nos casos em que o tamanho não é constante de tempo de compilação (em
    menos para o compilador atual),
  2. requer muitas conversões de tipo. Primeiro a converter um uint32 para
    uint64 e, em seguida, convertê-lo de volta.
  3. devemos reservar o nome genérico SwapBytes para um futuro genérico
    versão da função (quando Go obtém suporte para genéricos).

@griesemer Robert, por favor,

Parece que há uma preferência clara por assinaturas do formulário:

func fffNN(x uintNN) uint

o que deixa em aberto qual NN apoiar. Vamos nos concentrar em NN = 64 para o propósito de continuar a discussão. Será fácil adicionar os tipos restantes conforme necessário (incl. Uintptr).

O que nos leva de volta à proposta original de @brtzsnr e às seguintes funções (e suas respectivas variações para diferentes tipos), além do que as pessoas sugeriram nesse ínterim:

// LeadingZeros64 returns the number of leading zero bits in x.
// The result is 64 if x == 0.
func LeadingZeros64(x uint64) uint

// TrailingZeros64 returns the number of trailing zero bits in x.
// The result is 64 if x == 0.
func TrailingZeros64(x uint64) uint

// Ones64 returns the number of bits set in x.
func Ones64(x uint64) uint

// RotateLeft64 returns the value of x rotated left by n%64 bits.
func RotateLeft64(x uint64, n uint) uint64

// RotateRight64 returns the value of x rotated right by n%64 bits.
func RotateRight64(x uint64, n uint) uint64

Podemos ter um conjunto de funções de troca também. Pessoalmente, sou cético quanto a isso: 1) Nunca precisei de um SwapBits, e a maioria dos usos de SwapBytes se deve ao código que reconhece o endian quando não deveria. Comentários?

// SwapBits64 reverses the order of the bits in x.
func SwapBits64(x uint64) uint64

// SwapBytes64 reverses the order of the bytes in x.
func SwapBytes64(x uint64) uint64

Então, pode haver um conjunto de operações inteiras. Eles estariam neste pacote apenas porque alguns deles (por exemplo, Log2) podem estar próximos de outras funções neste pacote. Essas funções podem estar em outro lugar. Talvez o nome do pacote bits precise ser ajustado. Comentários?

// Log2 returns the integer binary logarithm of x.
// The result is the integer n for which 2^n <= x < 2^(n+1).
// If x == 0, the result is -1.
func Log2(x uint64) int

// Sqrt returns the integer square root of x.
// The result is the value n such that n^2 <= x < (n+1)^2.
func Sqrt(x uint64) uint64

Finalmente, @minux sugeriu operações como AddUint32 etc. Vou deixá-las de fora por enquanto, porque acho que são mais difíceis de especificar corretamente (e há mais variedade). Podemos voltar a eles mais tarde. Vamos chegar a um consenso sobre o acima.

Perguntas:

  • Alguma opinião sobre os nomes das funções?
  • Alguma opinião sobre as operações inteiras?
  • Alguma opinião sobre o nome / localização do pacote?

Eu prefiro nomes de funções longos. Go evita abreviar nomes de funções. Os comentários podem conter seus apelidos ("nlz", "ntz", etc) para as pessoas que procuram o pacote dessa maneira.

@griesemer , parece que você tem erros de digitação em sua mensagem. Os comentários não correspondem às assinaturas da função.

Eu uso SwapBits em aplicativos de compressão. Dito isso, as principais arquiteturas fornecem alguma capacidade de fazer a reversão de bits com eficiência? Eu estava planejando apenas fazer em meu próprio código:

v := bits.SwapBytes64(v)
v = (v&0xaaaaaaaaaaaaaaaa)>>1 | (v&0x5555555555555555)<<1
v = (v&0xcccccccccccccccc)>>2 | (v&0x3333333333333333)<<2
v = (v&0xf0f0f0f0f0f0f0f0)>>4 | (v&0x0f0f0f0f0f0f0f0f)<<4

@bradfitz , @dsnet : assinaturas atualizadas (erros de digitação corrigidos). Também perguntas atualizadas (as pessoas preferem nomes explícitos de atalhos).

A menos que haja suporte nativo para troca de bits, eu votaria para deixar de fora SwapBits . Pode ser um pouco ambíguo apenas pelo nome se os bits são trocados apenas dentro de cada byte ou em todo o uint64.

Por que excluir algo com base apenas na disponibilidade de instruções? Bit reverso
tem usos notáveis ​​em FFT.

Para responder à sua pergunta sobre a disponibilidade da instrução de bit reverso: a arm tem
a instrução RBIT.

@griesemer No que diz respeito às assinaturas de tipo de funções como

func Ones64(x uint64) uint
func RotateLeft64(x uint64, n uint) uint64

RotateLeftN etc leva uints porque é necessário para fácil intrinisificação (quando possível) ou apenas porque esse é o domínio? Se não houver uma necessidade técnica forte, há um precedente mais forte para aceitar ints, mesmo que valores negativos não façam sentido. Se houver uma necessidade técnica forte, eles devem ser incluídos no N apropriado para regularidade?

Independentemente de OnesN e seus semelhantes devem retornar ints, a menos que seja assim, essas operações podem ser compostas com outras operações de bits, caso em que eles devem retornar um uintN para o N. apropriado

@jimmyfrasche Porque é o domínio. A CPU não se importa. Se algo é um int ou um uint é irrelevante para a maioria das operações, exceto as comparações. Dito isso, se torná-lo um int, temos que explicar que nunca é negativo (resultado), ou que não deve ser negativo (argumento), ou especificar o que deve ser feito quando for negativo (argumento para rotação). Podemos ser capazes de nos safar com apenas um Rotate nesse caso, o que seria bom.

Não estou convencido de que usar int torna as coisas mais fáceis. Se projetado corretamente, uints fluirá para uints (esse é o caso em matemática / grande) e nenhuma conversão é necessária. Mas mesmo que uma conversão seja necessária, ela será gratuita em termos de custo de CPU (embora não em termos de legibilidade).

Mas fico feliz em ouvir / ver argumentos convincentes de outra forma.

A contagem de bits de rotação deve ser uint (sem tamanho específico) para corresponder ao
operador de deslocamento da língua.

A razão para não usar int é que haverá ambigüidade se
RotateRight -2 bits é igual a RotateLeft 2 bits.

@minux Não há ambigüidade quando Rotate(x, n) é definido para girar x por n mod 64 bits: Girar para a esquerda em 10 bits é o mesmo que girar para a direita em 64-10 = 54 bits, ou (se permitirmos argumentos int) por -10 bits (assumindo um valor positivo significa girar para a esquerda). -10 mod 64 = 54. Provavelmente, obtemos o efeito de graça, mesmo com uma instrução da CPU. Por exemplo, as instruções ROL / ROR de 32 bits x86 já olham apenas para os 5 bits inferiores do registro CL, realizando efetivamente o mod 32 para rotações de 32 bits.

O único argumento real é a regularidade com o resto do stdlib. Existem vários lugares onde uints fazem muito sentido, mas em vez deles são usados ​​ints.

Já que matemática / grande é uma exceção notável, não tenho problema em ser outra, mas é algo a se considerar.

Presumo que @minux significa ambigüidade para alguém lendo / depurando código em vez de ambigüidade na implementação, que é um argumento persuasivo para que isso ocorra.

Os bits.SwapBits devem realmente ser nomeados com um sufixo de bits quando o nome do pacote já é chamado de bits? Que tal bits.Swap?

Outra ideia é bits.SwapN (v uint64, n int) em vez de bits.SwapBytes, em que n representa o número de bits a serem agrupados para a troca. Quando n = 1 os bits são revertidos, quando n = 8, os bytes são trocados. Um benefício é que bits.SwapBytes não precisa mais existir, embora eu nunca tenha visto a necessidade de qualquer outro valor de n, talvez outros discordem.

Conforme especificado acima , LeadingZeros64 , TrailingZeros64 , Ones64 todos LGTM.

Um único Rotate64 com um valor de rotação assinado é atraente. A maioria das rotações será por um valor constante, também, então (se / quando isso se tornar intrínseco) não haverá custo de tempo de execução por não ter funções direita / esquerda separadas.

Não tenho uma opinião forte sobre embaralhamento / troca / reversão de bit / byte. Para inverter bits, bits.Reverse64 parece um nome melhor. E talvez bits.ReverseBytes64 ? Acho Swap64 um pouco confuso. Vejo o apelo de Swap64 pegar um tamanho de grupo, mas qual é o comportamento quando o tamanho do grupo não divide 64 por igual? Se os únicos tamanhos de grupo que importam são 1 e 8 , seria mais simples dar a eles funções separadas. Também me pergunto se algum tipo de manipulação geral de campo de bits seria melhor, talvez olhando as instruções arm64 e amd64 BMI2 para se inspirar.

Não estou convencido de que funções matemáticas inteiras pertençam a este pacote. Eles parecem mais pertencer à matemática de pacotes, onde podem usar funções de bits em sua implementação. Relacionado, por que não func Sqrt(x uint64) uint32 (em vez de retornar uint64 )?

Embora AddUint32 e amigos sejam complicados, espero que possamos revisitá-los eventualmente. Junto com outras coisas, MulUint64 forneceria acesso ao HMUL, que até mesmo o ISA superminimalista RISC-V fornece como uma instrução. Mas sim, vamos colocar algo primeiro; sempre podemos expandir mais tarde.

bits parece claramente ser o nome de pacote correto. Estou tentado a dizer que o caminho de importação deve ser apenas bits , não math/bits , mas não sinto muito.

bits parece claramente o nome de pacote correto. Estou tentado a dizer que o caminho de importação deve ser apenas bits, não matemática / bits, mas não me sinto fortemente.

IMO, se fosse apenas bits , (sem ler a documentação do pacote) eu esperaria que fosse um pouco semelhante ao pacote bytes . Ou seja, além das funções para operar em bits, eu esperaria encontrar Reader e Writer para ler e gravar bits de / para um fluxo de bytes. Torná-lo math/bits torna mais óbvio que é apenas um conjunto de funções sem estado.

(Não estou propondo adicionar um leitor / gravador para fluxos de bits)

Um único Rotate64 com um valor de rotação assinado é atraente. A maioria das rotações será por um valor constante, também, então (se / quando isso se tornar intrínseco) não haverá custo de tempo de execução por não ter funções direita / esquerda separadas.

Eu posso ver como pode ser conveniente ter uma API de pacote ligeiramente menor combinando girar para a esquerda / direita em um único Rotate , mas também há a questão de documentar efetivamente o parâmetro n para indique aquilo:

  • n negativos resulta em uma rotação para a esquerda
  • zero n resultados sem alteração
  • n positivos resultam em uma rotação à direita

Para mim, a sobrecarga mental e de documentação adicionada não parece justificar a combinação dos dois em um único Rotate . Ter um RotateLeft explícito e RotateRight onde n é uint parece mais intuitivo para mim.

@mdlayher Lembre-se de que, para uma palavra de n bits girando k bits para a esquerda, é o mesmo que girar nk bits para a direita. Mesmo se você separar isso em duas funções, ainda precisa entender que girar em k bits para a esquerda sempre significa girar em (k mod n) bits (se girar em mais de n bits, comece de novo). Depois de fazer isso, você pode apenas dizer que a função de rotação sempre gira em k mod n bits e pronto. Não há necessidade de explicar o valor negativo ou uma segunda função. Simplesmente funciona. Na verdade, é mais simples.

Além disso, o hardware (por exemplo, no x86) faz isso para você automaticamente, mesmo para k não constante.

@griesemer , uma desvantagem de Rotate é que ele não diz qual direção é positiva. Rotate32(1, 1) igual a 2 ou 0x80000000? Por exemplo, se eu estivesse lendo um código que usasse isso, esperaria que o resultado fosse 2, mas aparentemente @mdlayher esperaria que fosse 0x80000000. Por outro lado, RotateLeft e RotateRight são nomes inequívocos, independentemente de tomarem um argumento com ou sem sinal. (Eu discordo de @minux que é ambíguo se RotateRight por -2 é igual a RotateLeft 2. Eles parecem obviamente equivalentes a mim e não vejo de que outra forma você poderia especificá-los.)

@aclements que poderiam ser corrigidos tendo apenas uma função chamada RotateLeft64 e usando valores negativos para girar para a direita. Ou com documentos. (FWIW, em seu exemplo, eu também esperaria 2, não 0x800000000.)

@josharian , eu concordo, embora pareça um pouco estranho ter um RotateLeft sem também ter um RotateRight . A consistência de ter uma rotação assinada parece potencialmente valiosa se a rotação for calculada, mas para rotações constantes eu prefiro ler o código que diz "gire para a direita em dois bits" do que "gire para a esquerda apenas brincando com dois bits negativos".

O problema de consertar isso com documentos é que os documentos não ajudam na legibilidade do código que chama a função.

Pessoas que escrevem código querem girar para a esquerda ou para a direita. Eles não querem girar para a esquerda ou direita, dependendo. Se fornecermos apenas girar para a esquerda, qualquer pessoa que quiser girar para a direita terá que escrever uma expressão para converter sua rotação para a direita em uma rotação para a esquerda. Este é o tipo de expressão que os computadores podem fazer trivialmente, mas os programadores cometerão erros. Por que fazer os programadores escreverem eles mesmos?

Estou convencido.

@aclements @ianlancetaylor Ponto tomado. (Dito isso, a implementação de RotateLeft / Right provavelmente chamará apenas uma implementação de rotate.)

Uma ideia de nome é colocar o tipo no nome do pacote em vez do nome da assinatura. bits64.Ones vez de bits.Ones64 .

@btracey , flag64.Int ou math64.Floatfrombits ou sort64.Floats ou atomic64.StoreInt .

@bradfitz golang.org/x/image/math/f64 e golang.org/x/image/math/f32 existem embora

Eu não tinha pensado em atomic como um precedente (não no meu uso normal do Go, felizmente). Os outros exemplos são diferentes porque os pacotes são maiores do que um conjunto de funções repetidas para vários tipos diferentes.

A documentação é mais fácil de olhar (digamos, no godoc), quando você vai para bits64 e vê
Ones LeadingZeros TrailingZeros

Em vez de ir para bits e ver
Ones8 Ones16 Ones32 Ones64 LeadingZeros8 ...

@iand , isso também é muito nojento. Suponho que 32 e 64 não tenham uma boa aparência para todos os sufixos numéricos existentes para Aff, Mat e Vec. Ainda assim, eu diria que é uma exceção, e não o estilo Go normal.

Há um precedente bastante forte em Go para usar o estilo de nomenclatura pkg.foo64 em vez de pkg64.foo.

A API do pacote fica n vezes maior se tivermos n tipos, mas a complexidade da API não. Uma abordagem melhor para resolver isso pode ser por meio da documentação e como a API é apresentada por meio de uma ferramenta como go doc. Por exemplo, em vez de:

// TrailingZeros16 returns the number of trailing zero bits in x.
// The result is 16 if x == 0.
func TrailingZeros16(x uint16) uint

// TrailingZeros32 returns the number of trailing zero bits in x.
// The result is 32 if x == 0.
func TrailingZeros32(x uint32) uint

// TrailingZeros64 returns the number of trailing zero bits in x.
// The result is 64 if x == 0.
func TrailingZeros64(x uint64) uint

que claramente adiciona sobrecarga mental ao olhar para a API, poderíamos apresentá-la como:

// TrailingZerosN returns the number of trailing zero bits in a uintN value x for N = 16, 32, 64.
// The result is N if x == 0.
func TrailingZeros16(x uint16) uint
func TrailingZeros32(x uint32) uint
func TrailingZeros64(x uint64) uint

godoc pode ser inteligente sobre funções semelhantes nesse sentido e apresentá-las como um grupo com uma única string doc. Isso seria útil em outros pacotes também.

Para resumir, acho que o esquema de nomenclatura proposto parece bom e na tradição Go. O problema da apresentação é uma questão separada que pode ser discutida em outro lugar.

+1 em colocar o tamanho do bit no nome do pacote.

bits64.Log2 lê melhor do que bits.Log264 (sinto que Log2 pertence a esse lugar - e a nomenclatura de pacotes não é um bom motivo para mantê-lo fora)

No final, é a mesma API para cada tipo "parametrizado" por tipo escalar, então se as funções têm o mesmo nome entre os tipos, o código é mais fácil de refatorar com pacotes separados - basta alterar o caminho de importação (substituições de palavras inteiras são trivial com gofmt -r, mas as transformações de sufixo personalizadas são mais complicadas).

Concordo com @griesmeyer que o sufixo bitsize no nome da função pode ser idiomático, mas acho que vale a pena apenas se houver código não trivial no pacote que seja independente do tipo.

Poderíamos roubar um jogo de codificação / binário e criar vars chamados Uint64, Uint32, ... que teriam métodos que aceitam os tipos predefinidos associados.

bits.Uint64.RightShift
bits.Uint8.Reverse
bits.Uintptr.Log2
...

@griesemer como saberia agrupá-los? Uma string doc com o nome da função terminando em N na documentação em vez do nome real e, em seguida, encontrando um prefixo comum entre funcs sem docstrings que corresponda à incongruência? Como isso generaliza? Parece um caso especial complicado para servir poucos pacotes.

@rogpeppe poderia ser bits. Log64 e os documentos dizem que era de base 2 (o que mais seria em um pacote de bits?) Embora tão confuso quanto ter pacotes de 8/16/32/64 / ptr estão em um nível, ele faz faça cada um deles mais limpo individualmente. (Também parece que sua transmissão foi interrompida no meio da frase.)

@nerdatmath isso seria um pouco diferente, pois eles teriam a mesma interface no sentido coloquial, mas não no sentido Go, como é o caso em codificação / binário (ambos implementam binary.ByteOrder), então as variáveis ​​seriam apenas para namespacing e godoc não pegaria nenhum dos métodos (# 7823) a menos que os tipos fossem exportados, o que é confuso de uma maneira diferente.

Eu estou bem com os sufixos de tamanho, mas, no geral, eu preferiria os pacotes separados. Não o suficiente para se preocupar em empurrar esse comentário, no entanto.

@nerdatmath se eles forem vars, então eles são mutáveis ​​(você poderia fazer bits.Uint64 = bits.Uint8 ), o que impediria o compilador de tratá-los como intrínsecos, o que foi uma das motivações para (pelo menos parte do) pacote.

As variáveis ​​não são realmente mutáveis ​​se forem de um tipo não exportado e não tiverem estado (estrutura vazia não exportada). Mas sem uma interface comum, o godoc (hoje) seria nojento. No entanto, corrigível se essa for a resposta.

Porém, se vocês acabarem tendo vários pacotes, se houver repetição e volume suficientes para justificá-lo, pelo menos nomeie os pacotes como "X / bits / int64s", "X / bits / ints", "X / bits / int32s "para corresponder a" erros "," strings "," bytes ". Ainda parece muita explosão de pacotes.

Não acho que devemos seguir o caminho para vários pacotes específicos de tipo, ter um único pacote de bits é simples e não aumenta o número de pacotes na biblioteca Go.

Não acho que Ones64 e TrailingZeroes64 sejam bons nomes. Eles não informam que retornam uma contagem. Adicionar o prefixo Count torna os nomes ainda mais longos. Em meus programas, essas funções são frequentemente incorporadas em expressões maiores, portanto, nomes de função curtos aumentam a legibilidade. Embora eu tenha usado os nomes do livro "Hacker's Delight", sugiro seguir os mnemônicos do Intel assembler: Popcnt64, Tzcnt64 e Lzcnt64. Os nomes são curtos, seguem um padrão consistente, informam que uma contagem é retornada e já está estabelecida.

Normalmente as contagens em Go são retornadas como ints, mesmo quando uma contagem nunca pode ser negativa. O exemplo principal é a função interna len, mas Read, Write, Printf, etc. todos retornam inteiros também. Acho o uso do valor uint para uma contagem bastante surpreendente e sugiro retornar um valor int.

Se a escolha for entre (abusar da notação) math / bits / int64s.Ones e math / bits.Ones64, então eu definitivamente preferiria um único pacote. É mais importante ter bits no identificador do pacote do que particionar por tamanho. Ter bits no identificador do pacote torna a leitura do código mais fácil, o que é mais importante do que o pequeno inconveniente de repetição na documentação do pacote.

@ulikunitz Você sempre pode fazer ctz := bits.TrailingZeroes64 , e assim por diante , em código que usa bits extensivamente. Isso resulta em um equilíbrio entre ter nomes autodocumentados globalmente e nomes localmente compactos para código complexo. A transformação oposta, countTrailingZeroes := bits.Ctz64 , é menos útil porque as boas propriedades globais já foram perdidas.

Eu mexo com coisas no nível de bits muito raramente. Eu tenho que olhar muito essas coisas todas as vezes, só porque já faz anos que não tenho que pensar sobre isso, então eu acho todos os nomes do Hacker Delight / asm-style irritantemente enigmáticos. Prefiro que ele apenas diga o que diz, para que possa localizar o que preciso e voltar à programação.

Eu concordo com @ulikunitz os nomes longos são uma ferida nos olhos. A primeira vez que você vê bits.TrailingZeroes64, pode ser autodocumentado. Todas as vezes subsequentes, é irritante. Posso ver por que as pessoas fariam ctz: = bits.TrailingZeroes64, mas acho que um nome que precisa ser atribuído a um nome mais curto é um nome ruim. Há lugares onde Go abrevia, por exemplo,

init() instead of initialize(), 
func instead of function
os instead of operatingsystem
proc instead of process
chan instead of channel

Além disso, eu desafiaria esses bits. TrailingZeroes64 é autodocumentado. O que significa um zero estar atrás? A propriedade de autodocumentação existe com a suposição de que o usuário sabe algo sobre a semântica da documentação para começar. Se não estiver usando Hacker's Delight para consistência, por que não chamá-lo de ZeroesRight64 e ZeroesLeft64 para corresponder a RotateRight64 e RotateLeft64?

Para esclarecer, não quis dizer que sempre que você o usar, a primeira coisa que você deve fazer é criar um apelido curto e nunca deve usar o nome fornecido. Eu quis dizer que se você estiver usando repetidamente, você pode criar um alias, se isso melhorar a legibilidade do código.

Por exemplo, se estou chamando strings.HasSuffix, eu uso o nome fornecido na maioria das vezes porque estou chamando-o uma ou duas vezes, mas, de vez em quando, em um script ETL único ou semelhante, acabo chamando de bando, então farei ends := strings.HasSuffix que é mais curto e torna o código mais claro. É bom porque a definição do alias está próxima.

Nomes abreviados são adequados para coisas extremamente comuns. Muitos não programadores sabem o que significa SO. Qualquer pessoa que ler código, mesmo que não conheça Go, vai entender que func é a abreviatura de function. Canais são fundamentais para Go e amplamente usados, então chan é bom. As funções de bits nunca serão extremamente comuns.

O TrailingZeroes pode não ser capaz de dizer exatamente o que está acontecendo se você não estiver familiarizado com o conceito, mas dá uma ideia muito melhor do que está acontecendo, pelo menos aproximadamente, do que Ctz.

@jimmyfrasche Sobre https://github.com/golang/go/issues/18616#issuecomment -275828661: Seria muito fácil para go / doc reconhecer uma sequência contígua de funções no mesmo arquivo que têm nomes que começam com o mesmo prefixo e tem um sufixo que é apenas uma sequência de números e / ou talvez uma palavra que seja "curta" em relação ao prefixo. Eu combinaria isso com o fato de que apenas a primeira dessas funções tem uma string doc e a outra com o prefixo correspondente, não. Acho que poderia funcionar muito bem em configurações mais gerais. Dito isso, vamos discutir isso em outro lugar para não sequestrar essa proposta.

Para sua informação, criou o # 18858 para discutir as mudanças de go/doc e manter essa conversa separada desta.

@mdlayher, obrigado por fazer isso.

@rogpeppe Colocar o tamanho no nome do pacote parece intrigante, mas a julgar pelos comentários e dado o estilo Go existente, acho que a preferência é colocar o tamanho com o nome da função. Com relação ao Log2, eu diria, vamos deixar o 2 de lado. (Além disso, repita se houver mais alguma coisa que você queira adicionar, seus comentários aparecem cortados no meio da frase.)

@ulikunitz geralmente sou um dos primeiros a votar em nomes curtos; especialmente no contexto local (e no contexto global para nomes usados ​​com extrema frequência). Mas aqui temos uma API de pacote e (pelo menos na minha experiência) as funções não aparecerão de forma generalizada nos clientes. Por exemplo, math / big faz usa LeadingZeros, Log2, etc., mas é apenas uma ou duas chamadas. Acho que um nome descritivo mais longo está ok e, a julgar pela discussão, a maioria dos comentários é a favor disso. Se você chama uma função com frequência, é barato envolvê-la em uma função com um nome curto. A chamada adicional será sequencial. FWIW, os mnemônicos da Intel também não são bons, sua ênfase está na "contagem" (cnt) e então você fica com 2 letras que precisam ser descriptografadas.

Eu concordo que o 2 no Log2 não é necessário. bits.Log implica uma base de 2.

bits.Log64 SGTM.

@griesemer Minha frase cortada foi provavelmente um artefato de edição desajeitada. Acabei de removê-lo.

@griesemer Eu me pergunto se vale a pena continuar com o derramamento de bicicletas do nome. Eu sou breve: tive dois argumentos: brevidade e clareza sobre retornar um número. O pacote grande usa nlz, não LeadingZeros, internamente. Eu concordo que o número de usos para essas funções é pequeno.

@ulikunitz Acho que a maioria dos comentários aqui é a favor de nomes de função claros que não sejam atalhos.

@ulikunitz , adicionalmente:

O pacote grande usa nlz, não LeadingZeros, internamente.

Nomes internos privados não divulgados não têm influência aqui.

Tudo o que importa é a consistência e a sensação padrão da API pública.

Andar de bicicleta pode ser irritante, mas os nomes importam quando estamos presos a eles para sempre.

CL https://golang.org/cl/36315 menciona esse problema.

Eu carreguei https://go-review.googlesource.com/#/c/36315/ como uma API inicial (parcialmente testada) (com implementação preliminar) para revisão (no lugar de um documento de design).

Ainda estamos verificando se a API está correta, então, por favor, comente apenas sobre a API (bits.go) por enquanto. Para questões menores (erros de digitação, etc.), comente no CL. Para questões de design, por favor, comente sobre este assunto. Vou continuar atualizando o CL até estarmos satisfeitos com a interface.

Especificamente, não se preocupe com a implementação. É básico e apenas parcialmente testado. Quando estivermos satisfeitos com a API, será fácil expandir a implementação.

@griesemer Tenho certeza de que você está bem, mas se for útil, tenho uma implementação de assembly CLZ com testes https://github.com/ericlagergren/decimal/tree/ba42df4f517084ca27f8017acfaeb69629a090fb/internal/arith para os quais você está livre roubar / mexer com / qualquer coisa.

@ericlagergren Obrigado pelo link. Assim que a API estiver estabelecida e tivermos testes em todos os lugares, podemos começar a otimizar a implementação. Vou manter isso em mente. Obrigado.

@griesemer , parece que a documentação da API proposta depende de go / doc ser capaz de documentar funções adequadamente com o mesmo prefixo, mas um sufixo inteiro.

O plano seria trabalhar nisso antes de 1.9 também?

18858, isso é!

@mdlayher Isso seria o ideal. Mas não seria um obstáculo, pois afeta "apenas" a documentação, não a API (que deve permanecer compatível com as versões anteriores no futuro).

@griesemer @bradfitz Passei algum tempo repensando minha posição em relação aos nomes das funções. Um objetivo importante da escolha de nomes deve ser a legibilidade do código usando a API.

O código a seguir é realmente fácil de entender:

n := bits.LeadingZeros64(x)

Ainda estou um pouco em dúvida sobre a clareza de Ones64 (x), mas é consistente com LeadingZeros e TrailingZeros. Portanto, encerro meu caso e apoio a proposta atual. Como um experimento, usei meu código existente para uma implementação experimental de Go puro do pacote, incluindo casos de teste.

Repositório: https://github.com/ulikunitz/bits
Documentação: https://godoc.org/github.com/ulikunitz/bits

Podemos ter um conjunto de funções Swap, assim [...] a maioria dos usos de SwapBytes se deve ao código que reconhece o endian quando não deveria. Comentários?

Eu gostaria de manter SwapBytes fora da API por este motivo. Eu me preocupo que as pessoas comecem a usá-lo de maneiras não portáteis porque é mais simples (ou presume-se que seja mais rápido) do que binary/encoding.BigEndian .

Gostaria de manter os SwapBytes fora da API por esse motivo. Eu me preocupo que as pessoas comecem a usá-lo de maneiras não portáteis porque é mais simples (ou presume-se que seja mais rápido) do que binary / encoding.BigEndian.

@mundaym, essas rotinas algum dia serão intrinsecadas? Deixar SwapBytes64 compilar diretamente como BSWAP pode ser mais importante do que as pessoas que o usam indevidamente, imo.

Eu me preocupo que as pessoas comecem a usá-lo de maneiras não portáteis porque é mais simples (ou presume-se que seja mais rápido) do que binário / codificação.

Eu acredito que o uso de ReverseBytes só é não portátil se usado em conjunto com unsafe onde você acessa dados de baixo nível na forma como a máquina os coloca na memória. No entanto, o uso de encoding.{Little,Big}Endian.X é portátil quando usado dessa forma. Aguardo ReverseBytes para uso na compressão e ficaria triste se não fosse incluído.

@ericlagergren

essas rotinas algum dia serão intrinsecadas?

Sim, as funções em encoding/binary que realizam reversões de bytes usam padrões de código que são reconhecidos pelo compilador e são (ou podem ser) otimizados para instruções únicas (por exemplo, BSWAP ) em plataformas que suportam dados não alinhados acessos (ex: 386, amd64, s390x, ppc64le e provavelmente outros).

Deixar o SwapBytes64 compilar diretamente como o BSWAP pode ser mais importante do que as pessoas que o usam indevidamente, imo.

Estou inclinado a discordar. Pode haver usos legítimos de SwapBytes no código endianness-unaware, mas suspeito que ele será usado incorretamente com mais frequência do que usado corretamente.

Observe que runtime / internal / sys já otimizou Go, assembly e versões intrínsecas do compilador de zeros à direita e troca de bytes, portanto, podemos reutilizar essas implementações.

@dsnet Interessante, você tem algo específico em mente?

@aclements Você sabe onde o byte swap intrínseco é usado no tempo de execução? Não encontrei nenhum uso fora dos testes.

@mundaym , AFAIK não é usado no tempo de execução. Não precisamos ser tão cuidadosos com APIs internas, então acho que apenas adicionamos ao adicionar ctz porque era fácil. :) (Popcount teria sido uma escolha melhor.)

Todos: Apenas um lembrete para se concentrar na API por enquanto. A implementação no CL é simplesmente uma implementação lenta e trivial que nos permite realmente usar essas funções e testá-las. Assim que estivermos satisfeitos com a API, podemos ajustar a implementação.

Nessa nota: provavelmente devemos incluir versões para uintptr pois não é garantido (embora seja provável) que seja do mesmo tamanho de uint32 ou uint64 . (Observe que uint é sempre uint32 ou uint64 ). Opiniões? Deixar para depois? Quais seriam os bons nomes? ( LeadingZerosPtr ?).

Devemos fazer uintptr agora, caso contrário, math / big não pode usá-lo. Sufixo Ptr SGTM. Acho que também devemos fazer uint , caso contrário, todo código de chamada precisa escrever código condicional em torno do tamanho interno para fazer a chamada livre de conversão. Sem ideias de nomes.

Ptr para uintptr, sem sufixo para uint. Uns, Ones8, Ones16, Ones32, Ones64, OnesPtr, etc.

Posso ser uma minoria aqui, mas sou contra a adição de funções que levem uintptr.
math / big deveria ter usado uint32 / uint64 desde o início. (Na verdade, eu
basta usar uint64 para todas as arquiteturas e deixar a otimização para o
compilador.)

O problema é este:
math / big deseja usar o tamanho da palavra nativa para um desempenho ideal,
entretanto, embora próximo, uintptr não é a escolha correta.
não há garantia de que o tamanho de um ponteiro seja do mesmo tamanho que o
tamanho da palavra nativa.
O exemplo principal é amd64p32, e também podemos adicionar mips64p32 e
mips64p32le mais tarde, que são muito mais populares para hosts MIPS de 64 bits.

Certamente não quero encorajar mais usos desse tipo. uintptr é mais
para ponteiros, não para inteiros neutros de arquitetura.

Uma solução, no entanto, é introduzir um alias de tipo na API (talvez
deve ser do tipo com marca, isso está em discussão).
digite Word = uint32 // ou uint64 em arquiteturas de 64 bits
// provavelmente também precisamos fornecer algumas constantes ideais para o Word, como
número de bits, máscara, etc.

E então introduza funções sem qualquer prefixo de tipo para levar o Word.

Na verdade, imagino que se matemática / bit fornecer os dois resultados add, sub, mul,
div; math / big pode ser reescrito sem qualquer montagem específica de arquitetura.

matemática / grandes exportações type Word uintptr . Concordo que pode ter sido um erro, mas acredito que a compatibilidade exige que isso permaneça. Portanto, se quisermos usar math / bits para implementar math / big, o que acho que fazemos, precisamos de APIs uintptr.

Vagando um pouco fora do tópico, mas o tamanho da palavra não assinada nativa não é apenas uint? Nesse caso, não ter sufixo para uint parece adequado.

Eu não gostaria que um único tipo, o Word, fosse diferente em diferentes arquiteturas. Isso parece apresentar muita complexidade para o autor da chamada e os documentos. Manter os tipos existentes significa que você pode simplesmente usá-los. Ter um tipo que varia entre as arquiteturas significa projetar todo o seu código em torno desse tipo ou ter um monte de adaptadores em arquivos protegidos por marca de construção.

FWIW, a implementação de math / big ainda poderia usar uma API de bits baseada em uint32 / 64 (se não houvesse uma versão uintptr das funções).

@minux Não sou contra add, sub, mul, div de 2 resultados - mas vamos fazer isso em uma segunda etapa. Ainda precisamos do código assembly se quisermos um desempenho decente. Mas estou feliz de ser convencido do contrário pelo compilador ...

Eu adicionei as versões uint e uintptr à API. Dê uma olhada no CL e comente. Não estou muito feliz com a proliferação de funções, no entanto.

@josharian O uint nativo pode ser um valor de 32 bits mesmo em uma máquina de 64 bits. Não há garantia. Eu sei que uintptr também não garante o tamanho da palavra da máquina; mas na época parecia a escolha mais sensata, embora errada em retrospecto. Talvez realmente precisemos de um tipo de palavra.

Se a única necessidade legítima das funções uintptr for suportar math / big, talvez a implementação de math / bits pudesse ir em um pacote interno: use apenas as coisas uintptr em math / big e exponha o resto.

@jimmyfrasche , @griesemer como isso afetaria o big.Int.Bits? ou seja, ainda será alocação zero?

uint também está errado. é de 32 bits em amd64p32.
Na verdade, math / big poderia ter usado uint em vez de uintptr, mas em Go 1,
int / uint são sempre de 32 bits, o que torna uintptr a única solução possível
para matemática / big.Word.

Estamos projetando um novo pacote, então não acredito que a compatibilidade seja muito
interesse. matemática / grande está usando o tipo errado, mas isso é mais um histórico
acidente. Não vamos levar o erro adiante.

Não entendo por que usar um tipo unificado "Word" que é sempre o mais
tipo inteiro eficiente para uma determinada arquitetura é mais complicado do que
usando uintptr. Para o código do usuário, você pode tratá-lo como um tipo mágico que é
32 ou 64 bits, dependendo da arquitetura. Se você pode escrever
seu código usando uintptr (cuja largura também depende da arquitetura) em um
arquitetura independente, então acredito que você pode fazer o mesmo com o Word.
E o Word está correto em todas as arquiteturas.

Para a proliferação da API, sugiro deixarmos funções para 8
e tipos de 16 bits na primeira passagem. Eles são raramente usados ​​e mais
arquitetura fornecerá apenas instruções de 32 bits e 64 bits de qualquer maneira.

math / big define e exporta o Word, mas é a) não compatível diretamente com uintptr (é um tipo diferente) eb) o código que usa o Word corretamente e não faz suposições sobre suas implementações será capaz de qualquer tipo de implementação do Word . Podemos mudar isso. Não vejo por que isso deve afetar a garantia de compatibilidade da API (ainda não tentei). De qualquer forma, vamos deixar isso de lado. Podemos lidar com isso separadamente.

Podemos simplesmente fazer todos os tipos de uint de tamanho explícito? Se conhecermos o tamanho uint / uintptr como uma constante, podemos escrever trivialmente uma instrução if que chama a função de tamanho correto. Essa instrução if será dobrada constantemente, e a função de wrapper respectiva simplesmente chama a função sized que será embutida.

Finalmente, qual é a solução certa em relação ao tipo de palavra (independente de matemática / grande)? Claro que poderíamos definir em matemática / bits, mas esse é o lugar certo? É um tipo específico de plataforma. Deve ser um tipo pré-declarado 'palavra'? E porque não?

Ter qualquer assinatura (ou nome) de função aqui que mencione uintptr diretamente parece um erro. As pessoas não devem fazer esse tipo de manipulação de ponteiros Go.

Se deveria haver funções para um tipo de palavra de máquina natural, acho que elas podem se referir a int / uint agora. Não usamos uint em matemática / big porque era de 32 bits em sistemas de 64 bits, mas desde então corrigimos isso. E embora math / big seja um mundo coerente em si mesmo, onde faz sentido ter seu próprio tipo big.Word, este pacote é mais uma coleção de rotinas de utilitários de escolha e escolha. Definir um novo tipo neste contexto é menos atraente.

Não sei se precisamos de variantes int / uint. Se eles são comumente necessários, defini-los neste pacote faz mais sentido do que forçar todos os chamadores a escreverem instruções if. Mas não sei se eles serão comumente necessários.

Para resolver a possível incompatibilidade com matemática / grande, existem pelo menos duas soluções que não envolvem a menção de uintptr na API aqui.

  1. Defina os arquivos word32.go e word64.go em math / big com tags de compilação e invólucros que aceitam uintptr que redirecionam apropriadamente (e certifique-se de que o inlining do compilador faz a coisa certa).

  2. Altere a definição de big.Word para uint. A definição exata é um detalhe de implementação interno, embora seja exposta na API. Mudá-lo não pode quebrar nenhum código, exceto em amd64p32, e mesmo assim parece muito improvável que importe fora da matemática / grande.

@rsc : "Não usamos uint em matemática / big porque era de 32 bits em sistemas de 64 bits, mas já corrigimos isso." Sim, esqueci completamente o motivo da escolha de uintptr. O objetivo dos tipos Go uint / int era ter tipos inteiros que refletissem o tamanho do registro natural de uma máquina. Vou tentar mudar o big.Word para uint e ver o que acontece.

Todos: https://go-review.googlesource.com/#/c/36315/ atualizado para excluir as versões da função uintptr de acordo com a discussão acima.

Atualizei matemática / big provisoriamente para usar matemática / bits para ver o efeito (https://go-review.googlesource.com/36328).

Alguns comentários:
1) Estou inclinado a fazer os resultados das funções Leading / TrailingZeros uint vez de int . Esses resultados tendem a ser usados ​​para deslocar um valor de acordo; e math/big evidencia isso. Também sou a favor de fazer Ones retornar um uint , para consistência. Contra-argumentos?

2) Não sou fã do nome One , embora seja consistente com Leading / TrailingZeros . Que tal Population ?

3) Algumas pessoas reclamaram que Log não cabe neste pacote. Log é equivalente ao índice do msb (com -1 para x == 0). Portanto, poderíamos chamá-lo de MsbIndex (embora Log pareça mais agradável). Alternativamente, poderíamos ter uma função Len que tem o comprimento de um x em bits; ou seja, `Log (x) == Len (x) -1).

Comentários?

Não sou fã do nome One, embora seja consistente com Leading / TrailingZeros. E quanto à população?

Por que não o tradicional Popcount ?

Alternativamente, poderíamos ter uma função Len que tem o comprimento de um x em bits; ou seja, `Log (x) == Len (x) -1).

Len ou Length soa como um bom nome e uma boa ideia.

1

Não sou fã do nome One, embora seja consistente com Leading /
TrailingZeros. E quanto à população?

bits.Count?

@aclements Contar o quê?

População pode ser o melhor nome por razões históricas, mas ele realmente não diz mais do que Um, se você não estiver familiarizado com o termo e tiver o mesmo problema, é apenas Contar: População de quê?

É um tanto unidiomático, mesmo dentro do pacote, mas talvez CountOnes, para dar um nome claro e óbvio.

@aclements Contar o quê?

Em um pacote chamado "bits", não tenho certeza do que mais você poderia estar contando, exceto bits definidos, mas CountOnes também é bom e obviamente mais explícito. Adicionar "uns" também é paralelo aos "zeros" em LeadingZeros / TrailingZeros .

Essa é a interpretação mais óbvia, mas ainda há ambigüidade. Pode ser contar bits não definidos ou bits totais (a última interpretação seria extremamente improvável, mas ainda assim uma armadilha potencial para alguém lendo código usando bits que não está familiarizado com os conceitos envolvidos e pode pensar que Count sem sufixo é inseguro. TamanhoOf)

Talvez algo como AllOnes ou TotalOnes para espelhar Trailing / LeadingZeroes, mas especifique que, ao contrário desses, a posição não é levada em consideração.

AllOnes parece retornar todos os uns (em um bitmask, talvez?), Ou talvez uma palavra que é todos uns.

CountOnes e TotalOnes parecem quase iguais, mas como o nome mais conhecido dessa operação é "contagem de população", CountOnes parece ser preferível.

Enviei uma nova versão (https://go-review.googlesource.com/#/c/36315/) com algumas alterações:

1) Mudei o nome de Ones para PopCount : A maioria das pessoas não ficou muito entusiasmada com o nome consistente, mas um tanto monótono, Ones . Todos que usarem este pacote saberão exatamente o que PopCount faz: é o nome pelo qual essa função é comumente conhecida. É um pouco inconsistente com o resto, mas seu significado se tornou muito mais claro. Eu vou com Ralph Waldo Emerson neste aqui ("Uma consistência tola é o hobgoblin das mentes pequenas ...").

2) Eu renomei Log para Len como em bits.Len . O comprimento do bit de um número x é o número de bits necessários para representar x na representação binária (https://en.wikipedia.org/wiki/Bit-length). Parece adequado e elimina a necessidade de ter Log aqui, que não tem uma qualidade "pouco complicada". Eu acredito que isso também é uma vitória porque Len(x) == Log(x) + 1 considerando como tínhamos Log definidos. Além disso, tem a vantagem de que o resultado agora é sempre> = 0 e remove algumas correções +/- 1 na implementação (trivial).

No geral, estou muito feliz com esta API neste ponto (podemos querer adicionar mais funções posteriormente). A única outra coisa que acho que devemos considerar seriamente é se todos os resultados devem ser não assinados. Como mencionei antes, os resultados da função Trailing / Leading Zero tendem a ser entradas para operações de deslocamento que devem ser sem sinal. Isso deixa apenas Len e PopCount, que sem dúvida poderiam retornar valores não assinados também.

Comentários?

Minha experiência com matemática / grande tem sido a frustração de ser forçado a entrar no modo uint por funções quando não estou mudando. Em matemática / big / prime.go, escrevi

for i := int(s.bitLen()); i >= 0; i-- {

mesmo que s.bitLen () retorne int não uint, porque eu não tinha certeza sem olhar e também não tinha certeza de que algum CL futuro não poderia alterá-lo para retornar uint, transformando assim aquele loop for em um loop infinito. Ter de ser tão defensivo sugere que há um problema.

Uints são muito mais sujeitos a erros do que ints, e é por isso que os desencorajamos na maioria das APIs. Acho que seria muito melhor se todas as funções em matemática / bits retornassem int e deixassem a conversão para uint para ser feita na expressão de deslocamento, como é necessário na maioria das expressões de deslocamento de qualquer maneira.

(Não estou propondo uma mudança, mas acho que a mudança que exige uint pode ter sido um erro, em retrospecto. Talvez possamos consertar em algum momento. Seria bom se não prejudicasse nossas outras APIs.)

Eu apóio o retorno de int depois de usar o grep em meu código para chamadas de zeros à direita e funções popcount. A maioria das chamadas são em declarações de variáveis ​​curtas e comparações do tipo int. É claro que as chamadas em turnos exigem o tipo uint, mas são surpreendentemente raras.

Pequenos ajustes carregados. Por + 1s e comentários, vamos deixar os resultados das contagens como int .

https://go-review.googlesource.com/36315

Deixei as contagens de entrada para RotateLeft/Right como uint caso contrário, precisaremos especificar o que acontece quando o valor for negativo ou não permitir.

Finalmente, podemos até mesmo deixar de fora Len afinal, uma vez que LenN(x) == N - LeadingZerosN(x) . Opiniões?

Se não houver mais feedback significativo neste ponto, sugiro que avancemos com esta API e cometamos uma implementação inicial após a revisão. Depois disso, podemos começar a ajustar a implementação.

Na próxima etapa, podemos querer discutir se e quais outras funções podemos querer incluir, especificamente Add / Sub / etc. que consomem 2 argumentos e carregam, e produzem um resultado e carregam .

Pensamentos @gri sobre uma função de base 10 Len ? Seria apenas ((N - clz(x) + 1) * 1233) >> 12

É menos "um pouco hacky" do que a base 2, mas ainda é útil.
Na sexta-feira, 10 de fevereiro de 2017 às 17h03, Robert Griesemer [email protected]
escreveu:

Pequenos ajustes carregados. Por + 1s e comentários, vamos deixar os resultados de
conta como int.

https://go-review.googlesource.com/36315

Eu deixei as contagens de entrada para RotateLeft / Right como uint, caso contrário,
precisa especificar o que acontece quando o valor é negativo ou não permitir.

Finalmente, podemos até deixar de lado Len afinal, já que LenN (x) == N -
LeadingZerosN (x). Opiniões?

Se não houver mais feedback significativo neste momento, sugiro que
avançar com esta API e comprometer uma implementação inicial após
Reveja. Depois disso, podemos começar a ajustar a implementação.

Em uma próxima etapa, podemos discutir se e quais outras funções podemos
deseja incluir, especificamente Adicionar / Sub / etc. que consome 2 argumentos e
carregue e produza um resultado e carregue.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/golang/go/issues/18616#issuecomment-279107013 ou mudo
o segmento
https://github.com/notifications/unsubscribe-auth/AFnwZ_QMhMtBZ_mzQAb7XZDucXrpliSYks5rbQjCgaJpZM4Lg5zU
.

Também. Perdoe-me se estou saindo do escopo deste problema, mas eu seria a favor da aritmética verificada. Por exemplo, AddN(x, y T) (sum T, overflow bool)

Na sexta-feira, 10 de fevereiro de 2017 às 20:16, Eric Lagergren [email protected]
escreveu:

Também. Perdoe-me se estou saindo do escopo deste problema, mas estaria em
favor da aritmética verificada. Por exemplo, AddN (x, y T) (soma T, overflow bool)

Você está falando sobre estouro de assinatura? ou estouro não assinado (carregar / emprestar)?

Além disso, prefiro retornar overflow / carry / emprestar como tipo T, para simplificar
aritmética multi-precisão.

@ericlagergren A função (base 2) Len , embora quase log2, ainda é essencialmente uma função de contagem de bits. Uma função Len base 10 é, na verdade, uma função de log. Não há como negar que é útil, mas parece menos apropriado para este pacote.

Sim, acho que seria bom obter checked aritmética. Eu só queria encerrar a API atual primeiro para que possamos fazer progresso em vez de ir e voltar. Parece que a maioria das pessoas que comentaram até agora parece feliz com isso.

Em relação às funções Add, Sub: Concordo com @minux que o carry deve ser do mesmo tipo que o argumento. Além disso, não estou convencido de que precisamos de mais nada além de argumentos uint para essas funções: o ponto é que (na maioria dos casos) uint tem o tamanho do registro natural da plataforma, e esse é o nível em que essas operações fazem mais sentido.

Um pacote de matemática / verificação pode ser um lar melhor para eles. checked.Add é mais claro do que bits.Add .

@minux Eu estava pensando em aritmética verificada assinada, então estouro.

@griesemer re: base 10 Len : faz sentido!

Se você quiser, posso enviar um CL para aritmética verificada. Gosto da ideia de @jimmyfrasche de ter um nome de pacote separado.

Adicionar / sub / mul pode ou não pertencer a bits , mas a matemática verificada não é o único uso para essas operações. De maneira mais geral, eu diria que são operações aritméticas "amplas". Para add / sub há pouca diferença entre o resultado de um bit de carry / overflow e um indicador booleano de overflow, mas provavelmente também desejaríamos fornecer add-with-carry e subtract-with-borrow para torná-los encadeados. E para mul largo, há muito mais informações no resultado adicional do que estouro.

É uma boa ideia lembrar as operações aritméticas verificadas / amplas, mas vamos deixá-las para a segunda rodada.

"Todos que usarem este pacote saberão exatamente o que o PopCount faz"

Já usei essa funcionalidade antes e, de alguma forma, não estava familiarizado com o nome PopCount (embora devesse, porque tenho certeza de que retirei a implementação do Hacker's Delight, que usa "pop").

Eu sei que estou na festa, mas "OnesCount" parece consideravelmente mais óbvio para mim, e se a palavra "PopCount" for mencionada no comentário do documento, as pessoas que procuram por ela irão encontrá-la de qualquer maneira.

Relacionado para aritmética verificada / ampla: # 6815. Mas sim, vamos entrar na primeira rodada!

@griesemer escreveu:

A função Len (base 2), embora quase log2, ainda é, em essência, uma função de contagem de bits. Uma função Len de base 10 é, na verdade, uma função de log. Não há como negar que é útil, mas parece menos apropriado para este pacote.

Eu escrevi um benchmark para comparar algumas abordagens para o problema do "comprimento de dígitos decimais" em outubro passado, que publiquei resumidamente .

Aceito como proposta; provavelmente haverá pequenos ajustes no futuro, e tudo bem.

@rogpeppe : Alterou PopCount para OnesCount , pois também recebeu 4 polegares para cima (como minha sugestão de usar PopCount ). Referido a "contagem de população" na sequência de documentos.

Por @rsc , deixando de fora as operações aritméticas verificadas / amplas por enquanto.

Também por @rsc , todas as funções de contagem retornam int valores, e para facilidade de uso (e com atenção em # 19113), use int valores para contagens de rotação.

Deixaram LenN nas funções, embora sejam simplesmente N - LeadingZerosN . Mas uma simetria semelhante existe para RotateLeft / Right e nós temos ambos.

Adicionada implementação um pouco mais rápida para TrailingZeroes e testes concluídos.

Neste ponto, acredito que temos uma primeira implementação utilizável. Revise o código em https://go-review.googlesource.com/36315 , especialmente a API. Gosto de enviar isso, se estivermos todos satisfeitos.

Próximos passos:

  • implementação mais rápida (primário)
  • adicionar funcionalidade adicional (secundária)

Estamos projetando um novo pacote

@minux Você quer dizer nova matemática / grande? É possível seguir o processo em algum lugar?

@TuomLarsen : @minux referia-se a matemática / bits com "novo pacote". Ele mencionou matemática / grande como um caso em que matemática / bits seriam usados. (No futuro, seja mais específico com seus comentários para que não tenhamos que pesquisar e adivinhar a que você está se referindo - obrigado).

CL https://golang.org/cl/37140 menciona esse problema.

Haverá alguma intrinsificação auxiliada por compilador acontecendo em matemática / bits para o Go 1.9?

@cespare Depende de se chegarmos a ele (@khr?). Independentemente disso, queremos ter uma implementação independente de plataforma decente. (Uma das razões pelas quais não queremos mudar a matemática / grande totalmente para o uso de matemática / bits é que atualmente temos alguma montagem específica da plataforma em matemática / grande que é mais rápida.)

No meu prato, pelo menos para os arcos, já fazemos intrínsecos para
(386, amd64, arm, arm64, s390x, mips, provavelmente ppc64).

Na sexta-feira, 17 de fevereiro de 2017 às 12h54, Robert Griesemer < notificaçõ[email protected]

escreveu:

@cespare https://github.com/cespare Depende se chegarmos a ele (
@khr https://github.com/khr ?). Independentemente disso, queremos ter um
implementação independente de plataforma decente. (Uma das razões pelas quais não fazemos
queremos mudar matemática / grande inteiramente para o uso de matemática / bits é que atualmente nós
tem alguma montagem específica da plataforma em matemática / grande que é mais rápida.)

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/golang/go/issues/18616#issuecomment-280763679 ou mudo
o segmento
https://github.com/notifications/unsubscribe-auth/AGkgIIb8v1X5Cr-ljDgf8tQtT4Dg2MGiks5rdgkegaJpZM4Lg5zU
.

Este artigo sobre a maneira mais rápida de implementar a contagem de população em x86-64 pode ser útil: A montagem codificada manualmente supera os intrínsecos em velocidade e simplicidade - Dan Luu, outubro de 2014

CL https://golang.org/cl/38155 menciona esse problema.

CL https://golang.org/cl/38166 menciona esse problema.

CL https://golang.org/cl/38311 menciona esse problema.

CL https://golang.org/cl/38320 menciona esse problema.

CL https://golang.org/cl/38323 menciona esse problema.

Mais alguma discussão:

Mim:

math / bits.RotateLeft está atualmente definido para entrar em pânico se seu argumento for menor que zero.
Eu gostaria de mudar isso para definir RotateLeft para fazer uma rotação à direita se seu arg for menor que zero.

Ter uma rotina básica como essa parece desnecessariamente severo. Eu diria que uma rotação negativa é mais análoga a uma mudança maior do que o tamanho da palavra (o que não causa pânico) em vez de uma divisão por zero (o que causa pânico). Dividir por zero realmente causa pânico, pois não há um resultado razoável que você retornaria em seu lugar. Rotações por valores negativos têm um resultado perfeitamente bem definido que podemos retornar.

Acho que devemos manter RotateLeft e RotateRight como funções separadas, mesmo que agora uma possa ser implementada com a outra. O uso padrão ainda será com argumentos não negativos.

Se vamos fazer algo aqui, devemos congelar. Depois que o go 1.9 for lançado, não poderemos mudar de ideia.

Roubar:

Se você realmente quiser algo assim, eu teria apenas uma função, Girar, que leva um positivo para a esquerda e um negativo para a direita.

Mim:

O problema é
bits.Rotate (x, -5)

Não fica claro ao ler este código se acabamos girando para a esquerda ou para a direita.
bits.RotateRight (5)
é muito mais claro, mesmo que signifique que há duas vezes mais funções Rotate * em matemática / bits.

Michael Jones:

A rotação assinada significa saltos no código, sim? Parece uma pena.

Mim:

Não, a implementação é realmente mais simples com a semântica proposta. Mascare os 6 bits baixos (para rotações de 64 bits) e funciona.

Eu prefiro o único Rotate com um argumento assinado porque significa metade do número de funções e pode ser feito de forma muito eficiente em todos os casos, sem pânico ou mesmo um desvio condicional.

Rodar é uma função especializada de qualquer maneira, então as pessoas que a usam certamente ficarão confortáveis ​​em serem solicitadas a lembrar que um argumento positivo muda para a esquerda e um argumento negativo muda para a direita.

Você sempre pode dividir a diferença e fornecer apenas RotateLeft com um argumento assinado. Isso fornece um mnemônico prático para a direção, mas evita funções duplicadas.

@bcmills @robpike veja também a discussão anterior sobre este tópico exato começando em https://github.com/golang/go/issues/18616#issuecomment -275598092 e continuando para alguns comentários

@josharian Eu vi os comentários e ainda prefiro a minha versão. Isso pode ser complicado para sempre, mas estou tentando ser simples de implementar, simples de definir, simples de entender e simples de documentar. Acredito que uma única função Rotate com um argumento assinado satisfaça tudo isso, exceto para a necessidade de definir o sinal, mas a esquerda é positiva deve ser intuitiva para qualquer pessoa capaz de usar uma instrução rotate.

mas essa esquerda é positiva deve ser intuitiva para qualquer pessoa capaz de usar uma instrução de rotação.

Gosto de me considerar capaz de usar uma instrução de rotação e minha intuição é "Nossa, por que isso não diz em que direção é? Provavelmente saiu, mas vou ter que olhar a documentação para ter certeza ." Eu concordo que é intuitivo que se você fosse escolher uma direção positiva, seria a rotação para a esquerda, mas há um limite muito mais alto para algo ser tão obviamente a resposta certa que fica claro em cada site de chamada qual direção você está girando sem dizer isto.

No que diz respeito à legibilidade, que tal algo parecido com a API time.Duration :

const RotateRight = -1

bits.Rotate(x, 5 * RotateRight)

Talvez uma constante definida por bits ou talvez um exercício para o leitor (ligue para o site)?

@aclements E assim você acaba com duas (vezes N tipos) funções com capacidade idêntica, cuja única diferença é um sinal no argumento. Agora nós toleramos isso para Add e Sub, mas esse é o único exemplo que posso pensar.

No eixo dos números, os números positivos aumentam para a direita, então espero que uma rotação / mudança de direção definida por sinal mova os bits para a direita para números positivos.

Não tenho nenhum problema se vai ser o oposto [documentado], mas não chamaria isso de intuitivo.

Os bits

Também sou a favor de apenas Rotate (https://github.com/golang/go/issues/18616#issuecomment-275016583) se fizermos com que funcione nas duas direções.

Como um contra-argumento à preocupação de @aclements sobre a direção: fornecer um RotateLeft que funcione também ao girar para a direita também pode fornecer uma falsa sensação de segurança: "Diz RotateLeft então certamente não está girando direito!". Em outras palavras, se ele disser RotateLeft é melhor não fazer mais nada.

Além disso, o uso de bits.Rotate é realmente apenas no código de especialista. Esta não é uma função usada por um grande número de programadores. Os usuários que realmente precisam entenderão a simetria das rotações.

@nathany

bits são escritos da direita para a esquerda embora

Os bits são apenas dígitos binários. Os dígitos numéricos em qualquer base são escritos da esquerda para a direita - mesmo na maioria, senão em todos, os sistemas de escrita da direita para a esquerda. 123 é cento e vinte e três, não trezentos e vinte e um.

Que a potência do multiplicando para o dígito diminui para a direita é uma coisa diferente.

Mais uma vez: não me importo com a direção, só que a intuitiva é uma questão de imaginação pessoal.

Eu gosto de girar. O bit menos significativo é intuitivamente 0 o suficiente na minha opinião.

Por favor, mantenha RotateLeft e RotateRight ao invés de fazer algo que metade dos desenvolvedores irá se lembrar mal. No entanto, parece bom lidar com números negativos.

99% dos desenvolvedores nunca irão programar uma instrução de rotação, então a necessidade de direção inequívoca é fraca na melhor das hipóteses. Uma única ligação é o suficiente.

O problema que despertou essa discussão é que ter os dois exige discutir se os valores negativos são OK e, se não, o que fazer com eles. Por ter apenas um, todo o argumento se desfaz. É um design mais limpo.

Eu simpatizo um pouco com o argumento sobre design limpo, mas parece estranho que você tenha que remover "Right" de "RotateRight", mantendo a mesma implementação, a fim de alcançá-lo. Em termos práticos, parece que a única maneira de responder às perguntas é forçando as pessoas que o veem a ler a documentação, por meio das perguntas que o nome levanta.
No final, é uma questão de direção inequívoca versus comportamento inequívoco para valores negativos. Valores negativos provavelmente devem ser menos preocupantes no caso comum.

O que estou dizendo é que o Rotate levanta uma questão para todas as pessoas, respondendo-a indiretamente por meio de documentação.
RotateRight levanta uma questão para muito poucas pessoas que podem (e devem) ler a documentação se se importarem com ela.

Por outro lado, Girar provavelmente evitaria que as pessoas escrevessem if n < 0 { RotateLeft(...) } else { RotateRight(...) } .

@ golang / proposal-review discutiu isso e acabou tendo apenas uma função, mas nomeando-a RotateLeft , não apenas Rotate , para ser mais claro nos sites de chamadas. Os números negativos giram para a direita e a documentação deixará isso claro.

CL https://golang.org/cl/40394 menciona esse problema.

CL https://golang.org/cl/41630 menciona esse problema.

A proposta original mais algumas funções extras foram projetadas e implementadas neste ponto. Podemos adicionar itens a esta biblioteca com o tempo, mas ela parece razoavelmente "completa" por enquanto.

Mais notavelmente, não decidimos ou implementamos funcionalidades para:

  • testar se add / mul / etc estouro
  • fornecem funções para implementar add / mul / etc que aceitam um transporte de entrada e produzem um resultado mais transporte (ou palavra superior)

Pessoalmente, não estou convencido de que pertençam a um pacote de "bits" (talvez os testes façam). Funções para implementar multi-precisão add / sub / mul permitiriam uma implementação Go pura de alguns dos math / big kernels, mas não acredito que a granularidade esteja certa: o que queremos é kernels otimizados trabalhando em vetores, e no máximo desempenho para esses kernels. Não acredito que possamos conseguir isso com o código Go, dependendo apenas de add / sub / mul "intrínsecos".

Assim, por ora, gosto de encerrar esse problema como "pronto", a menos que haja grandes objeções. Por favor, fale durante a próxima semana ou depois se você for contra o encerramento.

Sou a favor de adicionar funções nesse sentido.

Acredito fortemente que eles pertencem ao seu próprio pacote, pelo menos para dar-lhe um nome que melhor reflita sua funcionalidade coletiva.

: +1: ao fechar este número e: coração: pelo trabalho realizado até agora.

Fechando por não haver objeções.

Este é um comentário para futuras decisões sobre API, entendo que este em particular está definido.

Rotate é uma função especializada; LTR ou RTL só é relevante dado o contexto. @aclements trouxe à tona uma questão válida, não um ponto válido sempre

Mas em vez disso, segue-se a inteligência.

O que "uma função de especialista" significa é uma coisa tão simples, que provavelmente foi dispensada rapidamente. Dado um exemplo de código, provavelmente já se entende a rotação e a direção antes mesmo de encontrar a linha de código. Esse código geralmente já é precedido por documentação ascii ilustrativa da forma como está.

O que é mentalmente turbulento não é que Go pudesse simplesmente ter escolhido RTL como uma forma padrão de interpretar bits de uma perspectiva de API, mas em vez disso, primeiro puxei as alterações de 1.9 e encontrei um RotateLeft sem contrapartida e o documento dando um exemplo de um passo negativo. Esta é uma decisão entorpecente, semelhante à de um comitê, que é muito lamentável de ser tomada em 1.9.

Eu apenas imploro para me ater ao contexto de uso para o futuro. Tudo isso deveria ser auto-evidente com perguntas como "por que não estamos fornecendo uma contraparte para RotateLeft, por que estamos entrando em pânico com passadas negativas ou debatendo int vs uint para uma passada"; em última análise, porque acho que o que "uma função de especialista" significa foi simplesmente descartado facilmente por não ser inteligente.

Vamos, por favor, evitar esperteza em nossa justificativa de APIs. Mostra nesta atualização 1.9.

Alterar https://golang.org/cl/90835 menciona este problema: cmd/compile: arm64 intrinsics for math/bits.OnesCount

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