Go: math / bits: una biblioteca de giro de bits enteros

Creado en 11 ene. 2017  ·  168Comentarios  ·  Fuente: golang/go

Discusiones anteriores en https://github.com/golang/go/issues/17373 y https://github.com/golang/go/issues/10757.

Abstracto

Esta propuesta presenta un conjunto de API para la manipulación de bits enteros.

Fondo

Esta propuesta presenta un conjunto de API para la manipulación de bits enteros. Para esta propuesta nos interesan las siguientes funciones:

  • ctz: cuenta los ceros finales.
  • clz - cuenta los ceros iniciales; log_2.
  • popcnt - conteo de población; distancia de martilleo; paridad entera.
  • bswap: invierte el orden de los bytes.

Estas funciones fueron seleccionadas por topografía:

Nos limitamos a estas cuatro funciones porque otros juegos
Los trucos son muy simples de implementar usando la biblioteca propuesta,
o construcciones Go ya disponibles.

Encontramos implementaciones para un subconjunto de las funciones giratorias seleccionadas
en muchos paquetes, incluido el tiempo de ejecución, el compilador y las herramientas:

| paquete | clz | ctz | popcnt | bswap |
| --- | --- | --- | --- | --- |
| matemáticas / grande | X | X | | |
| runtime / internal / sys | X | X | | X |
| herramientas / contenedor / intsets | X | | X | |
| cmd / compile / internal / 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 | |
| Incorporados GCC | X | X | X | X |

Muchos otros paquetes implementan un subconjunto de estas funciones:

Del mismo modo, los proveedores de hardware han reconocido la importancia
de tales funciones y soporte de nivel de máquina incluido.
Sin soporte de hardware, estas operaciones son muy caras.

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

Todas las funciones de giro de bits, excepto popcnt, ya están implementadas por runtime / internal / sys y reciben soporte especial del compilador para "ayudar a obtener el mejor rendimiento". Sin embargo, el soporte del compilador se limita al paquete de tiempo de ejecución y otros usuarios de Golang tienen que volver a implementar la variante más lenta de estas funciones.

Propuesta

Introducimos una nueva biblioteca estándar math/bits con la siguiente API externa, para proporcionar implementaciones optimizadas de compilador / hardware de las funciones clz, ctz, popcnt y 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

Razón fundamental

Las alternativas a esta propuesta son:

  • Las funciones de giro de bits se implementan en una biblioteca externa no admitida por el compilador. Este enfoque funciona y es el estado actual de las cosas. El tiempo de ejecución usa los métodos compatibles con el compilador, mientras que los usuarios de Golang continúan usando las implementaciones más lentas.
  • La biblioteca externa es compatible con el compilador. Dado que esperamos que esta biblioteca reemplace el tiempo de ejecución / internal / sys, significa que esta biblioteca debe estar bloqueada con el compilador y vivir en la biblioteca estándar.

Compatibilidad

Esta propuesta no cambia ni rompe ninguna API stdlib existente y cumple con las pautas de compatibilidad.

Implementación

SwapBytes, TrailingZeros y LeadingZeros ya están implementados. La única función que falta son las que se pueden implementar de manera similar a las otras funciones. Si se acepta esta propuesta, se puede implementar a tiempo para Go1.9.

Problemas abiertos (si corresponde)

Los nombres son difíciles, el cobertizo para bicicletas está en los comentarios.

Sugiera funciones adicionales para incluirlas en los comentarios. Idealmente, incluya dónde se usa dicha función en stdlib (por ejemplo, math / big), herramientas o paquetes populares.

Hasta ahora se han propuesto las siguientes funciones y se están considerando:

  • Rotar a la izquierda / Rotar a la derecha

    • Ventajas: & 63 ya no es necesario compilar una rotación con argumento no constante en una sola instrucción en x86, x86-64 ..

    • Contras: muy corto / sencillo de implementar y en línea.

    • Usado: cripto / usa la rotación constante que es manejada correctamente por el compilador.

  • ReverseBits

    • Pros:?

    • Contras: ?

    • Usó: ?

  • Add / Sub con devolución de acarreo

    • Ventajas: Caro de lo contrario

    • Contras: ?

    • Usado: matemáticas / grande

Historia

14.Ene: se aclaró la salida de TrailingZeros y LeadingZeros cuando el argumento es 0.
14.Ene: Métodos renombrados: CountTrailingZeros -> TrailingZeros, CountLeadingZeros -> LeadingZeros, CountOnes -> Ones.
13.Ene: Nombre de la arquitectura fija.
11 de enero: propuesta inicial abierta al público.

FrozenDueToAge Proposal-Accepted

Comentario más útil

Las personas que escriben código quieren rotar a la izquierda o a la derecha. No quieren rotar ni a la izquierda ni a la derecha, según. Si solo proporcionamos rotar a la izquierda, entonces cualquiera que quiera rotar a la derecha tiene que escribir una expresión para convertir su rotación a la derecha en una rotación a la izquierda. Este es el tipo de expresión que las computadoras pueden hacer trivialmente, pero los programadores cometerán errores. ¿Por qué hacer que los programadores lo escriban ellos mismos?

Todos 168 comentarios

@brtzsnr ¿ quizás debería enviar este documento al repositorio de propuestas como se describe en los pasos del proceso de propuesta ?

Dado que ya está marcado siguiendo la plantilla, debería ser fácil de copiar y pegar en un CL creando un diseño de archivo / 18616-bit-twiddling.md (o lo que sea).

@cespare de https://github.com/golang/proposal "si el autor quiere escribir un documento de diseño, entonces puede escribir uno". Comenzó como un documento de diseño, si hay un fuerte sentimiento de que debería enviar esto, estoy totalmente bien.

Estaría bien con esto, es una funcionalidad bastante común que se usa en muchas bibliotecas algorítmicas y math / bits parece un lugar apropiado.

(Por un lado, math / big también implementa nlz (== clz).)

Probablemente haya un derrame de bicicletas sobre los nombres. Por mi parte, preferiría que las funciones digan lo que devuelven en lugar de lo que hacen; lo que a su vez puede dar lugar a nombres más cortos. Por ejemplo:

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

Etcétera.

La propuesta parece bastante clara y mínima: un documento de diseño parece excesivo. Creo que un CL sería más apropiado en este momento.

(Se trata de una CL con API e implementación básica, para fines de discusión en lugar de un documento de diseño. Aún debemos decidir si esta propuesta debe aceptarse o no).

@brtzsnr ya ha escrito el documento de diseño: está en la descripción del problema y sigue la plantilla . Supuse que valía la pena tener todos estos documentos en un solo lugar.

El último arco que aparece en la tabla de soporte de hardware es "BSWAP" - ¿error tipográfico?

Gracias por escribir esto.

La cadena doc para ctz y clz debe especificar el resultado cuando se pasa 0.

También prefiero (por ejemplo) TrailingZeros32 a CountTrailingZeros32. También estaría feliz con Ctz32. Es conciso, familiar para la mayoría y fácil de buscar en Google para el resto.

Gracias por la propuesta.
Solo quiero agregar que probablemente también queremos enfocarnos en el uso, en lugar de enfocarnos solo en las instrucciones. Por ejemplo, @ dr2chase una vez encontrado hay varias funciones log2 en el compilador / ensamblador. Está cerca de CLZ pero no es lo mismo. Funciones como esta probablemente también deberían estar en el paquete bit twiddling. Y quizás también incluya funciones útiles que no estén relacionadas con estas instrucciones.

¿Qué tal si proporcionamos un paquete para todas las primitivas de giro de bits definidas por
la delicia del hacker?

Al diseñar el paquete, no necesitamos considerar si la función
puede ser intrínseco o no. La optimización puede ocurrir más tarde. Es decir,
no permita que la implementación de bajo nivel controle el paquete de nivel superior
interfaz. Queremos una buena interfaz de paquete, incluso si algunos de ellos no pueden
ser mapeado a una sola instrucción.

@minux , afortunadamente, todas las funciones que he necesitado hasta ahora son exactamente las que se encuentran en esta propuesta.

Seguir Hacker's Delight tiene la ventaja de que no necesitamos perder el tiempo discutiendo sobre los nombres.

Me gustaría agregar lo siguiente:

ReverseBits (para uint32 y uint64)
RotateLeft / Right (se puede expandir en línea con dos turnos, pero el compilador
no siempre puede hacer la transformación debido a problemas de rango de cambio)

¿Quizás también dos resultados de sumar y restar? P.ej

func AddUint32 (x, y, carryin uint32) (carryout, sum uint32) // carryin debe
ser 0 o 1.
Y de manera similar para uint64.

Y SqrtInt.

Muchas de mis sugerencias no se pueden implementar en una sola instrucción, pero
ese es el punto: queremos un buen paquete de juegos, no un mero
paquete intrisics. No dejes que el hardware limite el paquete de alto nivel
interfaz.

De manera relacionada, funciones para verificar si una suma / multiplicación se desbordará.

Un punto de datos relacionado: hay varios problemas que se han presentado para un cgo más rápido. En uno de esos ejemplos (propuesta n. ° 16051), el hecho de que la rápida implementación de bsr / ctz / etc. Podría suceder se mencionó como una reducción del conjunto de casos de uso en los que las personas que escriben go están tentadas a usar cgo.

@aclements comentó el 1 de julio de 2016:

@eloff , ha habido alguna discusión (aunque no tengo propuestas concretas que yo sepa) para agregar funciones para cosas como popcount y bsr que se compilarían como intrínsecas donde sean compatibles (como lo es hoy en día math.Sqrt). Con 1.7 estamos probando esto en el tiempo de ejecución, que ahora tiene un ctz intrínseco disponible en amd64 SSA. Obviamente, eso no resuelve el problema general, pero eliminaría una razón más para usar cgo en un contexto de baja sobrecarga.

Mucha gente (incluyéndome a mí) se siente atraída por ir debido a la actuación, por lo que cosas como esta propuesta actual de juegos de azar ayudarían. (Y sí, cgo ahora también es más rápido en 1.8, lo cual también es bueno).

RotateLeft / Right (se puede expandir en línea con dos turnos, pero el compilador
no siempre puede hacer la transformación debido a problemas de rango de cambio)

@minux ¿Puedes explicarnos cuál es el problema?

@brtzsnr : Creo que a lo que se refiere Minux es a que cuando escribes (x << k) | (x >> (64-k)) , sabes que estás usando 0 <= k < 64 , pero el compilador no puede leer tu mente, y obviamente no es derivable del código. Si tuviéramos la función

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

Entonces podemos asegurarnos (a través de & 63) que el compilador sabe que el rango de k está acotado.
Entonces, si el compilador no puede probar que la entrada está acotada, entonces necesitamos un Y adicional. Eso es mejor que no generar el conjunto giratorio en absoluto.

El viernes 13 de enero de 2017 a las 10:37 p.m., Keith Randall [email protected]
escribió:

@brtzsnr https://github.com/brtzsnr : Creo que a lo que se refiere Minux
es que cuando escribes (x << k) | (x >> (64-k)), sabes que estás usando 0
<= k <64, pero el compilador no puede leer tu mente, y obviamente no es
derivable del código. Si tuviéramos la función

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

Entonces podemos asegurarnos (a través de & 63) que el compilador sabe que el rango
de k está acotado.
Entonces, si el compilador no puede probar que la entrada está acotada, entonces necesitamos un extra
Y. Eso es mejor que no generar el conjunto giratorio en absoluto.

Derecha. Si definimos las funciones RotateLeft y RotateRight, formalmente podemos
defina la función rotar k bits a la izquierda / derecha (no importa lo que sea k). Este es
similar a cómo se definen nuestras operaciones de turno. Y esta definición también
mapas a la instrucción de rotación real muy bien (a diferencia de los turnos, donde nuestros más
la definición intuitiva requiere una comparación en ciertas arquitecturas).

¿Qué hay de las funciones de reproducción aleatoria (y desactivación) de bytes y bits que utiliza la biblioteca de compresión blosc ? Las diapositivas (la mezcla comienza en la diapositiva 17). Estas funciones pueden acelerarse SSE2 / AVX2.

El viernes 13 de enero de 2017 a las 11:24 p.m., opennota [email protected] escribió:

¿Qué hay de las funciones de mezcla de bytes y bits que utiliza blosc?
https://github.com/Blosc/c-blosc biblioteca de compresión? Las diapositivas
http://www.slideshare.net/PyData/blosc-py-data-2014 (el barajado
comienza desde la diapositiva 17). Estas funciones pueden acelerarse SSE2 / AVX2.

SIMD es un problema mayor y está fuera del alcance de este paquete. Es

17373.

Las funciones propuestas actuales tienen una implementación nativa de Go mucho más grande y desproporcionadamente más cara que la óptima. Por otro lado, rotar es fácil de escribir en línea de una manera que el compilador pueda reconocer.

@minux y también todos los demás: ¿Sabes dónde se usa rotar a la izquierda / derecha con un número no constante de bits rotados? crypto / sha256 usa rotar, por ejemplo, pero con un número constante de bits.

rotate es fácil de escribir en línea de una manera que el compilador pueda reconocer.

Es fácil para aquellos que están familiarizados con los componentes internos del compilador. Ponerlo en un paquete math / bits lo hace fácil para todos.

¿Sabe dónde se usa rotar a la izquierda / derecha con un número no constante de bits rotados?

Aquí hay un ejemplo de # 9337:

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

En cada invocación es un número constante de bits rotados cada vez, pero la función en sí no está actualmente en línea, por lo que se compila sin instrucciones de rotación. Una función de biblioteca de matemáticas / bits definitivamente ayudaría aquí.

El sábado 14 de enero de 2017 a las 5:05 a.m., Alexandru Moșoi [email protected]
escribió:

Las funciones propuestas actuales tienen una implementación nativa de Go mucho más grande
y desproporcionadamente más caro que el óptimo. Por otro lado rotar
es fácil de escribir en línea de una manera que el compilador pueda reconocer.

Como destaqué muchas veces en este número, esta no es la forma correcta de
diseñar un paquete Go. Se relaciona demasiado con el hardware subyacente. Lo que
want es un buen paquete de juegos que generalmente es útil. Si
las funciones se pueden expandir en una sola instrucción es irrelevante siempre que
ya que la interfaz API es bien conocida y generalmente útil.

@minux https://github.com/minux y también todos los demás: ¿Sabes?
¿Dónde se usa rotar a la izquierda / derecha con un número no constante de bits rotados?
crypto / sha256 usa rotar, por ejemplo, pero con un número constante de bits.

Incluso si en el problema real el número de bits de rotación es constante, el
Es posible que el compilador no pueda ver eso. Por ejemplo, cuando se almacena el recuento de turnos
en una matriz, o esconderse en el contador de bucle o incluso el llamador de un no en línea
función.

Un ejemplo simple del uso de un número variable de rotación es un interesante
implementación 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 El ejemplo parece una mala decisión de inliner si rot no está inline. ¿Intentó escribir la función como func rot(x, n) lugar de rot = func(x, n) ?

@minux : Estoy de acuerdo contigo. No estoy tratando de vincular la API a un conjunto de instrucciones en particular; el soporte de hardware es una buena ventaja. Mi objetivo principal es encontrar usos en el código real (no en el código de juguete) para comprender el contexto, cuál es la mejor firma y qué tan importante es proporcionar la función favorita de todos. La promesa de compatibilidad nos morderá más tarde si no lo hacemos correctamente ahora.

Por ejemplo: ¿Cuál debería ser la firma de agregar con devolución de acarreo? Add(x, y uint64) (c, s uint64) ? Mirando math/big probablemente también necesitemos Add(x, y uintptr) (c, s uintptr) .

El ejemplo parece una mala decisión de inliner si rot no está inline.

Si. Es parte de un error que se queja de la inserción. :)

¿Intentó escribir la función como func rot (x, n) en lugar de rot = func (x, n)?

No es mi código, y eso es parte del punto. Y de todos modos, es un código razonable.

Sería bueno garantizar (¿en la documentación del paquete?) Que las funciones de rotación e intercambio de bytes son operaciones de tiempo constante para que se puedan usar de forma segura en algoritmos criptográficos. Posiblemente algo en lo que pensar para otras funciones también.

El jueves 19 de enero de 2017 a las 11:50 a. M., Michael Munday [email protected]
escribió:

Sería bueno garantizar (¿en la documentación del paquete?) Que el
Las funciones de rotación y de intercambio de bytes son operaciones de tiempo constante para que
se puede utilizar de forma segura en algoritmos criptográficos. Posiblemente algo en lo que pensar
también para otras funciones.

La implementación trivial del intercambio de bytes es un tiempo constante, pero si el
la arquitectura no proporciona instrucciones de cambio variables, será difícil
para garantizar una implementación rotatoria en tiempo constante. Quizás Go nunca correrá
aunque en esas arquitecturas.

Dicho esto, también existe una posibilidad no despreciable de que el
La microarquitectura utiliza una palanca de cambios de ciclos múltiples, y no podemos garantizar
el tiempo constante rota en esas implementaciones.

Si se requiere un tiempo constante estricto, tal vez la única forma sea escribir ensamblado
(e incluso en ese caso, hace fuertes suposiciones de que todos los
instrucciones son en sí mismas un tiempo constante, que depende implícitamente de la
microarquitectura.)

Si bien entiendo la necesidad de tales garantías, en realidad está más allá
nuestro control.

Me inclino a estar de acuerdo con @minux. Si desea primitivas criptográficas de tiempo constante, deberían vivir en criptografía / sutil. crypto / sutil puede redirigir fácilmente a math / bits en plataformas donde esas implementaciones han sido verificadas. Pueden hacer otra cosa si se requiere una implementación más lenta pero constante.

Parece que vale la pena hacerlo. Dejando a @griesemer y @ randall77 al veterinario. El siguiente paso parece un documento de diseño registrado en el lugar habitual (veo el boceto de arriba).

Ahora que esta propuesta puede seguir adelante, deberíamos acordar los detalles de la API. Preguntas que veo en este momento:

  • que funciones incluir?
  • qué tipos manejar ( uint64 , uint32 solamente, o uint64 , uint32 , uint16 , uint8 , o uintptr )?
  • detalles de la firma (por ejemplo, TrailingZeroesxx(x uintxx) uint , con xx = 64, 32, etc., frente a TrailingZeroes(x uint64, size uint) uint donde el tamaño es uno de 64, 32, etc. - este último daría lugar a una API más pequeña y puede aún ser lo suficientemente rápido en la implementación)
  • algo de nombre en bicicleta (me gusta la terminología "Hacker's Delight", pero puede ser más apropiado deletrearla)
  • ¿Hay un lugar mejor que math / bits para este paquete?

@brtzsnr ¿Te gustaría seguir encabezando este esfuerzo? Estoy bien si desea tomar su diseño inicial e iterar sobre él en este número o si prefiere configurar un documento de diseño dedicado. Alternativamente, puedo tomar esto y empujarlo hacia adelante. Me gusta ver que esto entra durante la fase 1.9.

Prefiero los nombres xx deletreados, personalmente: bits. TrailingZeroes (uint64 (x), 32) es más engorroso y menos legible que bits. TrailingZeroes32 (x), imo, y ese esquema de nomenclatura funcionaría con la plataforma de tamaño variable uintptr más fácilmente (xx = Ptr?).

También lo haría más evidente si, por ejemplo, no incluyese uint8 en la primera versión pero lo añadieras más tarde; de ​​repente, habría muchas funciones nuevas xx = 8 en lugar de un montón de comentarios de documentos actualizados que añaden 8 a la lista de tamaños válidos con una nota en los documentos de lanzamiento que es fácil pasar por alto.

Si se utilizan los nombres deletreados, creo que los términos encantadores del Hacker deberían incluirse en las descripciones como un "también conocido como" para que las búsquedas (en la página o mediante el motor de búsqueda) encuentren fácilmente el nombre canónico si busca la ortografía incorrecta. .

Creo que matemáticas / bits es un buen lugar, es matemáticas con bits.

Debo notar que SwapBytes{16,32,64} es más consistente con las funciones en sync/atomic que SwapBytes(..., bitSize uint) .

Aunque el paquete strconv usa el patrón ParseInt(..., bitSize int) , hay más relación entre bits y atomic , que con strconv .

Tres razones por las que no me gusta SwapBytes (uint64, tamaño uint):

  1. la gente podría usarlo en casos donde el tamaño no es constante de tiempo de compilación (en
    al menos para el compilador actual),
  2. necesita muchas conversiones de tipos. Primero en convertir un uint32 a
    uint64 y luego convertirlo de nuevo.
  3. deberíamos reservar el nombre genérico SwapBytes para un futuro genérico
    versión de la función (cuando Go obtiene soporte genérico).

@griesemer Robert,

Parece que hay una clara preferencia por las firmas del formulario:

func fffNN(x uintNN) uint

lo que deja abierto qué NN apoyar. Centrémonos en NN = 64 con el propósito de continuar la discusión. Será sencillo agregar los tipos restantes según sea necesario (incluido uintptr).

Lo que nos lleva directamente a la propuesta original de @brtzsnr y las siguientes funciones (y sus respectivas variaciones para diferentes tipos), además de lo que la gente ha sugerido mientras tanto:

// 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

Es posible que también tengamos un conjunto de funciones de intercambio. Personalmente, soy escéptico acerca de estos: 1) Nunca he necesitado un SwapBits, y la mayoría de los usos de SwapBytes se deben al código que es compatible con endian cuando no debería serlo. Comentarios

// 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

Entonces puede haber un conjunto de operaciones con números enteros. Solo estarían en este paquete porque algunos de ellos (por ejemplo, Log2) pueden estar cerca de otras funciones en este paquete. Estas funciones podrían estar en otra parte. Quizás sea necesario ajustar el nombre del paquete bits . Comentarios

// 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 sugirió operaciones como AddUint32 etc. Voy a dejarlas fuera por ahora porque creo que son más difíciles de especificar correctamente (y hay más variedad). Podemos volver con ellos más tarde. Lleguemos a un consenso sobre lo anterior.

Preguntas:

  • ¿Alguna opinión sobre los nombres de las funciones?
  • ¿Alguna opinión sobre las operaciones de enteros?
  • ¿Alguna opinión sobre el nombre / ubicación del paquete?

Preferiría nombres de funciones largos. Go evita abreviar los nombres de las funciones. Los comentarios pueden decir sus apodos ("nlz", "ntz", etc.) para las personas que buscan el paquete de esa manera.

@griesemer , parece que tiene errores tipográficos en su mensaje. Los comentarios no coinciden con las firmas de funciones.

Yo uso SwapBits en aplicaciones de compresión. Dicho esto, ¿las arquitecturas principales proporcionan alguna capacidad para realizar la inversión de bits de manera eficiente? Estaba planeando hacer en mi propio 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 : firmas actualizadas (errores tipográficos corregidos). También preguntas actualizadas (la gente prefiere nombres explícitos de atajos).

A menos que haya soporte nativo para el intercambio de bits, votaría para omitir SwapBits . Puede ser un poco ambiguo solo por el nombre si los bits solo se intercambian dentro de cada byte o en todo el uint64.

¿Por qué excluir algo basándose únicamente en la disponibilidad de instrucciones? Bit inverso
tiene usos notables en FFT.

Para responder a su pregunta sobre la disponibilidad de instrucciones de bits inversos: arm has
la instrucción RBIT.

@griesemer En cuanto a las firmas de tipos de funciones como

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

¿ RotateLeftN etc toma uints porque es necesario para una fácil intrinización (cuando sea posible) o simplemente porque ese es el dominio? Si no hay una necesidad técnica fuerte, hay un precedente más sólido para tomar entradas, incluso si los valores negativos no tienen sentido. Si existe una fuerte necesidad técnica, ¿deberían estar unidos por el N apropiado para la regularidad?

Independientemente de OnesN y su tipo deberían devolver ints a menos que sea para que estas operaciones se puedan componer con otras operaciones de bit, en cuyo caso deberían devolver un uintN para la N. apropiada.

@jimmyfrasche Porque es el dominio. A la CPU no le importa. Si somethings es un int o un uint es irrelevante para la mayoría de las operaciones, excepto las comparaciones. Dicho esto, si lo convertimos en un int, tenemos que explicar que nunca es negativo (resultado), o que no debe ser negativo (argumento), o especificar qué se supone que debe hacer cuando es negativo (argumento para la rotación). En ese caso, podríamos salirse con la nuestra con solo una rotación, lo cual sería bueno.

No estoy convencido de que usar int facilite las cosas. Si se diseña correctamente, las uints fluirán a uints (ese es el caso en math / big) y no se necesitan conversiones. Pero incluso si se necesita una conversión, será gratuita en términos de costo de CPU (aunque no en términos de legibilidad).

Pero estoy feliz de escuchar / ver argumentos convincentes de lo contrario.

El recuento de bits de rotación debe ser uint (sin un tamaño específico) para que coincida con el
operador de turno del idioma.

La razón para no usar int es que habrá ambigüedad si
RotateRight -2 bits es igual a RotateLeft 2 bits.

@minux No hay ambigüedad cuando se define Rotate(x, n) para rotar x por n mod 64 bits: Rotar 10 bits a la izquierda es lo mismo que rotar 64-10 = 54 bits a la derecha, o (si permitimos argumentos int) por -10 bits (asumir un valor positivo significa rotar a la izquierda). -10 mod 64 = 54. Lo más probable es que obtengamos el efecto gratis incluso con una instrucción de CPU. Por ejemplo, las instrucciones ROL / ROR de 32 bits x86 ya solo miran los 5 bits inferiores del registro CL, ejecutando efectivamente mod 32 para rotaciones de 32 bits.

El único argumento real es la regularidad con el resto de stdlib. Hay numerosos lugares donde las uints tienen mucho sentido, pero en su lugar se usan ints.

Dado que math / big es una excepción notable, no tengo ningún problema con que sea otra, pero es algo a considerar.

Supongo que @minux significaba ambigüedad para alguien que estaba leyendo / depurando código en lugar de ambigüedad en la implementación, que es un argumento persuasivo para que tome uint.

¿Se debería nombrar bits.SwapBits con un sufijo bits cuando el nombre del paquete ya se llama bits? ¿Qué hay de bits.Swap?

Otra idea es bits.SwapN (v uint64, n int) en lugar de bits.SwapBytes donde n representa el número de bits para agrupar para el intercambio. Cuando n = 1, los bits se invierten, cuando n = 8, los bytes se intercambian. Un beneficio es que bits.SwapBytes ya no tiene que existir, aunque nunca he visto la necesidad de ningún otro valor de n, tal vez otros no estén de acuerdo.

Como se especificó anteriormente , LeadingZeros64 , TrailingZeros64 , Ones64 todos LGTM.

Un solo Rotate64 con un monto rotatorio firmado es atractivo. La mayoría de las rotaciones también serán en una cantidad constante, por lo que (si / cuando esto se convierta en algo intrínseco) no habrá costo de tiempo de ejecución por no tener funciones derecha / izquierda separadas.

No tengo una opinión sólida sobre la mezcla / intercambio / inversión de bits / bytes. Para invertir bits, bits.Reverse64 parece un mejor nombre. ¿Y quizás bits.ReverseBytes64 ? Encuentro Swap64 un poco confuso. Veo el atractivo de que Swap64 tome un tamaño de grupo, pero ¿cuál es el comportamiento cuando el tamaño del grupo no divide uniformemente 64? Si los únicos tamaños de grupo que importan son 1 y 8 , sería más sencillo darles funciones separadas. También me pregunto si algún tipo de manipulación general del campo de bits podría ser mejor, tal vez mirando las instrucciones de arm64 y amd64 BMI2 en busca de inspiración.

No estoy convencido de que las funciones matemáticas enteras pertenezcan a este paquete. Parece que pertenecen más a la matemática de paquetes, donde podrían usar funciones de bits en su implementación. Relacionado, ¿por qué no func Sqrt(x uint64) uint32 (en lugar de devolver uint64 )?

Aunque AddUint32 y los amigos son complicados, espero que los volvamos a visitar eventualmente. Junto con otras cosas, MulUint64 proporcionaría acceso a HMUL, que incluso el superminimalista RISC-V ISA proporciona como instrucción. Pero sí, introduzcamos algo primero; siempre podemos expandirnos más tarde.

bits parece claramente el nombre de paquete correcto. Estoy tentado a decir que la ruta de importación debería ser solo bits , no math/bits , pero no me siento muy bien.

bits parece claramente el nombre de paquete correcto. Estoy tentado de decir que la ruta de importación debería ser solo bits, no matemática / bits, pero no me siento muy bien.

En mi opinión, si fuera solo bits , (sin leer los documentos del paquete), esperaría que fuera algo similar al paquete bytes . Es decir, además de las funciones para operar en bits, esperaría encontrar un Reader y Writer para leer y escribir bits hacia / desde un flujo de bytes. Hacerlo math/bits hace que sea más obvio que es solo un conjunto de funciones sin estado.

(No propongo que agreguemos Reader / Writer para flujos de bits)

Un solo Rotate64 con una cantidad de rotación firmada es atractivo. La mayoría de las rotaciones también serán en una cantidad constante, por lo que (si / cuando esto se convierta en algo intrínseco) no habrá costo de tiempo de ejecución por no tener funciones derecha / izquierda separadas.

Puedo ver cómo podría ser conveniente tener una API de paquete un poco más pequeña combinando rotar izquierda / derecha en un solo Rotate , pero luego también está la cuestión de documentar de manera efectiva el parámetro n para indican que:

  • n negativo da como resultado un giro a la izquierda
  • cero n da como resultado ningún cambio
  • positivo n da como resultado un giro a la derecha

Para mí, la sobrecarga mental y de documentación adicional no parece justificar la combinación de los dos en un solo Rotate . Tener un RotateLeft explícito y RotateRight donde n es un uint parece más intuitivo.

@mdlayher Tenga en cuenta que para una palabra de n bits, girar k bits a la izquierda es lo mismo que girar nk bits a la derecha. Incluso si separa esto en dos funciones, debe comprender que girar k bits a la izquierda siempre significa girar (k mod n) bits (si gira más de n bits, comienza de nuevo). Una vez que haga eso, puede decir que la función de rotación siempre gira en k mod n bits y listo. No es necesario explicar el valor negativo o una segunda función. Simplemente funciona. En realidad, es más sencillo.

Además de eso, el hardware (por ejemplo, en x86) incluso lo hace automáticamente, incluso para k no constante.

@griesemer , una desventaja de Rotate es que no dice qué dirección es positiva. ¿ Rotate32(1, 1) igual a 2 o 0x80000000? Por ejemplo, si estuviera leyendo un código que usara eso, esperaría que el resultado fuera 2, pero aparentemente @mdlayher esperaría que fuera 0x80000000. Por otro lado, RotateLeft y RotateRight son nombres inequívocos, independientemente de si toman un argumento firmado o no firmado. (No estoy de acuerdo con @minux en que es ambiguo si RotateRight by -2 es igual a RotateLeft 2. Estos me parecen obviamente equivalentes y no veo de qué otra manera podría especificarlos).

@aclements que podrían arreglarse teniendo solo una función llamada RotateLeft64 y usando valores negativos para rotar a la derecha. O con docs. (FWIW, en su ejemplo, también esperaría 2, no 0x800000000).

@josharian , estoy de acuerdo, aunque parece un poco extraño tener un RotateLeft sin tener también un RotateRight . La consistencia de tener una rotación firmada parece potencialmente valiosa si se calcula la rotación, pero para las rotaciones constantes prefiero leer el código que dice "rotar a la derecha dos bits" que "rotar a la izquierda simplemente bromeando con dos bits negativos".

El problema de solucionar esto con los documentos es que los documentos no ayudan a la legibilidad del código que llama a la función.

Las personas que escriben código quieren rotar a la izquierda o a la derecha. No quieren rotar ni a la izquierda ni a la derecha, según. Si solo proporcionamos rotar a la izquierda, entonces cualquiera que quiera rotar a la derecha tiene que escribir una expresión para convertir su rotación a la derecha en una rotación a la izquierda. Este es el tipo de expresión que las computadoras pueden hacer trivialmente, pero los programadores cometerán errores. ¿Por qué hacer que los programadores lo escriban ellos mismos?

Estoy convencido.

@aclements @ianlancetaylor Punto tomado. (Dicho esto, la implementación de RotateLeft / Right probablemente solo llamará a una implementación de rotar).

Una idea de nombre es poner el tipo en el nombre del paquete en lugar del nombre de la firma. bits64.Ones lugar de bits.Ones64 .

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

Aunque @bradfitz golang.org/x/image/math/f64 y golang.org/x/image/math/f32 existen

No había pensado en atomic como un precedente (no en mi uso normal de Go, afortunadamente). Los otros ejemplos son diferentes porque los paquetes son más grandes que un conjunto de funciones repetidas para varios tipos diferentes.

La documentación es más fácil de ver (digamos, en godoc), cuando vas a bits64 y ves
Ones LeadingZeros TrailingZeros

En lugar de ir a bits y ver
Ones8 Ones16 Ones32 Ones64 LeadingZeros8 ...

@iand , eso también es bastante asqueroso. Supongo que el 32 y el 64 no se veían bien con todos los sufijos numéricos existentes para Aff, Mat y Vec. Aún así, diría que es una excepción en lugar del estilo Go normal.

Hay un precedente bastante sólido en Go para usar el estilo de nomenclatura pkg.foo64 en lugar de pkg64.foo.

La API del paquete se vuelve n veces más grande si tenemos n tipos, pero la complejidad de la API no. Un mejor enfoque para resolver esto podría ser a través de la documentación y cómo se presenta la API a través de una herramienta como go doc. Por ejemplo, en lugar 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 agrega una sobrecarga mental al mirar la API, podríamos presentarla 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 podría ser inteligente con las funciones que son similares en ese sentido y presentarlas como un grupo con una sola cadena de documentos. Esto también sería útil en otros paquetes.

Para resumir, creo que el esquema de nomenclatura propuesto parece estar bien y en la tradición de Go. El tema de la presentación es una pregunta separada que se puede discutir en otra parte.

+1 al poner el tamaño de bit en el nombre del paquete.

bits64.Log2 se lee mejor que bits.Log264 (creo que Log2 pertenece allí, y el nombre de los paquetes no es una buena razón para mantenerlo fuera)

Al final, es la misma API para cada tipo "parametrizado" por tipo escalar, por lo que si las funciones tienen el mismo nombre en todos los tipos, el código es más fácil de refactorizar con paquetes separados; simplemente cambie la ruta de importación (los reemplazos de palabras completas son trivial con gofmt -r pero las transformaciones de sufijos personalizados son más incómodas).

Estoy de acuerdo con @griesmeyer en que el sufijo de tamaño de bits en el nombre de la función puede ser idiomático, pero creo que vale la pena solo si hay un código no trivial en el paquete que sea independiente del tipo.

Podríamos robar una jugada de codificación / binario y crear vars llamadas Uint64, Uint32, ... que tendrían métodos que acepten los tipos predefinidos asociados.

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

@griesemer ¿cómo sabría agruparlos? ¿Una cadena de documentos con el nombre de la función que termina en N en la documentación en lugar del nombre real y luego encuentra un prefijo común entre las funciones sin cadenas de documentos que coincida con la incongruencia? ¿Cómo se generaliza eso? Parece un caso especial complicado para servir muy pocos paquetes.

@rogpeppe podría ser bits.Log64 y los documentos dicen que era base 2 (¿qué más sería en un paquete de bits?) Aunque es tan complicado como tener paquetes de 8/16/32/64 / ptr en un nivel, sí Hágalos cada uno más limpio individualmente. (También parece que su transmisión se cortó a mitad de la oración).

@nerdatmath esto sería ligeramente diferente ya que tendrían la misma interfaz en el sentido coloquial pero no en el sentido de Go, como es el caso de encoding / binary (ambos implementan binary.ByteOrder), por lo que las variables serían solo para el espacio de nombres y godoc no recogería ninguno de los métodos (# 7823) a menos que los tipos fueran exportados, lo cual es complicado de una manera diferente.

Estoy bien con los sufijos de tamaño, pero, en general, preferiría los paquetes separados. Sin embargo, no es suficiente para molestarse en dejarlo atrás en este comentario.

@nerdatmath si son vars, entonces son mutables (podrías hacer bits.Uint64 = bits.Uint8 ), lo que evitaría que el compilador las trate como intrínsecas, que fue una de las motivaciones para (al menos parte) del paquete.

Las variables no son realmente mutables si son de un tipo no exportado y no tienen estado (estructura vacía no exportada). Pero sin una interfaz común, el godoc (hoy) sería asqueroso. Sin embargo, se puede arreglar si esa es la respuesta.

Sin embargo, si todos terminan usando varios paquetes, si hay suficiente repetición y volumen para justificarlo, al menos tal vez nombre los paquetes como "X / bits / int64s", "X / bits / ints", "X / bits / int32s "para que coincida con" errores "," cadenas "," bytes ". Todavía parece una gran explosión de paquetes.

No creo que debamos ir por la ruta a varios paquetes de tipos específicos, tener un solo paquete de bits es simple y no aumenta la cantidad de paquetes en la biblioteca Go.

No creo que Ones64 y TrailingZeroes64 sean buenos nombres. No informan que devuelven un recuento. Agregar el prefijo Count hace que los nombres sean aún más largos. En mis programas, esas funciones a menudo están integradas en expresiones más grandes, por lo que los nombres de funciones cortos aumentarán la legibilidad. Aunque utilicé los nombres del libro "Hacker's Delight", sugiero seguir los mnemónicos del ensamblador de Intel: Popcnt64, Tzcnt64 y Lzcnt64. Los nombres son cortos, siguen un patrón consistente, informan que se devuelve un conteo y ya están establecidos.

Por lo general, los recuentos en Go se devuelven como ints, incluso cuando un recuento nunca puede ser negativo. El mejor ejemplo es la función incorporada len, pero Read, Write, Printf, etc. también devuelven enteros. Encuentro el uso del valor uint para un recuento bastante sorprendente y sugiero devolver un valor int.

Si la elección es entre (para abusar de la notación) math / bits / int64s.Ones y math / bits.Ones64, entonces definitivamente preferiría un solo paquete. Es más importante tener bits en el identificador del paquete que particionar por tamaño. Tener bits en el identificador del paquete facilita la lectura del código, lo cual es más importante que el pequeño inconveniente de la repetición en la documentación del paquete.

@ulikunitz Siempre puedes hacer ctz := bits.TrailingZeroes64 , y cosas por el estilo, en un código que usa mucho bits. Eso logra un equilibrio entre tener nombres autodocumentados globalmente y nombres compactos localmente para código complejo. La transformación opuesta, countTrailingZeroes := bits.Ctz64 , es menos útil ya que las propiedades globales agradables ya se han perdido.

Me meto con cosas a nivel de bits muy raramente. Tengo que buscar muchas de estas cosas cada vez, solo porque han pasado años desde que tuve que pensar en ello, así que encuentro todos los nombres estilo Hacker's Delight / asm exasperantemente crípticos. Preferiría que solo dijera lo que hizo para poder encontrar el que necesito y volver a la programación.

Estoy de acuerdo con @ulikunitz, los nombres largos me

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

Además, desafiaría eso bits.TrailingZeroes64 se autodocumenta. ¿Qué significa que un cero esté detrás? La propiedad de auto documentación existe con la suposición de que el usuario sabe algo sobre la semántica de la documentación para empezar. Si no usa Hacker's Delight para mantener la coherencia, ¿por qué no simplemente llamarlo ZeroesRight64 y ZeroesLeft64 para que coincida con RotateRight64 y RotateLeft64?

Para aclarar, no quise dar a entender que cada vez que lo use, lo primero que debe hacer es crear un alias corto y nunca debe usar el nombre de pila. Me refiero a que si lo estás usando repetidamente, puedes ponerle un alias, si eso mejora la legibilidad del código.

Por ejemplo, si estoy llamando strings.HasSuffix, uso el nombre de pila la mayor parte del tiempo porque lo llamo una o dos veces, pero, de vez en cuando, en un script ETL único o similar, lo haré Termino llamándolo un montón, así que haré ends := strings.HasSuffix que es más corto y hace que el código sea más claro. Está bien porque la definición del alias está al alcance de la mano.

Los nombres abreviados están bien para cosas que son extremadamente comunes. Muchos no programadores saben lo que significa sistema operativo. Cualquiera que lea código, incluso si no sabe Go, obtendrá que func es la abreviatura de función. Los canales son fundamentales para Go y se usan ampliamente, por lo que chan está bien. Las funciones de bits nunca serán extremadamente comunes.

Es posible que TrailingZeroes no pueda decirle exactamente lo que está sucediendo si no está familiarizado con el concepto, pero le da una idea mucho mejor de lo que está sucediendo, al menos aproximadamente, que Ctz.

@jimmyfrasche Con respecto a https://github.com/golang/go/issues/18616#issuecomment -275828661: sería bastante fácil para go / doc reconocer una secuencia contigua de funciones en el mismo archivo que tienen nombres que comienzan con tienen el mismo prefijo y tienen un sufijo que es solo una secuencia de números y / o quizás una palabra que es "corta" en relación con el prefijo. Lo combinaría con el hecho de que solo la primera de estas funciones tiene una cadena de documento y la otra con el prefijo coincidente no la tiene. Creo que podría funcionar bastante bien en entornos más generales. Dicho esto, discutamos esto en otro lugar para no secuestrar esta propuesta.

FYI, creó # 18858 para discutir los cambios go/doc y mantener esa conversación separada de esta.

@mdlayher gracias por hacer eso.

@rogpeppe Poner el tamaño en el nombre del paquete suena intrigante, pero a juzgar por los comentarios y dado el estilo Go existente, creo que la preferencia es poner el tamaño con el nombre de la función. Con respecto a Log2, diría que dejemos el 2 a un lado. (Además, repita si había algo más que quisiera agregar, sus comentarios aparecen cortados a mitad de la oración).

@ulikunitz Normalmente soy uno de los primeros en votar por nombres cortos; especialmente en el contexto local (y en el contexto global para los nombres de uso muy frecuente). Pero aquí tenemos una API de paquete y (al menos en mi experiencia) las funciones no aparecerán de manera generalizada en los clientes. Por ejemplo, math / big makes usa LeadingZeros, Log2, etc. pero son solo una o dos llamadas. Creo que un nombre descriptivo más largo está bien y, a juzgar por la discusión, la mayoría de los comentarios están a favor de eso. Si llama a una función con frecuencia, es barato envolverla dentro de una función con un nombre corto. La llamada adicional se incluirá en línea. FWIW, los mnemónicos de Intel tampoco son buenos, su énfasis está en "count" (cnt) y luego te quedan 2 letras que necesitan ser descifradas.

Estoy de acuerdo en que el 2 en Log2 no es necesario. bits.Log implica una base de 2.

bits Log64 SGTM.

@griesemer Mi

@griesemer Me pregunto si vale la pena continuar con el nombre de la bicicleta derramada. Lo mantengo breve: tenía dos argumentos: brevedad y claridad sobre la devolución de un número. Package big usa internamente nlz, no LeadingZeros. Estoy de acuerdo en que el número de usos de estas funciones es pequeño.

@ulikunitz Creo que la mayoría de los comentarios aquí están a favor de nombres de funciones claros que no sean atajos.

@ulikunitz , además:

Package big usa internamente nlz, no LeadingZeros.

Los nombres internos privados no exportados no tienen influencia aquí.

Todo lo que importa es la coherencia y la sensación estándar de la API pública.

Bikeshedding puede ser molesto, pero los nombres importan cuando estamos atrapados con ellos para siempre.

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

Subí https://go-review.googlesource.com/#/c/36315/ como una API inicial (parcialmente probada) (con implementación preliminar) para su revisión (en lugar de un documento de diseño).

Todavía estamos asegurándonos de que la API sea correcta, así que comente solo sobre la API (bits.go) por ahora. Para problemas menores (errores tipográficos, etc.), comente en el CL. Para problemas de diseño, comente sobre este problema. Seguiré actualizando CL hasta que estemos satisfechos con la interfaz.

Específicamente, no se preocupe por la implementación. Es básico y solo se ha probado parcialmente. Una vez que estemos satisfechos con la API, será fácil ampliar la implementación.

@griesemer Estoy seguro de que estás bien, pero si es útil, tengo una implementación de ensamblaje CLZ con pruebas https://github.com/ericlagergren/decimal/tree/ba42df4f517084ca27f8017acfaeb69629a090fb/internal/arith que puedes hacer robar / jugar con / lo que sea.

@ericlagergren Gracias por el enlace. Una vez que la API se ha asentado y tenemos pruebas en todas partes, podemos comenzar a optimizar la implementación. Lo tendré en cuenta. Gracias.

@griesemer , parece que la documentación de la API propuesta depende de que go / doc pueda documentar adecuadamente las funciones con el mismo prefijo pero con un sufijo entero.

¿El plan sería trabajar en eso también antes de 1.9?

18858, eso es!

@mdlayher Eso sería ideal. Pero no sería sorprendente ya que afecta "solo" a la documentación, no a la API (que debe seguir siendo compatible con versiones anteriores).

@griesemer @bradfitz Dedico algo de tiempo a repensar mi posición con respecto a los nombres de las funciones. Un objetivo importante de la elección de nombres debe ser la legibilidad del código mediante la API.

El siguiente código es realmente fácil de entender:

n := bits.LeadingZeros64(x)

Todavía tengo algunas dudas sobre la claridad de Ones64 (x), pero es consistente con LeadingZeros y TrailingZeros. Así que dejo mi caso y apoyo la propuesta actual. Como experimento, utilicé mi código existente para una implementación experimental pura de Go del paquete, incluidos los casos de prueba.

Repositorio: https://github.com/ulikunitz/bits
Documentación: https://godoc.org/github.com/ulikunitz/bits

Es posible que tengamos un conjunto de funciones Swap y la [...] mayoría de los usos de SwapBytes se deben al código que es compatible con endian cuando no debería. Comentarios

Me gustaría mantener SwapBytes fuera de la API por este motivo. Me preocupa que la gente comience a usarlo de formas no portátiles porque es más simple (o se supone que es más rápido) que binary/encoding.BigEndian .

Me gustaría mantener SwapBytes fuera de la API por este motivo. Me preocupa que la gente comience a usarlo de formas no portátiles porque es más simple (o se supone que es más rápido) que binary / encoding.BigEndian.

@mundaym, ¿estas rutinas alguna vez estarán intrinsificadas? Permitir que SwapBytes64 compile directamente como BSWAP podría superar a las personas que lo usan incorrectamente, en mi opinión.

Me preocupa que la gente comience a usarlo de formas no portátiles porque es más simple (o se supone que es más rápido) que la codificación binaria.

Creo que el uso de ReverseBytes solo no es portátil si se usa junto con unsafe donde se accede a los datos en el nivel bajo en la forma en que la máquina los coloca en la memoria. Sin embargo, el uso de encoding.{Little,Big}Endian.X es igual de no portátil cuando se usa de esa manera. Espero ReverseBytes para usar en compresión y estaría triste si no estuviera incluido.

@ericlagergren

¿Estas rutinas van a estar intrinsificadas alguna vez?

Sí, las funciones en encoding/binary que realizan inversiones de bytes utilizan patrones de código que son reconocidos por el compilador y están (o podrían estar) optimizados para instrucciones únicas (por ejemplo, BSWAP ) en plataformas que admiten datos no alineados accesos (por ejemplo, 386, amd64, s390x, ppc64le y probablemente otros).

Permitir que SwapBytes64 se compile directamente como BSWAP podría superar a las personas que lo usan incorrectamente, en mi opinión.

Me inclino a no estar de acuerdo. Puede haber usos legítimos de SwapBytes en el código que no reconoce el endianness, pero sospecho que se usará incorrectamente con más frecuencia de lo que se usa correctamente.

Tenga en cuenta que runtime / internal / sys ya ha optimizado las versiones intrínsecas de Go, ensamblador y compilador de ceros finales e intercambio de bytes, por lo que podemos reutilizar esas implementaciones.

@dsnet Interesante, ¿tienes algo específico en mente?

@aclements ¿Sabe dónde se usa el intrínseco de intercambio de bytes en el tiempo de ejecución? No he encontrado ningún uso fuera de las pruebas.

@mundaym , AFAIK, no se usa en el tiempo de ejecución. No tenemos que ser tan cuidadosos con las API internas, así que creo que lo agregamos al agregar ctz porque era fácil. :) (Popcount hubiera sido una mejor opción).

Todos: Solo un recordatorio para que se concentre en la API por ahora. La implementación en CL es simplemente una implementación trivial y lenta que nos permite usar estas funciones y probarlas. Una vez que estemos satisfechos con la API, podemos ajustar la implementación.

En esa nota: probablemente deberíamos incluir versiones para uintptr ya que no está garantizado (aunque es probable) que tenga el mismo tamaño que un uint32 o uint64 . (Tenga en cuenta que uint siempre es uint32 o uint64 ). Opiniones ¿Dejarlo para más tarde? ¿Cuáles serían buenos nombres? ( LeadingZerosPtr ?).

Deberíamos hacer uintptr ahora, de lo contrario, math / big no puede usarlo. Sufijo Ptr SGTM. Creo que también deberíamos hacer uint , de lo contrario, todo el código de llamada debe escribir código condicional alrededor del tamaño int para realizar la llamada sin conversión. Sin ideas para nombrar.

Ptr para uintptr, sin sufijo para uint. Unos, Unos8, Unos16, Unos32, Unos64, OnesPtr, etc.

Podría ser una minoría aquí, pero estoy en contra de agregar funciones que tomen uintptr.
math / big debería haber usado uint32 / uint64 desde el principio. (De hecho, yo
simplemente use uint64 para todas las arquitecturas y deje la optimización al
compilador.)

El problema es este:
math / big quiere usar el tamaño de la palabra nativa para un rendimiento óptimo,
sin embargo, aunque está cerca, uintptr no es la opción correcta.
no hay garantía de que el tamaño de un puntero sea del mismo tamaño que el
tamaño de la palabra nativa.
El ejemplo principal es amd64p32, y también podríamos agregar mips64p32 y
mips64p32le más tarde, que son mucho más populares para hosts MIPS de 64 bits.

Ciertamente, no quiero fomentar más usos de este tipo. uintptr es más
para punteros, no para enteros neutros de arquitectura.

Sin embargo, una solución es introducir un alias de tipo en la API (quizás
debe ser de marca, esto está en discusión).
escriba Word = uint32 // o uint64 en arquitecturas de 64 bits
// probablemente también necesitemos proporcionar algunas constantes ideales para Word como
número de bits, máscara, etc.

Y luego introducir funciones sin ningún tipo de prefijo para llevar Word.

De hecho, imagino que si math / bit proporciona los dos resultados add, sub, mul,
div; math / big se puede reescribir sin ningún ensamblaje específico de arquitectura.

matemáticas / grandes exportaciones type Word uintptr . Estoy de acuerdo en que podría haber sido un error, pero creo que la compatibilidad requiere que eso permanezca. Entonces, si queremos usar math / bits para implementar math / big, lo que creo que hacemos, necesitamos las API de uintptr.

Divagando un poco del tema, pero ¿no es el tamaño de la palabra nativa sin firmar simplemente uint? En cuyo caso, no tener sufijo para uint parece correcto.

No quisiera que un solo tipo, Word, fuera diferente en diferentes arquitecturas. Eso parece introducir mucha complejidad para la persona que llama y los documentos. Cumplir con los tipos existentes significa que simplemente puede usarlo. Tener un tipo que varía según las arquitecturas significa diseñar todo su código en torno a ese tipo o tener un montón de adaptadores en archivos protegidos con etiquetas de compilación.

FWIW, la implementación de math / big aún podría usar una API de bits basada en uint32 / 64 (si no hubiera una versión uintptr de las funciones).

@minux No estoy en contra del add, sub, mul, div de 2 resultados, pero hagámoslo en un segundo paso. Sin embargo, todavía necesitamos código ensamblador si queremos un rendimiento decente. Pero estoy feliz de que el compilador me convenza de lo contrario ...

Agregué las versiones uint y uintptr a la API. Eche un vistazo a la CL y comente. Sin embargo, no estoy muy contento con la proliferación de funciones.

@josharian El uint nativo puede tener un valor de 32 bits incluso en una máquina de 64 bits. No hay garantía. Me doy cuenta de que uintptr tampoco garantiza el tamaño de la palabra de máquina; pero en ese momento parecía la opción más sensata, si bien retrospectivamente equivocada. Quizás realmente necesitemos un tipo de Word.

Si la única necesidad legítima de las funciones uintptr es admitir math / big, tal vez la implementación de math / bits podría ir en un paquete interno: solo use las cosas uintptr en math / big y exponga el resto.

@jimmyfrasche , @griesemer ¿cómo afectaría eso a big.Int.Bits? es decir, ¿seguirá siendo de asignación cero?

uint también está mal. es de 32 bits en amd64p32.
En realidad, math / big podría haber usado uint en lugar de uintptr, pero en Go 1,
int / uint son siempre de 32 bits, lo que hace que uintptr sea la única solución posible
para matemáticas / big.Word.

Estamos diseñando un nuevo paquete, así que no creo que la compatibilidad sea de mucha
preocupación. math / big está usando el tipo incorrecto, pero eso es más un histórico
accidente. No llevemos el error más lejos.

No entiendo por qué usar un tipo unificado "Word" que es siempre el más
El tipo de entero eficiente para una arquitectura dada es más complicado que
utilizando uintptr. Para el código de usuario, puede tratarlo como un tipo mágico que es
ya sea de 32 bits o de 64 bits, según la arquitectura. Si puedes escribir
su código usando uintptr (cuyo ancho también depende de la arquitectura) en un
arquitectura de forma independiente, entonces creo que puede hacer lo mismo con Word.
Y Word es correcto en todas las arquitecturas.

Para la proliferación de la API, sugiero que dejemos fuera funciones para 8
y tipos de 16 bits en la primera pasada. Rara vez se utilizan y la mayoría
De todos modos, la arquitectura solo proporcionará instrucciones de 32 y 64 bits.

math / big define y exporta Word, pero a) no es directamente compatible con uintptr (es un tipo diferente), yb) el código que usa Word correctamente y no hace suposiciones sobre sus implementaciones podrá con cualquier tipo de implementación de Word . Podemos cambiarlo. No veo por qué debería afectar la garantía de compatibilidad de la API (aunque no lo he probado). De todos modos, dejemos esto a un lado. Podemos manejar eso por separado.

¿Podemos simplemente hacer todos los tipos de uint de tamaño explícito? Si conocemos el tamaño de uint / uintptr como una constante, podemos escribir trivialmente una instrucción if que llame a la función del tamaño correcto. Esa declaración if se plegará constantemente, y la función contenedora respectiva simplemente llama a la función dimensionada que se incluirá en línea.

Finalmente, ¿cuál es la solución correcta con respecto al tipo de Word (independiente de matemáticas / grande)? Por supuesto, podríamos definirlo en matemáticas / bits, pero ¿es ese el lugar correcto? Es un tipo específico de plataforma. ¿Debería ser una 'palabra' de tipo predeclarada? ¿Y por qué no?

Tener aquí cualquier firma de función (o nombre) que mencione directamente a uintptr parece un error. Se supone que la gente no debe hacer este tipo de tonterías con los punteros de Go.

Si debería haber funciones para un tipo de palabra de máquina natural, creo que ahora pueden referirse a int / uint. No usamos uint en math / big porque era de 32 bits en sistemas de 64 bits, pero desde entonces lo hemos arreglado. Y aunque math / big es un mundo coherente en sí mismo, en el que tiene sentido tener su propio tipo big.Word, este paquete es más una colección de rutinas de utilidad pick-and-choose. Definir un nuevo tipo en este contexto es menos convincente.

No sé si necesitamos variantes int / uint en absoluto. Si son comúnmente necesarios, definirlos en este paquete tiene más sentido que obligar a todos los que llaman a escribir declaraciones if. Pero no sé si se necesitarán comúnmente.

Para abordar el posible desajuste con math / big, existen al menos dos soluciones que no implican mencionar uintptr en la API aquí.

  1. Defina los archivos word32.go y word64.go en math / big con etiquetas de compilación y envoltorios que acepten uintptr que redirijan adecuadamente (y asegúrese de que la inserción del compilador haga lo correcto).

  2. Cambie la definición de big.Word por uint. La definición exacta es un detalle de implementación interno, aunque se expone en la API. Cambiarlo no puede romper ningún código excepto en amd64p32, e incluso allí parece muy poco probable que importe fuera de math / big.

@rsc : "No usamos uint en matemáticas / grande porque era de 32 bits en sistemas de 64 bits, pero desde entonces lo hemos arreglado". Ah, sí, me olvidé por completo del motivo de la elección de uintptr. El objetivo de los tipos Go uint / int era tener tipos enteros que reflejaran el tamaño de registro natural de una máquina. Intentaré cambiar big.Word a uint y ver qué pasa.

Todos: actualizado https://go-review.googlesource.com/#/c/36315/ para excluir las versiones de la función uintptr según la discusión anterior.

He actualizado tentativamente math / big para usar math / bits para ver el efecto (https://go-review.googlesource.com/36328).

Algunos comentarios:
1) Me inclino a obtener los resultados de las funciones Leading / TrailingZeros uint lugar de int . Estos resultados tienden a usarse para cambiar un valor en consecuencia; y math/big evidencia esto. También estoy a favor de hacer que Ones devuelva un uint , por coherencia. ¿Contra argumentos?

2) No soy fanático del nombre One , sin embargo es consistente con Leading / TrailingZeros . ¿Qué tal Population ?

3) Algunas personas se han quejado de que Log no encaja en este paquete. Log resulta ser equivalente al índice del msb (con -1 para x == 0). Entonces podríamos llamarlo MsbIndex (aunque Log parece mejor). Alternativamente, podríamos tener una función Len que es la longitud de una x en bits; es decir, `Log (x) == Len (x) -1).

Comentarios

No soy fanático del nombre One, sin embargo es consistente con Leading / TrailingZeros. ¿Qué hay de la población?

¿Por qué no el tradicional Popcount ?

Alternativamente, podríamos tener una función Len que es la longitud de una x en bits; es decir, `Log (x) == Len (x) -1).

Len o Length suena como un buen nombre y una buena idea.

1.

No soy fanático del nombre Uno, sin embargo es consistente con Leading /
TrailingZeros. ¿Qué hay de la población?

bits. ¿Cuenta?

@aclements ¿Contar qué?

La población puede ser el mejor nombre por razones históricas, pero en realidad no dice más que unos si no está familiarizado con el término y tiene el mismo problema, es simplemente Recuento: ¿Población de qué?

Es algo unidiomático, incluso dentro del paquete, pero tal vez CountOnes para darle un nombre claro y obvio.

@aclements ¿Contar qué?

En un paquete llamado "bits", no estoy seguro de qué más podría estar contando, excepto los bits establecidos, pero CountOnes también está bien y, obviamente, es más explícito. Agregar "Unos" también es paralelo a los "Ceros" en LeadingZeros / TrailingZeros .

Esa es la interpretación más obvia, pero todavía hay ambigüedad. Podría estar contando los bits no configurados o los bits totales (la última interpretación sería extremadamente improbable, pero sigue siendo una trampa potencial para alguien que lee código usando bits y que no está familiarizado con los conceptos involucrados y podría pensar que Count sin sufijo es como inseguro.

Tal vez algo como AllOnes o TotalOnes para reflejar Trailing / LeadingZeroes pero especifique que, a diferencia de esos, la posición no se tiene en cuenta.

AllOnes suena como si devuelve todos los unos (¿en una máscara de bits tal vez?), O tal vez una palabra que es todos unos.

CountOnes y TotalOnes parecen casi lo mismo, pero dado que el nombre más conocido de esta operación es "recuento de población", CountOnes parece preferible.

Subí una nueva versión (https://go-review.googlesource.com/#/c/36315/) con un par de cambios:

1) Cambié el nombre de Ones a PopCount : La mayoría de la gente no estaba muy entusiasmada con el nombre consistente pero algo monótono Ones . Todos los que vayan a usar este paquete sabrán exactamente lo que hace PopCount : es el nombre con el que se conoce comúnmente a esta función. Es un poco inconsistente con el resto, pero su significado se ha vuelto mucho más claro. Voy con Ralph Waldo Emerson en este ("Una consistencia tonta es el duende de las mentes pequeñas ...").

2) Cambié el nombre de Log a Len como en bits.Len . La longitud de bits de un número x es la cantidad de bits que se necesitan para representar x en representación binaria (https://en.wikipedia.org/wiki/Bit-length). Parece apropiado, y elimina la necesidad de tener Log aquí, que tiene una calidad que no es "un poco complicada". Creo que esto también es una victoria porque Len(x) == Log(x) + 1 dado cómo teníamos Log definidos. Además, tiene la ventaja de que el resultado ahora es siempre> = 0, y elimina algunas correcciones +/- 1 en la implementación (trivial).

En general, estoy bastante contento con esta API en este momento (es posible que deseemos agregar más funciones más adelante). La única otra cosa que creo que deberíamos considerar seriamente es si todos los resultados deben estar sin firmar. Como he señalado antes, los resultados de la función Cero inicial / final tienden a ser entradas para operaciones de cambio que deben estar sin firmar. Eso solo deja a Len y PopCount, que posiblemente también podrían devolver valores sin firmar.

Comentarios

Mi experiencia con math / big ha sido la frustración de que las funciones me obliguen a entrar en modo uint cuando no estoy cambiando. En matemáticas / grande / prime.go, escribí

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

a pesar de que s.bitLen () devuelve int no uint, porque no estaba seguro sin mirar, y tampoco estaba seguro de que algún CL futuro podría no cambiarlo para devolver uint, convirtiendo así el bucle for en un bucle infinito. Necesitar estar así de defensivo sugiere que hay un problema.

Las Uints son mucho más propensas a errores que las ints, por lo que las desaconsejamos en la mayoría de las API. Creo que sería mucho mejor si cada función en math / bits devolviera int y dejara que la conversión a uint se hiciera en la expresión de cambio, como es necesario en la mayoría de las expresiones de cambio de todos modos.

(No estoy proponiendo un cambio, pero creo que el cambio que requiere uint puede haber sido un error en retrospectiva. Tal vez podamos solucionarlo en algún momento. Sería bueno si no perjudicara a nuestras otras API).

Estoy a favor de devolver int después de grepping mi código para las llamadas de los ceros finales y las funciones popcount. La mayoría de las llamadas son declaraciones breves de variables y comparaciones de tipo int. Las llamadas en turnos requieren, por supuesto, el tipo uint, pero son sorprendentemente raras.

Ajustes menores subidos. Por +1 y comentarios, dejemos los resultados de los recuentos como int .

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

Dejé los recuentos de entrada para RotateLeft/Right como uint contrario, necesitaremos especificar qué sucede cuando el valor es negativo o no lo permite.

Finalmente, incluso podríamos dejar de lado Len después de todo, ya que LenN(x) == N - LeadingZerosN(x) . Opiniones

Si ya no hay comentarios importantes en este momento, sugiero que sigamos adelante con esta API y comprometamos una implementación inicial después de la revisión. Después de eso, podemos comenzar a ajustar la implementación.

En el siguiente paso, es posible que deseemos discutir si y qué otras funciones podríamos querer incluir, específicamente Add / Sub / etc.que consumen 2 argumentos y llevan, y producen un resultado y llevan .

@gri piensa en una función base 10 Len ? Solo sería ((N - clz(x) + 1) * 1233) >> 12

Es menos "un poco hacky" que la base 2, pero sigue siendo útil.
El viernes 10 de febrero de 2017 a las 5:03 p.m. Robert Griesemer [email protected]
escribió:

Ajustes menores subidos. Por +1 y comentarios dejemos los resultados de
cuenta como int.

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

Dejé los recuentos de entrada para RotateLeft / Right como uint, de lo contrario lo haremos
Es necesario especificar qué sucede cuando el valor es negativo o no lo permite.

Finalmente, incluso podríamos dejar de lado a Len después de todo, ya que LenN (x) == N -
LeadingZerosN (x). Opiniones

Si ya no hay comentarios importantes en este momento, sugiero que
avanzar con esta API y realizar una implementación inicial después
revisión. Después de eso, podemos comenzar a ajustar la implementación.

En el siguiente paso, es posible que deseemos discutir si y qué otras funciones podríamos
desea incluir, específicamente Add / Sub / etc.que consumen 2 argumentos y
llevar y producir un resultado y llevar.

-
Recibes esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/golang/go/issues/18616#issuecomment-279107013 , o silenciar
la amenaza
https://github.com/notifications/unsubscribe-auth/AFnwZ_QMhMtBZ_mzQAb7XZDucXrpliSYks5rbQjCgaJpZM4Lg5zU
.

También. Disculpe si me salgo del alcance de este problema, pero estaría a favor de la aritmética comprobada. Por ejemplo, AddN(x, y T) (sum T, overflow bool)

El viernes 10 de febrero de 2017 a las 8:16 p.m., Eric Lagergren [email protected]
escribió:

También. Disculpe si me salgo del alcance de este problema, pero estaría en
favor de la aritmética comprobada. Por ejemplo, AddN (x, y T) (suma T, overflow bool)

¿Estás hablando de desbordamiento firmado? o desbordamiento sin firmar (llevar / pedir prestado)?

Además, prefiero devolver overflow / carry / pedir prestado como tipo T, para simplificar
aritmética de precisión múltiple.

@ericlagergren La función (base 2) Len , aunque casi log2, sigue siendo en esencia una función de conteo de bits. Una función de base 10 Len es realmente una función de registro. No se puede negar que es útil, pero parece menos apropiado para este paquete.

Sí, creo que sería bueno obtener checked aritmética. Solo quería cerrar primero la API actual para que podamos avanzar en lugar de ir y venir. Parece que la mayoría de las personas que han comentado hasta ahora parecen felices con él.

Con respecto a las funciones Add, Sub: Estoy de acuerdo con @minux en que el acarreo debe ser del mismo tipo que el argumento. Además, no estoy convencido de que necesitemos nada más además de los argumentos de uint para estas funciones: el punto es que (en la mayoría de los casos) uint tiene el tamaño de registro natural de la plataforma, y ​​ese es el nivel en el que estas operaciones tienen más sentido.

Un paquete de matemáticas / comprobado podría ser un mejor hogar para ellos. checked.Add es más claro que bits.Add .

@minux Estaba pensando en aritmética comprobada firmada, así que desbordamiento.

@griesemer re: base 10 Len : ¡tiene sentido!

Si lo desea, puedo enviar una CL para aritmética comprobada. Me gusta la idea de @jimmyfrasche de tenerlo con un nombre de paquete separado.

Agregar / sub / mul puede o no pertenecer a bits , pero las matemáticas marcadas no son el único uso para estas operaciones. De manera más general, diría que se trata de operaciones aritméticas "amplias". Para add / sub, hay poca diferencia entre el resultado de un bit de acarreo / desbordamiento y un indicador booleano de desbordamiento, pero probablemente también querríamos proporcionar agregar con acarrear y restar con pedir prestado para que estos se puedan encadenar. Y para mul amplio, hay mucha más información en el resultado adicional que el desbordamiento.

Es una buena idea recordar las operaciones aritméticas marcadas / amplias, pero dejémoslas para la segunda ronda.

"Todos los que vayan a utilizar este paquete sabrán exactamente lo que hace PopCount"

He usado esa funcionalidad antes y de alguna manera no estaba familiarizado con el nombre de PopCount (aunque debería haberlo hecho porque estoy seguro de que pellizqué la implementación de Hacker's Delight, que usa "pop").

Sé que voy a la fiesta, pero "OnesCount" me parece considerablemente más obvio, y si se menciona la palabra "PopCount" en el comentario del documento, las personas que lo busquen lo encontrarán de todos modos.

Relacionado para aritmética marcada / amplia: # 6815. Pero sí, ¡entremos la primera ronda!

@griesemer escribió:

La función Len (base 2), aunque casi log2, sigue siendo en esencia una función de conteo de bits. Una función de base 10 Len es realmente una función de registro. No se puede negar que es útil, pero parece menos apropiado para este paquete.

Escribí un punto de referencia para comparar algunos enfoques al problema de la "longitud del dígito decimal" en octubre pasado, que publiqué en un resumen .

Aceptado como propuesta; probablemente habrá pequeños ajustes en el futuro, y eso está bien.

@rogpeppe : Cambió PopCount a OnesCount ya que también recibió 4 pulgares arriba (como mi sugerencia de usar PopCount ). Se hace referencia a "recuento de población" en la cadena de documentos.

Por @rsc , dejando fuera las operaciones aritméticas marcadas / amplias por ahora.

También según @rsc , todas las funciones de conteo devuelven valores de int y, para facilitar su uso (y teniendo en cuenta # 19113), utilice valores de int para los conteos de rotación.

Dejó las funciones LenN en aunque sean simplemente N - LeadingZerosN . Pero existe una simetría similar para RotateLeft / Right y tenemos ambos.

Se agregó una implementación ligeramente más rápida para TrailingZeroes y se completaron las pruebas.

En este punto, creo que tenemos una primera implementación utilizable. Revise el código en https://go-review.googlesource.com/36315 , especialmente la API. Me gustaría que me enviaran esto si todos estamos contentos.

Próximos pasos:

  • implementación más rápida (primaria)
  • agregar funcionalidad adicional (secundaria)

Estamos diseñando un nuevo paquete

@minux ¿ Te refieres a nuevas matemáticas / grandes? ¿Es posible seguir el proceso en alguna parte?

@TuomLarsen : @minux se refirió a math / bits con "nuevo paquete". Mencionó matemáticas / grande como un caso en el que se usarían matemáticas / bits. (En el futuro, sea más específico con sus comentarios para que no tengamos que buscar y adivinar a qué se refiere, gracias).

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

¿Habrá alguna intrinsificación asistida por el compilador en math / bits para Go 1.9?

@cespare Depende de si llegamos a él (@khr?). Independientemente de eso, queremos tener una implementación decente independiente de la plataforma. (Una de las razones por las que no queremos pasar de math / big por completo al uso de math / bits es que actualmente tenemos un ensamblaje específico de la plataforma en math / big, que es más rápido).

En mi plato, al menos para los arcos ya hacemos intrínsecos para
(386, amd64, arm, arm64, s390x, mips, probablemente ppc64).

El viernes 17 de febrero de 2017 a las 12:54 p.m., Robert Griesemer < [email protected]

escribió:

@cespare https://github.com/cespare Depende de si llegamos a él (
@khr https://github.com/khr ?). Independientemente de eso, queremos tener una
Implementación decente independiente de la plataforma. (Una de las razones por las que no
queremos cambiar completamente math / big al uso de math / bits es que actualmente
tener un ensamblaje específico de la plataforma en matemáticas / grande, que es más rápido).

-
Recibes esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/golang/go/issues/18616#issuecomment-280763679 , o silenciar
la amenaza
https://github.com/notifications/unsubscribe-auth/AGkgIIb8v1X5Cr-ljDgf8tQtT4Dg2MGiks5rdgkegaJpZM4Lg5zU
.

Este artículo sobre la forma más rápida de implementar el recuento de población en x86-64 puede ser útil: El ensamblaje codificado a mano supera lo intrínseco en velocidad y simplicidad - Dan Luu, octubre de 2014

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

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

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

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

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

Un poco más de discusión:

Me:

math / bits.RotateLeft está actualmente definido para entrar en pánico si su argumento es menor que cero.
Me gustaría cambiar eso para definir RotateLeft para hacer una rotación a la derecha si su arg es menor que cero.

Tener una rutina básica como esta parece innecesariamente severo. Yo diría que una rotación por una cantidad negativa es más análoga a un cambio por más grande que el tamaño de la palabra (que no entra en pánico) en lugar de una división por cero (que sí entra en pánico). Dividir por cero realmente tiene que entrar en pánico, ya que no hay un resultado razonable que devolvería en su lugar. Las rotaciones por cantidades negativas tienen un resultado perfectamente definido que podemos devolver.

Creo que deberíamos mantener RotateLeft y RotateRight como funciones separadas, aunque ahora una podría implementarse con la otra. El uso estándar seguirá siendo con argumentos no negativos.

Si vamos a hacer algo aquí, deberíamos hacerlo congelando. Una vez que salga la versión 1.9, no podemos cambiar de opinión.

Robar:

Si realmente quieres algo como esto, solo tendría una función, Rotar, que toma un positivo para la izquierda y un negativo para la derecha.

Me:

El problema es
bits Girar (x, -5)

No está claro al leer este código si terminamos girando hacia la izquierda o hacia la derecha.
bits Girar a la derecha (5)
es mucho más claro, incluso si significa que hay el doble de funciones Rotate * en math / bits.

Michael Jones:

Rotar firmado significa saltos en el código, ¿no? Parece una pena.

Me:

No, la implementación es más sencilla con la semántica propuesta. Enmascare los 6 bits bajos (para rotaciones de 64 bits) y simplemente funciona.

Prefiero la rotación simple con un argumento con signo porque significa la mitad del número de funciones y se puede hacer de manera muy eficiente en todos los casos sin pánico o incluso una rama condicional.

Rotar es una función especializada de todos modos, por lo que las personas que la usan seguramente se sentirán cómodas cuando se les pida que recuerden que un argumento positivo se desplaza a la izquierda y un argumento negativo se desplaza a la derecha.

Siempre puede dividir la diferencia y proporcionar solo RotateLeft con un argumento firmado. Eso proporciona un mnemónico útil para la dirección, pero evita la duplicación de funciones.

@bcmills @robpike ver también la discusión anterior sobre este tema exacto comenzando en https://github.com/golang/go/issues/18616#issuecomment -275598092 y continuando con algunos comentarios

@josharian He visto los comentarios y todavía prefiero mi versión. Esto se puede descartar para siempre, pero estoy tratando de ser simple de implementar, simple de definir, simple de entender y simple de documentar. Creo que una sola función Rotate con un argumento con signo satisface todo eso, excepto por la necesidad de definir el signo, pero la izquierda es positiva debería ser intuitiva para cualquier persona capaz de usar una instrucción de rotación.

pero esa izquierda es positiva debería ser intuitiva para cualquier persona capaz de usar una instrucción rotativa.

Me gusta considerarme capaz de usar una instrucción rotatoria y mi intuición es "Caray, ¿por qué esto no dice en qué dirección es? Probablemente esté a la izquierda, pero tendré que mirar la documentación para estar seguro ". Estoy de acuerdo en que es intuitivo que si eligieras una dirección positiva sería la rotación a la izquierda, pero hay un umbral mucho más alto para que algo sea tan obviamente la respuesta correcta que está claro en cada sitio de llamada en qué dirección estás girando sin decir eso.

En cuanto a la legibilidad, ¿qué tal algo similar a la API time.Duration :

const RotateRight = -1

bits.Rotate(x, 5 * RotateRight)

¿Quizás una constante definida por bits o quizás un ejercicio para el lector (llamar al sitio)?

@aclements Y así terminas con dos (veces N tipos) funciones con idéntica capacidad cuya única diferencia es un signo en el argumento. Ahora lo toleramos para Add y Sub, pero ese es el único ejemplo en el que puedo pensar.

En el eje numérico, los números positivos aumentan hacia la derecha, por lo que espero una rotación / desplazamiento definida en la dirección por signo para mover los bits a la derecha para los números positivos.

No tengo ningún problema si va a ser lo contrario [documentado], pero no lo llamaría intuitivo.

Sin embargo, los bits

También estoy a favor de solo Rotate (https://github.com/golang/go/issues/18616#issuecomment-275016583) si lo hacemos funcionar en ambas direcciones.

Como contraargumento a la preocupación de @aclements sobre la dirección: proporcionar un RotateLeft que funcione también cuando se gira a la derecha también puede proporcionar una falsa sensación de seguridad: "Dice RotateLeft así que seguramente no está girando ¡Derecha!". En otras palabras, si dice RotateLeft es mejor que no haga nada más.

Además, el uso de bits.Rotate está realmente solo en código de especialista. Esta no es una función que sea utilizada por un gran número de programadores. Los usuarios que realmente lo necesiten comprenderán la simetría de las rotaciones.

@nathany

aunque los bits se escriben de derecha a izquierda

Los bits son solo dígitos binarios. Los dígitos de cualquier base se escriben de izquierda a derecha, incluso en la mayoría, si no en todos, los sistemas de escritura de derecha a izquierda. 123 es ciento veintitrés, no trescientos veintiuno.

Que la potencia del multiplicando para el dígito disminuya hacia la derecha es una cosa diferente.

Una vez más: no me importa la dirección, es solo que la intuitiva es cuestión de imaginación personal.

Me gusta Rotar. El bit menos significativo es intuitivamente lo suficientemente 0 en mi opinión.

Mantenga tanto RotateLeft como RotateRight en lugar de hacer algo que la mitad de los desarrolladores no recordarán. Sin embargo, parece estar bien manejar números negativos.

El 99% de los desarrolladores nunca programarán una instrucción rotativa, por lo que la necesidad de una dirección inequívoca es débil en el mejor de los casos. Una sola llamada es suficiente.

El problema que volvió a despertar esta discusión es que tener ambos requiere discutir sobre si los valores negativos están bien, y si no, qué hacer con ellos. Al tener solo uno, todo ese argumento se desvanece. Es un diseño más limpio.

Simpatizo un poco con el argumento sobre el diseño limpio, pero parece extraño que tenga que eliminar "Right" de "RotateRight", manteniendo la misma implementación, para lograrlo. En términos prácticos, la única forma en que parece responder preguntas es obligando a las personas que lo ven a leer la documentación, a través de las preguntas que plantea el nombre.
Al final, se trata de una dirección inequívoca frente a un comportamiento inequívoco para los valores negativos. Los valores negativos probablemente deberían ser menos preocupantes en el caso común.

Lo que estoy diciendo es que Rotate plantea una pregunta para todas las personas, respondiéndola indirectamente a través de la documentación.
RotateRight plantea una pregunta para muy pocas personas que pueden (y deben) leer la documentación si les interesa.

Por otro lado, Rotate probablemente evitaría que la gente escriba if n < 0 { RotateLeft(...) } else { RotateRight(...) } .

@ golang / propuesta-review discutió esto y terminó teniendo solo una función, pero nombrándola RotateLeft , no solo Rotate , para ser más claro en los sitios de llamadas. Los números negativos rotan a la derecha y la documentación lo aclarará.

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

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

La propuesta original más algunas funciones adicionales se han diseñado e implementado en este punto. Podemos agregar a esta biblioteca con el tiempo, pero parece razonablemente "completa" por ahora.

Lo más notable es que no hemos decidido aplicar o implementado el funcionamiento a:

  • probar si agregar / mul / etc desbordamiento
  • proporcionar funciones para implementar add / mul / etc que aceptan un acarreo entrante y producen un resultado más acarreo (o una palabra más alta)

Personalmente, no estoy convencido de que pertenezcan a un paquete de "bits" (tal vez las pruebas lo hagan). Las funciones para implementar add / sub / mul de precisión múltiple permitirían una implementación Go pura de algunos de los kernels matemáticos / grandes, pero no creo que la granularidad sea correcta: lo que queremos es kernels optimizados que trabajen en vectores, y máximo rendimiento para esos núcleos. No creo que podamos lograr eso con el código Go dependiendo solo de add / sub / mul "intrínsecos".

Por lo tanto, por ahora me gustaría cerrar este tema como "hecho" a menos que haya objeciones importantes. Por favor, hable durante la próxima semana si está en contra de cerrar esto.

Estoy a favor de agregar funciones en ese sentido.

Creo firmemente que pertenecen a su propio paquete, aunque solo sea por darle un nombre que refleje mejor su funcionalidad colectiva.

: +1: al cerrar este tema y: corazón: por el trabajo realizado hasta ahora.

Cerrando ya que no hubo objeciones.

Este es un comentario para futuras decisiones con respecto a API, entiendo que este en particular está configurado.

Rotate es una función especializada; LTR o RTL solo es relevante dado el contexto. @aclements planteó una pregunta válida, no un punto válido que se extiende siempre. Su pregunta podría haber sido respondida como "es RTL, tal como se incrementan los enteros"; fácil, ¿verdad?

Pero en cambio, surge la inteligencia.

Lo que significa "una función especializada" es algo tan simple que probablemente se descartó rápidamente. Dada una muestra de código, es probable que uno ya comprenda que se produce la rotación y la dirección incluso antes de encontrar la línea de código. Este código generalmente ya está precedido por una documentación ascii ilustrativa tal como está.

Lo que es mentalmente turbulento no es que Go simplemente haya elegido RTL como una forma estándar de interpretar bits desde una perspectiva de API, sino que, primero, busqué los cambios de 1.9 y encontré un RotateLeft sin contraparte y el documento dando un ejemplo de un zancada negativa. Esta es una decisión al estilo de un comité que aturde la mente y que es muy desafortunada para aterrizar en 1.9.

Solo suplico que me ciña al contexto de uso para el futuro. Todo esto debería haber sido evidente por sí mismo con preguntas como, "¿por qué no estamos proporcionando una contraparte de RotateLeft, por qué estamos entrando en pánico en los pasos negativos o debatiendo int vs uint por un paso"; en última instancia, porque creo que lo que significa "una función de especialista" fue simplemente descartado fácilmente por no ser inteligente.

Evitemos la inteligencia en nuestra justificación de las API. Se muestra en esta actualización 1.9.

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

¿Fue útil esta página
0 / 5 - 0 calificaciones