Go: math/bits : une bibliothèque de calcul de bits entiers

Créé le 11 janv. 2017  ·  168Commentaires  ·  Source: golang/go

Discussions précédentes sur https://github.com/golang/go/issues/17373 et https://github.com/golang/go/issues/10757.

Résumé

Cette proposition introduit un ensemble d'API pour le twiddling de bits entiers.

Fond

Cette proposition introduit un ensemble d'API pour le twiddling de bits entiers. Pour cette proposition nous nous intéressons aux fonctions suivantes :

  • ctz - compte les zéros à droite.
  • clz - compte les zéros non significatifs ; log_2.
  • popcnt - compter la population; distance de martèlement ; parité entière.
  • bswap - inverse l'ordre des octets.

Ces fonctions ont été sélectionnées en arpentant :

Nous nous sommes limités à ces quatre fonctions car d'autres
les astuces sont très simples à mettre en œuvre à l'aide de la bibliothèque proposée,
ou des constructions Go déjà disponibles.

Nous avons trouvé des implémentations pour un sous-ensemble des fonctions de twiddling sélectionnées
dans de nombreux packages, y compris runtime, compilateur et outils :

| forfait | clz | ctz | popcnt | bswap |
| --- | --- | --- | --- | --- |
| maths/grand | X | X | | |
| runtime/interne/sys | X | X | | X |
| outils/conteneur/ensembles | X | | X | |
| cmd/compile/interne/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 | |
| Intégrés GCC | X | X | X | X |

De nombreux autres packages implémentent un sous-ensemble de ces fonctions :

De même, les fournisseurs de matériel ont reconnu l'importance
de ces fonctions et inclus le support au niveau de la machine.
Sans support matériel, ces opérations sont très coûteuses.

| arc | clz | ctz | popcnt | bswap |
| --- | --- | --- | --- | --- |
| AMD64 | X | X | X | X |
| BRAS | X | X | ? | X|
| ARM64 | X | X | ? | X|
| S390X | X | X | ? | X|

Toutes les fonctions de calcul de bits, à l'exception de popcnt, sont déjà implémentées par runtime/internal/sys et reçoivent un support spécial du compilateur afin "d'aider à obtenir les meilleures performances". Cependant, la prise en charge du compilateur est limitée au package d'exécution et les autres utilisateurs de Golang doivent réimplémenter la variante plus lente de ces fonctions.

Proposition

Nous introduisons une nouvelle bibliothèque std math/bits avec l'API externe suivante, pour fournir des implémentations optimisées pour le compilateur/le matériel des fonctions clz, ctz, popcnt et 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

Raisonnement

Les alternatives à cette proposition sont :

  • Les fonctions de déplacement de bits sont implémentées dans une bibliothèque externe non prise en charge par le compilateur. Cette approche fonctionne et c'est l'état actuel des choses. Le runtime utilise les méthodes prises en charge par le compilateur, tandis que les utilisateurs de Golang continuent d'utiliser les implémentations les plus lentes.
  • La bibliothèque externe est prise en charge par le compilateur. Étant donné que nous nous attendons à ce que cette bibliothèque remplace runtime/internal/sys, cela signifie que cette bibliothèque doit être verrouillée avec le compilateur et vivre dans la bibliothèque standard.

Compatibilité

Cette proposition ne modifie ni ne brise aucune API stdlib existante et est conforme aux directives de compatibilité.

Mise en œuvre

SwapBytes, TrailingZeros et LeadingZeros sont déjà implémentés. La seule fonction manquante est Ones qui peut être implémentée de la même manière que les autres fonctions. Si cette proposition est acceptée, elle peut être mise en œuvre à temps pour Go1.9.

Problèmes en suspens (le cas échéant)

Les noms sont durs, le hangar à vélos est dans les commentaires.

S'il vous plaît suggérer des fonctions supplémentaires à inclure dans les commentaires. Idéalement, veuillez inclure où une telle fonction est utilisée dans stdlib (par exemple math/big), des outils ou des packages populaires.

Jusqu'à présent, les fonctions suivantes ont été proposées et sont à l'étude :

  • Rotation à gauche / Rotation à droite

    • Avantages : &63 n'a plus besoin de compiler une rotation avec un argument non const en une seule instruction sur x86, x86-64.

    • Inconvénients : très court/simple à mettre en œuvre et en ligne.

    • Utilisé : crypto/ utilise la rotation constante qui est gérée correctement par le compilateur.

  • ReverseBits

    • Avantages: ?

    • Les inconvénients: ?

    • Utilisé: ?

  • Add/Sub avec retour de report

    • Points positifs : Cher sinon

    • Les inconvénients: ?

    • Utilisé : math/grand

Histoire

14.Jan : Clarification de la sortie de TrailingZeros et LeadingZeros lorsque l'argument est 0.
14.Jan : Méthodes renommées : CountTrailingZeros -> TrailingZeros, CountLeadingZeros -> LeadingZeros, CountOnes -> Ones.
13.Jan : Correction du nom de l'architecture.
11.Jan : proposition initiale ouverte au public.

FrozenDueToAge Proposal-Accepted

Commentaire le plus utile

Les personnes qui écrivent du code veulent tourner à gauche ou à droite. Ils ne veulent pas tourner à gauche ou à droite, selon. Si nous ne fournissons que rotation à gauche, toute personne souhaitant effectuer une rotation à droite doit écrire une expression pour convertir sa rotation à droite en rotation à gauche. C'est le genre d'expression que les ordinateurs peuvent faire trivialement, mais les programmeurs feront des erreurs. Pourquoi obliger les programmeurs à l'écrire eux-mêmes ?

Tous les 168 commentaires

@brtzsnr, vous devriez peut-être soumettre ce document au dépôt de la proposition comme indiqué dans les étapes du processus de proposition ?

Comme il s'agit déjà d'une démarque suivant le modèle, il devrait être facile de copier-coller dans une CL en créant un fichier design/18616-bit-twiddling.md (ou autre).

@cespare de https://github.com/golang/proposal "si l'auteur veut écrire un document de conception, alors il peut en écrire un". Cela a commencé comme un document de conception, s'il y a un fort sentiment que je devrais soumettre cela, je vais tout à fait bien.

Je serais d'accord avec cela, c'est une fonctionnalité assez courante utilisée dans de nombreuses bibliothèques algorithmiques et les mathématiques/bits semblent être un endroit approprié.

(D'une part, math/big implémente également nlz (== clz).)

Il y a probablement une perte de vélo à propos des noms. Pour ma part, je préférerais que les fonctions disent ce qu'elles renvoient plutôt que ce qu'elles font ; ce qui à son tour peut conduire à des noms plus courts. Par exemple:

bits.TrailingZeros64(x) plutôt que bits.CountTrailingZeros64(x)

et ainsi de suite.

La proposition semble assez claire et minimale - un document de conception semble exagéré. Je pense qu'un CL serait plus approprié à ce stade.

(Il s'agit d'une CL avec une API et une implémentation de base - à des fins de discussion à la place d'un document de conception. Nous devons encore décider si cette proposition doit être acceptée ou non.)

@brtzsnr a déjà écrit le document de conception : il est dans la description du problème et il suit le modèle . J'ai supposé qu'il y avait une certaine valeur à avoir tous ces documents en un seul endroit.

La dernière arche répertoriée dans le tableau de prise en charge matérielle est "BSWAP" -- faute de frappe ?

Merci d'avoir écrit cela.

La chaîne doc pour ctz et clz doit spécifier le résultat lorsqu'elle est passée à 0.

Je préfère également (par exemple) TrailingZeros32 à CountTrailingZeros32. Je serais également heureux avec Ctz32. Il est concis, familier à la plupart des gens et facilement consultable sur Google pour les autres.

Merci pour la proposition.
Je veux juste ajouter que nous voulons probablement aussi nous concentrer sur l'utilisation, au lieu de nous concentrer uniquement sur les instructions. Par exemple, @dr2chase une fois trouvé, il y a plusieurs fonctions log2 dans le compilateur/assembleur. C'est proche de CLZ mais pas pareil. Des fonctions comme celle-ci devraient probablement également être incluses dans le package Bit twiddling. Et peut-être aussi inclure des fonctions utiles qui ne sont pas liées à ces instructions.

Que diriez-vous de fournir un package pour toutes les primitives de calcul de bits définies par
le délice des pirates ?

Lors de la conception du package, nous n'avons pas besoin de considérer si la fonction
peut être intrinsèque ou non. L'optimisation peut avoir lieu plus tard. C'est-à-dire,
ne laissez pas la mise en œuvre de bas niveau contrôler le package de niveau supérieur
interface. Nous voulons une bonne interface de package, même si certains d'entre eux ne peuvent pas
être mappé sur une seule instruction.

@minux , heureusement, chaque fonction d'enroulement dont j'ai eu besoin jusqu'à présent est exactement celle qui se trouve dans cette proposition.

Suivre Hacker's Delight a l'avantage de ne pas perdre de temps à discuter des noms.

J'aimerais ajouter ce qui suit :

ReverseBits (pour uint32 et uint64)
RotateLeft/Right (peut être étendu en ligne avec deux décalages, mais le compilateur
ne peut pas toujours faire la transformation en raison de problèmes de plage de décalage)

Peut-être aussi deux résultats sous forme d'addition et de soustraction ? Par exemple

func AddUint32(x, y, carryin uint32) (carryout, sum uint32) // carryin doit
être 0 ou 1.
Et de même pour uint64.

Et SqrtInt.

Beaucoup de mes suggestions ne peuvent pas être mises en œuvre en une seule instruction, mais
c'est le point : nous voulons un bon paquet de bidouillage, pas un simple
paquet intrinsèque. Ne laissez pas le matériel limiter le package de haut niveau
interface.

De manière connexe, des fonctions pour vérifier si un ajout/multiplication débordera.

Un point de données connexe : il y a divers problèmes qui ont été déposés pour un cgo plus rapide. Dans un de ces exemples (proposition #16051), le fait que la mise en œuvre rapide de bsr/ctz/etc. pourrait arriver a été mentionné comme, espérons-le, réduisant l'ensemble des cas d'utilisation où les personnes qui écrivent go sont tentées d'utiliser cgo.

@aclements a commenté le 1er juillet 2016 :

@eloff , il y a eu des discussions (bien qu'aucune proposition concrète à ma connaissance) pour ajouter des fonctions pour des choses comme popcount et bsr qui seraient compilées en tant qu'intrinsèques lorsqu'elles sont prises en charge (comme math.Sqrt l'est aujourd'hui). Avec la version 1.7, nous essayons cela dans le runtime, qui dispose désormais d'un ctz intrinsèque sur amd64 SSA. De toute évidence, cela ne résout pas le problème global, mais cela éliminerait une raison de plus d'utiliser cgo dans un contexte à faible surcharge.

Beaucoup de gens (moi y compris) sont attirés par la performance, donc des choses comme cette proposition actuelle de bidouillage seraient utiles. (Et oui, cgo est aussi maintenant plus rapide en 1.8, ce qui est bien aussi).

RotateLeft/Right (peut être étendu en ligne avec deux décalages, mais le compilateur
ne peut pas toujours faire la transformation en raison de problèmes de plage de décalage)

@minux Pouvez-vous préciser quel est le problème ?

@brtzsnr : Je pense que Minux fait référence au fait que lorsque vous écrivez (x << k) | (x >> (64-k)) , vous savez que vous utilisez 0 <= k < 64 , mais le compilateur ne peut pas lire dans vos pensées, et ce n'est évidemment pas le cas dérivable du code. Si nous avions la fonction

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

Ensuite, nous pouvons nous assurer (via le &63) que le compilateur sait que la plage de k est bornée.
Donc, si le compilateur ne peut pas prouver que l'entrée est bornée, alors nous avons besoin d'un ET supplémentaire. C'est mieux que de ne pas générer du tout l'assemblage de rotation.

Le vendredi 13 janvier 2017 à 22h37, Keith Randall [email protected]
a écrit:

@brtzsnr https://github.com/brtzsnr : Je pense à quoi Minux fait référence
à est que lorsque vous écrivez (x << k) | (x >> (64-k)), vous savez que vous utilisez 0
<= k < 64, mais le compilateur ne peut pas lire dans vos pensées, et ce n'est évidemment pas
dérivable du code. Si nous avions la fonction

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

Ensuite, nous pouvons nous assurer (via le &63) que le compilateur sait que la plage
de k est borné.
Donc, si le compilateur ne peut pas prouver que l'entrée est bornée, alors nous avons besoin d'un
ET. C'est mieux que de ne pas générer du tout l'assemblage de rotation.

Droit. Si nous définissons les fonctions RotateLeft et RotateRight, nous pouvons formellement
définir la fonction de rotation gauche/droite de k bits (peu importe ce qu'est k). C'est
similaire à la façon dont nos opérations de quart sont définies. Et cette définition aussi
correspond bien à l'instruction de rotation réelle (contrairement aux décalages, où notre plus
définition intuitive nécessite une comparaison sur certaines architectures).

Qu'en est-il des fonctions de brassage d'octets et de bits (et de désarrangement) utilisées par la bibliothèque de compression blosc ? Les diapositives (le brassage commence à partir de la diapositive 17). Ces fonctions peuvent être accélérées SSE2/AVX2.

Le vendredi 13 janvier 2017 à 23h24, opennota [email protected] a écrit :

Qu'en est-il des fonctions de brassage d'octets et de bits utilisées par le blosc
https://github.com/Blosc/c-blosc bibliothèque de compression ? Les diapositives
http://www.slideshare.net/PyData/blosc-py-data-2014 (le brassage
commence à partir de la diapositive 17). Ces fonctions peuvent être accélérées SSE2/AVX2.

SIMD est un problème plus important et il sort du cadre de ce package. C'est

17373.

Les fonctions actuellement proposées ont une implémentation native Go beaucoup plus grande et disproportionnellement plus chère qu'optimale. D'un autre côté, rotate est facile à écrire en ligne d'une manière que le compilateur peut reconnaître.

@minux et aussi tout le monde : Savez-vous où la rotation gauche/droite est utilisée avec un nombre non constant de bits tournés ? crypto/sha256 utilise par exemple rotate, mais avec un nombre de bits constant.

rotate est facile à écrire en ligne d'une manière que le compilateur peut reconnaître.

C'est facile pour ceux qui sont familiers avec les internes du compilateur. Le mettre dans un package math/bits le rend facile pour tout le monde.

Savez-vous où la rotation gauche/droite est utilisée avec un nombre non constant de bits tournés ?

Voici un exemple de #9337 :

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

Dans chaque invocation, il y a un nombre constant de bits tournés à chaque fois, mais la fonction elle-même n'est pas actuellement en ligne, elle se compile donc sans aucune instruction de rotation. Une fonction de bibliothèque mathématique/bits serait certainement utile ici.

Le samedi 14 janvier 2017 à 5h05, Alexandru Moșoi [email protected]
a écrit:

Les fonctions proposées actuellement ont une implémentation native Go beaucoup plus grande
et disproportionnellement plus cher que l'optimum. d'autre part tourner
est facile à écrire en ligne d'une manière que le compilateur peut reconnaître.

Comme je l'ai souligné à maintes reprises dans ce numéro, ce n'est pas la bonne façon de
concevoir un package Go. Il est trop lié au matériel sous-jacent. Ce que nous
want est un bon paquet de bidouillage qui est généralement utile. Qu'il s'agisse
les fonctions peuvent être étendues en une seule instruction n'a pas d'importance tant que
car l'interface API est bien connue et généralement utile.

@minux https://github.com/minux et aussi tout le monde : Savez-vous
où rotation gauche/droite est utilisé avec un nombre non constant de bits tournés ?
crypto/sha256 utilise par exemple rotate, mais avec un nombre de bits constant.

Même si dans le problème réel le nombre de bits de rotation est constant, le
le compilateur ne pourra peut-être pas le voir. Par exemple, lorsque le nombre de quarts est stocké
dans un tableau, ou cacher dans le compteur de boucle ou même l'appelant d'un non-inline
fonction.

Un exemple simple d'utilisation d'un nombre variable de rotation est intéressant
mise en place 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 L'exemple ressemble à une mauvaise décision d'inliner si la pourriture n'est pas intégrée. Avez-vous essayé d'écrire la fonction sous la forme func rot(x, n) au lieu de rot = func(x, n) ?

@minux : je suis d'accord avec toi. Je n'essaie pas de lier l'API à un jeu d'instructions particulier ; le support matériel est un bon bonus. Mon objectif principal est de trouver des usages dans le code réel (pas dans le code jouet) pour comprendre le contexte, quelle est la meilleure signature et à quel point il est important de fournir la fonction préférée de chacun. La promesse de compatibilité nous mordra plus tard si nous ne le faisons pas correctement maintenant.

Par exemple : Quelle doit être la signature d'un ajout avec retour de report ? Add(x, y uint64) (c, s uint64) ? En regardant math/big nous avons probablement besoin de Add(x, y uintptr) (c, s uintptr) aussi.

L'exemple ressemble à une mauvaise décision d'inliner si la pourriture n'est pas en ligne.

Oui. Cela fait partie d'un bogue se plaignant de l'inline. :)

Avez-vous essayé d'écrire la fonction sous la forme func rot(x, n) au lieu de rot = func(x, n) ?

Ce n'est pas mon code - et cela fait partie du problème. Et de toute façon, c'est du code raisonnable.

Il serait bien de garantir (dans la documentation du package ?) que les fonctions de rotation et d'échange d'octets sont des opérations à temps constant afin qu'elles puissent être utilisées en toute sécurité dans les algorithmes de chiffrement. Peut-être quelque chose à penser pour d'autres fonctions aussi.

Le jeu. 19 janvier 2017 à 11h50, Michael Munday [email protected]
a écrit:

Ce serait bien de garantir (dans la documentation du package ?) que le
les fonctions de rotation et d'échange d'octets sont des opérations à temps constant de sorte qu'elles
peut être utilisé en toute sécurité dans les algorithmes de chiffrement. Peut-être quelque chose à penser
pour d'autres fonctions aussi.

l'implémentation triviale de l'échange d'octets est à temps constant, mais si le sous-jacent
l'architecture ne fournit pas d'instructions de décalage variable, ce sera difficile
pour garantir une mise en œuvre de rotation à temps constant. Peut-être que Go ne courra jamais
sur ces architectures cependant.

Cela dit, il y a aussi une chance non négligeable que le sous-jacent
la microarchitecture utilise un levier de vitesses multicycle, et nous ne pouvons garantir
rotation à temps constant sur ces implémentations.

Si un temps constant strict est requis, le seul moyen est peut-être d'écrire l'assembly
(et même dans ce cas, il fait de fortes hypothèses que tous les
les instructions sont elles-mêmes à temps constant, qui dépend implicitement de la
microarchitecture.)

Bien que je comprenne la nécessité de telles garanties, mais c'est en fait au-delà
notre contrôle.

Je suis enclin à être d'accord avec @minux. Si vous voulez des primitives crypto à temps constant, elles doivent vivre en crypto/subtile. crypto/subtle peut facilement rediriger vers les maths/bits sur les plates-formes où ces implémentations ont été vérifiées. Ils peuvent faire autre chose si une implémentation plus lente mais constante est requise.

Cela semble valoir la peine d'être fait. Départ pour @griesemer et @randall77 chez le vétérinaire. L'étape suivante ressemble à un document de conception vérifié à l'endroit habituel (je vois le croquis ci-dessus).

Maintenant que cette proposition peut aller de l'avant, nous devons nous mettre d'accord sur les détails de l'API. Questions que je vois en ce moment :

  • quelles fonctions inclure ?
  • quels types gérer ( uint64 , uint32 uniquement, ou uint64 , uint32 , uint16 , uint8 , ou uintptr ) ?
  • détails de la signature (par exemple TrailingZeroesxx(x uintxx) uint , avec xx = 64, 32, etc., vs TrailingZeroes(x uint64, size uint) uint où la taille est l'une de 64, 32, etc. - cette dernière conduirait à une API plus petite et peut être encore assez rapide selon la mise en œuvre)
  • un certain nom de délestage de vélo (j'aime la terminologie "Hacker's Delight", mais l'épeler peut être plus approprié)
  • y a-t-il un meilleur endroit que math/bits pour ce paquet.

@brtzsnr Souhaitez-vous continuer à être le fer de lance de cet effort ? Je suis d'accord si vous voulez reprendre votre conception initiale et la répéter dans ce numéro ou si vous préférez créer un document de conception dédié. Alternativement, je peux le ramasser et le pousser vers l'avant. J'aime voir cela entrer pendant la phase 1.9.

Personnellement, je préfère les noms xx épelés : bits.TrailingZeroes(uint64(x), 32) est plus encombrant et moins lisible que bits.TrailingZeroes32(x), imo, et ce schéma de nommage fonctionnerait avec la taille de la variable de plate-forme uintptr plus facilement (xx = Ptr?).

Cela le rendrait également plus évident si, par exemple, vous n'incluiez pas uint8 dans la première version mais l'ajoutiez plus tard. à la liste des tailles valides avec une note dans les documents de version qui est facile à manquer.

Si les noms épelés sont utilisés, je pense que les termes de délice du pirate informatique devraient être inclus dans les descriptions en tant que "également connu sous le nom" afin que les recherches (dans la page ou via le moteur de recherche) trouvent facilement le nom canonique si vous recherchez la mauvaise orthographe .

Je pense que les maths/bits sont un bon endroit—c'est des maths avec des bits.

Je dois noter que SwapBytes{16,32,64} est plus cohérent avec les fonctions de sync/atomic que SwapBytes(..., bitSize uint) .

Bien que le package strconv utilise le modèle ParseInt(..., bitSize int) , il y a plus de relations entre bits et atomic , qu'il n'y en a avec strconv .

Trois raisons pour lesquelles je n'aime pas SwapBytes (uint64, size uint) :

  1. les gens pourraient l'utiliser dans les cas où la taille n'est pas une constante de temps de compilation (à
    au moins jusqu'au compilateur actuel),
  2. cela nécessite beaucoup de conversions de type. D'abord pour convertir un uint32 en
    uint64, puis de le reconvertir.
  3. nous devrions réserver le nom générique SwapBytes pour un futur générique
    version de la fonction (lorsque Go obtient le support des génériques).

@griesemer Robert, veuillez reprendre la proposition. J'aurai le temps de faire avancer cette proposition dans un mois, mais les progrès ne devraient pas s'arrêter d'ici là.

Il semble qu'il y ait une nette préférence pour les signatures du formulaire :

func fffNN(x uintNN) uint

ce qui laisse ouvert quel NN supporter. Concentrons-nous sur NN=64 dans le but de poursuivre la discussion. Il sera simple d'ajouter les types restants au besoin (y compris uintptr).

Ce qui nous ramène directement à la proposition originale de @brtzsnr et aux fonctions suivantes (et leurs variations respectives pour différents types), plus ce que les gens ont suggéré entre-temps :

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

Nous pouvons également avoir un ensemble de fonctions Swap. Personnellement, je suis sceptique à ce sujet : 1) Je n'ai jamais eu besoin d'un SwapBits, et la plupart des utilisations de SwapBytes sont dues à un code qui prend en compte les endians alors qu'il ne devrait pas l'être. Commentaires?

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

Ensuite, il peut y avoir un ensemble d'opérations entières. Ils ne seraient que dans ce package car certains d'entre eux (par exemple Log2) peuvent être proches d'autres fonctions de ce package. Ces fonctions pourraient être ailleurs. Peut-être que le nom du package bits doit être ajusté. Commentaires?

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

Enfin, @minux a suggéré des opérations telles que AddUint32 etc. Je vais les laisser de côté pour l'instant car je pense qu'elles sont plus difficiles à spécifier correctement (et il y a plus de variété). Nous pourrons y revenir plus tard. Parvenons à un certain consensus sur ce qui précède.

Des questions:

  • Des avis sur les noms de fonctions ?
  • Des opinions sur les opérations entières?
  • Des avis sur le nom/l'emplacement du paquet ?

Je préférerais des noms de fonctions longs. Go évite d'abréger dans les noms de fonction. Les commentaires peuvent indiquer leurs surnoms ("nlz", "ntz", etc.) pour les personnes recherchant le paquet de cette façon.

@griesemer , vous semblez avoir des fautes de frappe dans votre message. Les commentaires ne correspondent pas aux signatures de fonction.

J'utilise SwapBits dans les applications de compression. Cela dit, les principales architectures offrent-elles la possibilité d'effectuer une inversion de bits efficacement ? J'avais prévu de faire dans mon propre code :

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 : Signatures mises à jour (erreurs de frappe corrigées). Questions également mises à jour (les gens préfèrent les noms explicites des raccourcis).

À moins qu'il n'y ait un support natif pour l'échange de bits, je voterais pour omettre SwapBits . Le nom seul peut être un peu ambigu si les bits ne sont échangés que dans chaque octet ou sur l'ensemble de uint64.

Pourquoi exclure quelque chose en se basant uniquement sur la disponibilité des instructions ? Inverser le bit
a des utilisations notables dans FFT.

Pour répondre à votre question sur la disponibilité de l'instruction de bit inversé : le bras a
l'instruction RBIT.

@griesemer En ce qui concerne les signatures de type de fonctions comme

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

Est-ce que RotateLeftN etc prend des uints parce que c'est nécessaire pour une intrinisation facile (quand c'est possible) ou juste parce que c'est le domaine ? S'il n'y a pas un besoin technique important, il existe un précédent plus fort pour la prise d'ints, même si les valeurs négatives n'ont pas de sens. S'il y a un fort besoin technique, doivent-ils être uintN pour le N approprié pour la régularité ?

Quoi qu'il en soit, OnesN et ses semblables devraient retourner des entiers à moins que ce ne soit pour que ces opérations puissent être composées avec d'autres opérations sur les bits, auquel cas elles devraient retourner un uintN pour le N approprié.

@jimmyfrasche Parce que c'est le domaine. Le processeur s'en moque. Que quelque chose soit un int ou un uint n'a pas d'importance pour la plupart des opérations, à l'exception des comparaisons. Cela dit, si nous en faisons un entier, nous devons expliquer qu'il n'est jamais négatif (résultat), ou qu'il ne doit pas être négatif (argument), ou spécifier ce qu'il est censé faire lorsqu'il est négatif (argument pour la rotation). Nous pourrions peut-être nous en tirer avec une seule rotation dans ce cas, ce qui serait bien.

Je ne suis pas convaincu que l'utilisation de int facilite les choses. S'ils sont correctement conçus, les uints iront vers les uints (c'est le cas en math/big) et aucune conversion n'est nécessaire. Mais même si une conversion est nécessaire, elle sera gratuite en termes de coût CPU (mais pas en termes de lisibilité).

Mais je suis heureux d'entendre/voir des arguments convaincants autrement.

Le nombre de bits de rotation doit être uint (sans taille spécifique) pour correspondre au
opérateur de décalage de la langue.

La raison de ne pas utiliser int est qu'il y aura une ambiguïté si
RotateRight -2 bits est égal à RotateLeft 2 bits.

@minux Il n'y a pas d'ambiguïté lorsque Rotate(x, n) est défini pour faire pivoter x de n mod 64 bits : une rotation à gauche de 10 bits est identique à une rotation à droite de 64-10 = 54 bits, ou (si nous autorisons les arguments int) de -10 bits (en supposant qu'une valeur positive signifie une rotation vers la gauche). -10 mod 64 = 54. Très probablement, nous obtenons l'effet gratuitement même avec une instruction CPU. Par exemple, les instructions ROL/ROR 32 bits x86 ne regardent déjà que les 5 bits inférieurs du registre CL, exécutant efficacement le mod 32 pour les rotations 32 bits.

Le seul vrai argument est la régularité avec le reste de la stdlib. Il existe de nombreux endroits où les uints ont beaucoup de sens, mais les ints sont utilisés à la place.

Étant donné que math/big est une exception notable, cela ne me pose aucun problème, mais c'est quelque chose à considérer.

Je suppose que @minux signifiait ambiguïté pour quelqu'un qui lisait/déboguait du code plutôt qu'une ambiguïté dans l'implémentation, ce qui est un argument convaincant pour qu'il prenne uint.

Est-ce que bits.SwapBits doit vraiment être nommé avec un suffixe bits alors que le nom du package est déjà appelé bits ? Qu'en est-il des bits.Swap ?

Une autre idée est un bits.SwapN(v uint64, n int) plutôt que bits.SwapBytes où n représente le nombre de bits à regrouper pour l'échange. Lorsque n=1 les bits sont inversés, lorsque n=8, les octets sont permutés. L'un des avantages est que bits.SwapBytes n'a plus besoin d'exister, bien que je n'aie jamais vu la nécessité d'une autre valeur de n, peut-être que d'autres seront en désaccord.

Comme spécifié ci - LeadingZeros64 , TrailingZeros64 , Ones64 tous LGTM.

Un seul Rotate64 avec un montant de rotation signé est attrayant. La plupart des rotations seront également d'un montant constant, donc (si/quand cela devient intrinsèque), il n'y aura aucun coût d'exécution à ne pas avoir de fonctions droite/gauche séparées.

Je n'ai pas d'opinion tranchée sur le mélange/l'échange/l'inversion de bits/octets. Pour inverser les bits, bits.Reverse64 semble être un meilleur nom. Et peut-être bits.ReverseBytes64 ? Je trouve Swap64 un peu flou. Je vois l'intérêt d'avoir Swap64 prendre une taille de groupe, mais quel est le comportement lorsque la taille du groupe ne divise pas uniformément 64 ? Si les seules tailles de groupe qui comptent sont 1 et 8 , il serait plus simple de leur donner des fonctions distinctes. Je me demande également si une sorte de manipulation générale des champs de bits ne serait pas meilleure, en regardant peut-être les instructions arm64 et amd64 BMI2 pour s'en inspirer.

Je ne suis pas convaincu que les fonctions mathématiques entières appartiennent à ce package. Ils semblent plus appartenir aux mathématiques du package, où ils pourraient utiliser des fonctions de bits dans leur implémentation. Connexe, pourquoi pas func Sqrt(x uint64) uint32 (au lieu de retourner uint64 ) ?

Bien que AddUint32 et les amis soient compliqués, j'espère que nous les reverrons éventuellement. Entre autres choses, MulUint64 donnerait accès à HMUL, que même le super-minimaliste RISC-V ISA fournit comme instruction. Mais oui, commençons par quelque chose ; nous pouvons toujours développer plus tard.

bits semble clairement être le bon nom de package. Je suis tenté de dire que le chemin d'importation devrait être juste bits , pas math/bits , mais je ne me sens pas trop fort.

bits semble clairement être le bon nom de package. Je suis tenté de dire que le chemin d'importation ne devrait être que des bits, pas des maths/bits, mais je ne me sens pas trop fort.

IMO, s'il ne s'agissait que de bits , (sans lire la documentation du package), je m'attendrais à ce que ce soit quelque peu similaire au package bytes . C'est-à-dire qu'en plus des fonctions permettant d'opérer sur des bits, je m'attendrais à trouver un Reader et un Writer pour lire et écrire des bits vers/depuis un flux d'octets. Le rendre math/bits rend plus évident qu'il s'agit simplement d'un ensemble de fonctions sans état.

(Je ne propose pas d'ajouter Reader/Writer pour les flux binaires)

Un seul Rotate64 avec un montant de rotation signé est attrayant. La plupart des rotations seront également d'un montant constant, donc (si/quand cela devient intrinsèque), il n'y aura aucun coût d'exécution à ne pas avoir de fonctions droite/gauche séparées.

Je peux voir comment il pourrait être pratique d'avoir une API de package légèrement plus petite en combinant la rotation gauche/droite en un seul Rotate , mais il y a aussi la question de documenter efficacement le paramètre n pour indique que:

  • négatif n entraîne une rotation à gauche
  • zéro n n'entraîne aucun changement
  • positif n entraîne une rotation à droite

Pour moi, la surcharge mentale et la documentation supplémentaire ne semblent pas justifier la combinaison des deux en un seul Rotate . Avoir un RotateLeft explicite et un RotateRightn est un uint me semble plus intuitif.

@mdlayher Gardez à l'esprit que pour un mot de n bits, la rotation de k bits vers la gauche

En plus de cela, le matériel (par exemple sur x86) le fait même pour vous automatiquement, même pour un k non constant.

@griesemer , un inconvénient de Rotate est qu'il ne dit pas quelle direction est positive. Est-ce que Rotate32(1, 1) égal à 2 ou 0x80000000 ? Par exemple, si je lisais du code qui l'utilisait, je m'attendrais à ce que le résultat soit 2, mais apparemment @mdlayher s'attendrait à ce que ce soit 0x80000000. D'un autre côté, RotateLeft et RotateRight sont des noms sans ambiguïté, indépendamment du fait qu'ils prennent un argument signé ou non signé. (Je ne suis pas d'accord avec @minux qu'il est ambigu que RotateRight by -2 soit égal à RotateLeft 2. Ceux-ci me semblent évidemment équivalents et je ne vois pas comment vous pourriez les spécifier autrement.)

@aclements qui pourraient être corrigés en n'ayant qu'une seule fonction appelée RotateLeft64 et en utilisant des valeurs négatives pour faire pivoter vers la droite. Ou avec des docs. (FWIW, dans votre exemple, je m'attendrais également à 2, pas à 0x800000000.)

@josharian , je suis d'accord, même si cela semble un peu étrange d'avoir un RotateLeft sans avoir aussi un RotateRight . La cohérence d'avoir une rotation signée semble potentiellement précieuse si la rotation est calculée, mais pour des rotations constantes, je préférerais de loin lire le code qui dit "tourner à droite de deux bits" que "tourner à gauche en plaisantant juste moins deux bits".

Le problème avec la résolution de ce problème avec les documents est que les documents n'aident pas à la lisibilité du code qui appelle la fonction.

Les personnes qui écrivent du code veulent tourner à gauche ou à droite. Ils ne veulent pas tourner à gauche ou à droite, selon. Si nous ne fournissons que rotation à gauche, toute personne souhaitant effectuer une rotation à droite doit écrire une expression pour convertir sa rotation à droite en rotation à gauche. C'est le genre d'expression que les ordinateurs peuvent faire trivialement, mais les programmeurs feront des erreurs. Pourquoi obliger les programmeurs à l'écrire eux-mêmes ?

Je suis convaincu.

@aclements @ianlancetaylor Point pris. (Cela dit, l'implémentation de RotateLeft/Right n'appellera probablement qu'une implémentation de rotate.)

Une idée de nom est de mettre le type dans le nom du package plutôt que dans le nom de la signature. bits64.Ones plutôt que bits.Ones64 .

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

@bradfitz golang.org/x/image/math/f64 et golang.org/x/image/math/f32 existent cependant

Je n'avais pas pensé à atomic comme précédent (pas dans mon utilisation normale de Go, heureusement). Les autres exemples sont différents car les packages sont plus gros qu'un ensemble de fonctions répétées pour un certain nombre de types différents.

La documentation est plus facile à regarder (disons, sur godoc), quand vous allez à bits64 et voyez
Ones LeadingZeros TrailingZeros

Au lieu d'aller à bits et de voir
Ones8 Ones16 Ones32 Ones64 LeadingZeros8 ...

@iand , c'est aussi assez dégoûtant. Je suppose que les 32 et 64 ne semblaient pas agréables à tous les suffixes numériques existants pour Aff, Mat et Vec. Pourtant, je dirais que c'est une exception plutôt que le style Go normal.

Il existe un précédent assez fort dans Go pour utiliser le style de nommage pkg.foo64 plutôt que pkg64.foo .

L'API du package devient n fois plus grande si nous avons n types, mais pas la complexité de l'API. Une meilleure approche pour résoudre ce problème pourrait être la documentation et la présentation de l'API via un outil tel que go doc. Par exemple, au lieu 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

ce qui ajoute clairement une surcharge mentale lorsque l'on regarde l'API, nous pourrions la présenter comme :

// 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 pourrait être intelligent sur les fonctions qui sont similaires dans ce sens et les présenter comme un groupe avec une seule chaîne de documentation. Cela serait également utile dans d'autres packages.

Pour résumer, je pense que le schéma de nommage proposé semble correct et dans la tradition du Go. La question de la présentation est une question distincte qui peut être discutée ailleurs.

+1 pour mettre la taille du bit dans le nom du package.

bits64.Log2 se lit mieux que bits.Log264 (je pense que Log2 y appartient - et le nommage des packages n'est pas une bonne raison pour le garder à l'écart)

En fin de compte, c'est la même API pour chaque type "paramétré" par type scalaire, donc si les fonctions ont le même nom à travers les types, le code est plus facile à refactoriser avec des packages séparés - il suffit de changer le chemin d'importation (les remplacements de mots entiers sont trivial avec gofmt -r mais les transformations de suffixes personnalisées sont plus délicates).

Je suis d'accord avec @griesmeyer que le suffixe de taille de bits sur le nom de la fonction peut être idiomatique, mais je pense que cela en vaut la peine seulement s'il y a du code non trivial dans le package qui est indépendant du type.

Nous pourrions voler un jeu d'encodage/binaire et créer des vars nommées Uint64, Uint32, ... qui auraient des méthodes acceptant les types prédéfinis associés.

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

@griesemer comment saurait-il les regrouper ? Une chaîne de documentation avec le nom de la fonction se terminant par N dans la documentation au lieu du nom réel, puis trouver un préfixe commun parmi les fonctions sans chaîne de documentation qui correspond à l'incongruité ? Comment ça se généralise ? Cela semble être un cas spécial compliqué de servir très peu de colis.

@rogpeppe cela pourrait être bits.Log64 et les docs disent que c'était la base 2 (que serait-ce d'autre dans un package bits?) Bien qu'aussi désordonné que d'avoir des packages 8/16/32/64/ptr soient à un niveau faites-les chaque nettoyeur individuellement. (En outre, il semble que votre transmission ait été coupée au milieu d'une phrase.)

@nerdatmath ce serait légèrement différent car ils auraient la même interface dans le sens familier mais pas dans le sens Go, comme c'est le cas dans le codage/binaire (ils implémentent tous les deux binary.ByteOrder), donc les variables seraient juste pour l'espacement des noms et godoc ne récupérerait aucune des méthodes (#7823) à moins que les types ne soient exportés, ce qui est compliqué d'une manière différente.

Je suis d'accord avec les suffixes de taille, mais, dans l'ensemble, je préférerais les packages séparés. Pas assez pour prendre la peine de dépasser ce commentaire, cependant.

@nerdatmath si ce sont des vars, alors ils sont mutables (vous pouvez faire bits.Uint64 = bits.Uint8 ), ce qui empêcherait le compilateur de les traiter comme des intrinsèques, ce qui était l'une des motivations (au moins en partie) du package.

Les variables ne sont pas vraiment mutables si elles sont d'un type non exporté et n'ont pas d'état (struct vide non exporté). Mais sans une interface commune, le godoc (aujourd'hui) serait dégueulasse. Réparable si c'est la réponse.

Si vous finissez tous par utiliser plusieurs packages, s'il y a suffisamment de répétition et de volume pour le justifier, au moins peut-être nommer les packages comme "X/bits/int64s", "X/bits/ints", "X/ bits/int32s" pour correspondre à "errors", "strings", "bytes". Cela ressemble encore à beaucoup d'explosion de colis.

Je ne pense pas que nous devrions suivre le chemin vers plusieurs packages spécifiques à un type, avoir un seul paquet de bits est simple et ne gonfle pas le nombre de packages dans la bibliothèque Go.

Je ne pense pas que Ones64 et TrailingZeroes64 soient de bons noms. Ils n'informent pas qu'ils renvoient un décompte. L'ajout du préfixe Count rend les noms encore plus longs. Dans mes programmes, ces fonctions sont souvent intégrées dans des expressions plus grandes, donc les noms de fonctions courts augmenteront la lisibilité. Bien que j'aie utilisé les noms du livre "Hacker's Delight", je suggère de suivre les mnémoniques de l'assembleur Intel : Popcnt64, Tzcnt64 et Lzcnt64. Les noms sont courts, suivent un modèle cohérent, informent qu'un décompte est renvoyé et sont déjà établis.

Habituellement, les nombres dans Go sont renvoyés sous forme d'entiers, même lorsqu'un nombre ne peut jamais être négatif. Le premier exemple est la fonction intégrée len, mais Read, Write, Printf, etc. renvoient également des entiers. Je trouve l'utilisation de la valeur uint pour un compte assez surprenante et suggère de renvoyer une valeur int.

Si le choix est entre (pour abuser de la notation) math/bits/int64s.Ones et math/bits.Ones64, alors je préférerais certainement un seul package. Il est plus important d'avoir des bits dans l'identifiant du package que de partitionner par taille. Le fait d'avoir des bits dans l'identifiant du package facilite la lecture du code, ce qui est plus important que l'inconvénient mineur de la répétition dans la documentation du package.

@ulikunitz Vous pouvez toujours faire ctz := bits.TrailingZeroes64 , etc., dans du code qui utilise beaucoup de bits. Cela établit un équilibre entre avoir des noms globalement auto-documentés et des noms localement compacts pour un code complexe. La transformation inverse, countTrailingZeroes := bits.Ctz64 , est moins utile puisque les belles propriétés globales sont déjà perdues.

Je joue très rarement avec des trucs au niveau du bit. Je dois chercher beaucoup de choses à chaque fois, juste parce que cela fait des années que je n'ai pas dû y penser, donc je trouve tous les noms de style Hacker's Delight/asm d'une manière exaspérante. Je préférerais qu'il dise simplement ce qu'il a fait pour que je puisse repérer celui dont j'ai besoin et revenir à la programmation.

Je suis d'accord avec @ulikunitz, les noms longs font mal aux yeux. La première fois que vous voyez bits.TrailingZeroes64, il se peut qu'il s'auto documente. À chaque fois, c'est agaçant. Je peux voir pourquoi les gens feraient ctz := bits.TrailingZeroes64, mais je pense qu'un nom qui doit être attribué à un nom plus court est un mauvais nom. Il y a des endroits où Go abrège, par exemple,

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

De plus, je contesterais que bits.TrailingZeroes64 s'auto documente. Qu'est-ce que cela signifie pour un zéro d'être à la traîne ? La propriété d'auto-documentation existe avec l'hypothèse que l'utilisateur sait quelque chose sur la sémantique de la documentation pour commencer. Si vous n'utilisez pas Hacker's Delight pour plus de cohérence, pourquoi ne pas l'appeler simplement ZeroesRight64 et ZeroesLeft64 pour faire correspondre RotateRight64 et RotateLeft64 ?

Pour clarifier, je ne voulais pas dire que chaque fois que vous l'utilisez, la première chose que vous devez faire est de créer un alias court et vous ne devez jamais utiliser le nom donné. Je voulais dire que si vous l'utilisez à plusieurs reprises, vous pouvez l'aliaser, si cela améliore la lisibilité du code.

Par exemple, si j'appelle strings.HasSuffix, j'utilise le nom donné la plupart du temps parce que je l'appelle une ou deux fois, mais, de temps en temps, dans un script ETL unique ou similaire, je vais finir par l'appeler un tas donc je vais faire ends := strings.HasSuffix qui est plus court et rend le code plus clair. C'est bien car la définition de l'alias est à portée de main.

Les noms abrégés sont parfaits pour les choses qui sont extrêmement courantes. Beaucoup de non-programmeurs savent ce que signifie OS. Quiconque lit du code, même s'il ne connaît pas Go, comprendra que func est l'abréviation de fonction. Les canaux sont fondamentaux pour Go et largement utilisés, donc chan est parfait. Les fonctions de bits ne seront jamais extrêmement courantes.

TrailingZeroes peut ne pas être en mesure de vous dire exactement ce qui se passe si vous n'êtes pas familier avec le concept, mais cela vous donne une bien meilleure idée de ce qui se passe, au moins approximativement, que Ctz.

@jimmyfrasche Concernant https://github.com/golang/go/issues/18616#issuecomment -275828661: Il serait assez facile pour go/doc de reconnaître une séquence contiguë de fonctions dans le même fichier qui ont des noms qui commencent toutes par le même préfixe et ont un suffixe qui est juste une séquence de nombres et/ou peut-être un mot qui est "court" par rapport au préfixe. Je le combinerais avec le fait que seule la première de ces fonctions a une chaîne doc et l'autre avec le préfixe correspondant n'en a pas. Je pense que cela pourrait très bien fonctionner dans des contextes plus généraux. Cela dit, discutons-en ailleurs pour ne pas détourner cette proposition.

Pour info, créé #18858 pour discuter des changements de go/doc et garder cette conversation séparée de celle-ci.

@mdlayher merci d'avoir fait ça.

@rogpeppe Mettre la taille dans le nom du package semble intrigant, mais à en juger par les commentaires et compte tenu du style Go existant, je pense que la préférence est de mettre la taille avec le nom de la fonction. En ce qui concerne Log2, je dirais, laissons le 2 de côté. (En outre, veuillez répéter s'il y avait autre chose que vous vouliez ajouter, vos commentaires semblent coupés au milieu de la phrase.)

@ulikunitz Je suis généralement l'un des premiers à voter pour des noms courts ; surtout dans le contexte local (et dans le contexte global pour les noms extrêmement fréquemment utilisés). Mais ici, nous avons une API de package et (du moins selon mon expérience) les fonctions n'apparaîtront pas de manière omniprésente dans les clients. Par exemple, math/big fait utilise LeadingZeros, Log2, etc. mais c'est juste un ou deux appels. Je pense qu'un nom descriptif plus long est ok, et à en juger par la discussion, une majorité de commentaires est en faveur de cela. Si vous appelez fréquemment une fonction, il est peu coûteux de l'envelopper dans une fonction avec un nom court. L'appel supplémentaire sera mis en ligne. FWIW, les mnémoniques Intel ne sont pas géniaux non plus, leur accent est mis sur "count" (cnt) et il vous reste alors 2 lettres qui doivent être décryptées.

Je suis d'accord que le 2 dans Log2 n'est pas nécessaire. bits.Log implique une base de 2.

bits.Log64 SGTM.

@griesemer Ma phrase de coupure était probablement un artefact d'édition maladroite. Je viens de l'enlever.

@griesemer, je me demande si cela vaut la peine de continuer le nom bike shedding. Je fais court : j'avais deux arguments : la brièveté et la clarté sur le retour d'un numéro. Package big utilise nlz, pas LeadingZeros, en interne. Je suis d'accord que le nombre d'utilisations de ces fonctions est faible.

@ulikunitz Je pense que la majorité des commentaires ici sont en faveur de noms de fonctions clairs qui ne sont pas des raccourcis.

@ulikunitz , en plus :

Package big utilise nlz, pas LeadingZeros, en interne.

Les noms privés internes non exportés n'ont aucune influence ici.

Tout ce qui compte, c'est la cohérence et la sensation standard de l'API publique.

Bikeshedding peut être ennuyeux, mais les noms comptent quand nous sommes coincés avec eux pour toujours.

CL https://golang.org/cl/36315 mentionne ce problème.

J'ai téléchargé https://go-review.googlesource.com/#/c/36315/ en tant

Nous nous assurons toujours que l'API est correcte, veuillez donc commenter uniquement l'API (bits.go) pour l'instant. Pour les problèmes mineurs (fautes de frappe, etc.), veuillez commenter le CL. Pour les problèmes de conception, veuillez commenter ce problème. Je continuerai à mettre à jour la CL jusqu'à ce que nous soyons satisfaits de l'interface.

Plus précisément, ne vous inquiétez pas pour la mise en œuvre. C'est basique, et seulement partiellement testé. Une fois que nous serons satisfaits de l'API, il sera facile de développer l'implémentation.

@griesemer Je suis sûr que vous allez bien, mais si c'est utile, j'ai une implémentation d'assemblage CLZ avec des tests https://github.com/ericlagergren/decimal/tree/ba42df4f517084ca27f8017acfaeb69629a090fb/internal/arith que vous êtes libre de voler/jouer avec/quoi que ce soit.

@ericlagergren Merci pour le lien. Une fois que l'API est installée et que nous avons des tests en place partout, nous pouvons commencer à optimiser la mise en œuvre. Je vais garder ça en tête. Merci.

@griesemer , il semble que la documentation de votre API proposée dépende de la capacité de go/doc à documenter de manière adéquate les fonctions avec le même préfixe mais un suffixe entier.

Est-ce que le plan serait de travailler là-dessus avant la 1.9 également ?

18858, c'est-à-dire !

@mdlayher Ce serait l'idéal. Mais ce ne serait pas un obstacle car cela n'affecte "que" la documentation, pas l'API (qui doit rester rétrocompatible à l'avenir).

@griesemer @bradfitz Je passe du temps à repenser ma position concernant les noms de fonction. Un objectif important du choix des noms doit être la lisibilité du code à l'aide de l'API.

Le code suivant est en effet facile à comprendre :

n := bits.LeadingZeros64(x)

Je doute encore un peu de la clarté de Ones64(x), mais il est cohérent avec LeadingZeros et TrailingZeros. Je repose donc ma cause et soutiens la proposition actuelle. À titre d'expérience, j'ai utilisé mon code existant pour une implémentation expérimentale pure Go du package, y compris les cas de test.

Référentiel : https://github.com/ulikunitz/bits
Documentation : https://godoc.org/github.com/ulikunitz/bits

Nous pouvons également disposer d'un ensemble de fonctions Swap. Commentaires?

J'aimerais garder SwapBytes hors de l'API pour cette raison. Je crains que les gens ne commencent à l'utiliser de manière non portable, car il est plus simple (ou supposé être plus rapide) que binary/encoding.BigEndian .

J'aimerais garder SwapBytes hors de l'API pour cette raison. Je crains que les gens ne commencent à l'utiliser de manière non portable car il est plus simple (ou supposé être plus rapide) que binaire/encodage.BigEndian.

@mundaym est-ce que ces routines vont jamais être intrinsèques ? Laisser SwapBytes64 compiler directement en tant que BSWAP pourrait l'emporter sur les personnes qui l'utilisent de manière incorrecte, imo.

Je crains que les gens ne commencent à l'utiliser de manière non portable car il est plus simple (ou supposé être plus rapide) que le binaire/encodage.BigEndian

Je pense que l'utilisation de ReverseBytes n'est pas portable si elle est utilisée avec unsafe où vous accédez aux données de bas niveau de la manière dont la machine les met en mémoire. Cependant, l'utilisation de encoding.{Little,Big}Endian.X est tout aussi non portable lorsqu'elle est utilisée de cette manière. J'attends avec impatience ReverseBytes pour une utilisation en compression et je serais triste s'il n'était pas inclus.

@ericlagergren

ces routines vont-elles jamais être intrinsèques ?

Oui, les fonctions de encoding/binary qui effectuent des inversions d'octets utilisent des modèles de code qui sont reconnus par le compilateur et sont (ou pourraient être) optimisés pour des instructions simples (par exemple BSWAP ) sur les plates-formes qui prennent en charge les données non alignées accès (par exemple 386, amd64, s390x, ppc64le et probablement d'autres).

Laisser SwapBytes64 compiler directement en tant que BSWAP pourrait l'emporter sur les personnes qui l'utilisent de manière incorrecte, imo.

Je suis enclin à ne pas être d'accord. Il peut y avoir des utilisations légitimes de SwapBytes dans du code non compatible, mais je soupçonne qu'il sera mal utilisé plus souvent qu'il n'est utilisé correctement.

Notez que runtime/internal/sys a déjà optimisé les versions intrinsèques Go, assembly et compilateur des zéros de fin et de l'échange d'octets, nous pouvons donc réutiliser ces implémentations.

@dsnet Intéressant, avez-vous quelque chose de spécifique en tête ?

@aclements Savez-vous où l'intrinsèque d'échange d'octets est utilisé dans le runtime ? Je n'ai trouvé aucune utilisation en dehors des tests.

@mundaym , AFAIK, il n'est pas utilisé dans le runtime. Nous n'avons pas besoin d'être aussi prudents avec les API internes, donc je pense que nous l'avons simplement ajouté lors de l'ajout de ctz parce que c'était facile. :) (Popcount aurait été un meilleur choix.)

Tous : Juste un rappel pour vous concentrer sur l'API pour le moment. L'implémentation dans la CL est simplement une implémentation triviale et lente qui nous permet d'utiliser réellement ces fonctions et de les tester. Une fois que nous sommes satisfaits de l'API, nous pouvons ajuster l'implémentation.

Sur cette note : nous devrions probablement inclure des versions pour uintptr car il n'est pas garanti (bien que probable) qu'il soit de la même taille qu'un uint32 ou uint64 . (Notez que uint est toujours soit uint32 soit uint64 ). Des avis? Le laisser pour plus tard ? Quels seraient les bons noms? ( LeadingZerosPtr ?).

Nous devrions faire uintptr maintenant, sinon math/big ne peut pas l'utiliser. Suffixe Ptr SGTM. Je pense que nous devrions également faire uint , sinon tout le code appelant doit écrire du code conditionnel autour de la taille int pour effectuer l'appel sans conversion. Aucune idée de nom.

Ptr pour uintptr, pas de suffixe pour uint. Ones, Ones8, Ones16, Ones32, Ones64, OnesPtr, etc.

Je pourrais être une minorité ici, mais je suis contre l'ajout de fonctions qui prennent uintptr.
math/big aurait dû utiliser uint32/uint64 depuis le début. (En fait, je
utilisez simplement uint64 pour toutes les architectures et laissez l'optimisation au
compilateur.)

Le problème est le suivant :
math/big veut utiliser la taille de mot native pour des performances optimales,
cependant, bien que proche, uintptr n'est pas le bon choix.
il n'y a aucune garantie que la taille d'un pointeur est la même que la
taille du mot natif.
Le premier exemple est amd64p32, et nous pourrions également ajouter mips64p32 et
mips64p32le plus tard, qui sont beaucoup plus populaires pour les hôtes MIPS 64 bits.

Je ne veux certainement pas encourager davantage de tels usages. uintptr est le plus
pour les pointeurs, pas pour les entiers neutres pour l'architecture.

Une solution, cependant, consiste à introduire un alias de type dans l'API (peut-être
devrait être du type de marque, ceci est à discuter).
tapez Word = uint32 // ou uint64 sur les architectures 64 bits
// nous avons probablement aussi besoin de fournir quelques constantes idéales pour Word comme
nombre de bits, masque, etc.

Et puis introduisez des fonctions sans préfixe de type pour prendre Word.

En fait, j'imagine que si math/bit fournit les deux résultats add, sub, mul,
div; math/big peut être réécrit sans aucun assemblage spécifique à l'architecture.

math/grand export type Word uintptr . Je suis d'accord que cela a pu être une erreur, mais je pense que la compatibilité exige que cela reste. Donc, si nous voulons utiliser math/bits pour implémenter math/big, ce que je pense que nous faisons, nous avons besoin d'API uintptr.

Je m'éloigne un peu du sujet, mais la taille du mot non signé n'est-elle pas juste ? Dans ce cas, ne pas avoir de suffixe pour uint semble à peu près correct.

Je ne voudrais pas qu'un seul type, Word, soit différent sur différentes architectures. Cela semble introduire beaucoup de complexité pour l'appelant et les docs. S'en tenir aux types existants signifie que vous pouvez simplement l'utiliser. Avoir un type qui varie selon les architectures signifie concevoir tout votre code autour de ce type ou avoir un tas d'adaptateurs dans des fichiers protégés par des balises de construction.

FWIW, l'implémentation de math/big pourrait toujours utiliser une API basée sur uint32/64 bits (s'il n'y avait pas de version uintptr des fonctions).

@minux Je ne suis pas contre les 2 résultats add,sub,mul,div - mais faisons-le dans une 2ème étape. Nous avons toujours besoin de code assembleur si nous voulons des performances décentes. Mais je suis heureux d'être convaincu du contraire par le compilateur...

J'ai ajouté les versions uint et uintptr à l'API. Jetez un œil à la CL et commentez. Je ne suis pas trop content de la prolifération des fonctions, cependant.

@josharian L'uint natif peut être une valeur 32 bits même sur une machine 64 bits. Il n'y a aucune garantie. Je me rends compte que uintptr ne garantit pas non plus la taille des mots machine ; mais à l'époque, cela semblait être le choix le plus sensé, s'il était mauvais rétrospectivement. Peut-être avons-nous vraiment besoin d'un type Word.

Si le seul besoin légitime des fonctions uintptr est de prendre en charge math/big, peut-être que l'implémentation de math/bits pourrait aller dans un package interne : n'utilisez que les éléments uintptr dans math/big et exposez le reste.

@jimmyfrasche , @griesemer comment cela affecterait-il big.Int.Bits ? c'est-à-dire qu'il sera toujours alloué à zéro ?

uint est également faux. c'est 32 bits sur amd64p32.
En fait, math/big aurait pu utiliser uint au lieu de uintptr, mais à Go 1,
int/uint sont toujours 32 bits, ce qui fait de uintptr la seule solution possible
pour les maths/big.Word.

Nous concevons un nouveau package, donc je ne pense pas que la compatibilité soit de beaucoup
préoccuper. math/big utilise le mauvais type, mais c'est plus un historique
accident. N'allons pas plus loin dans l'erreur.

Je ne comprends pas pourquoi utiliser un type unifié "Word" qui est toujours le plus
type entier efficace pour une architecture donnée est plus compliqué que
en utilisant uintptr. Pour le code utilisateur, vous pouvez le traiter comme un type magique qui est
32 bits ou 64 bits, selon l'architecture. Si vous pouvez écrire
votre code en utilisant uintptr (dont la largeur dépend également de l'architecture) dans un
de manière indépendante de l'architecture, alors je pense que vous pouvez faire la même chose avec Word.
Et Word est correct sur toutes les architectures.

Pour la prolifération de l'API, je suggère de laisser de côté les fonctions pour 8
et les types 16 bits dans la première passe. Ils sont rarement utilisés et la plupart
L'architecture ne fournira de toute façon que des instructions 32 bits et 64 bits.

math/big définit et exporte Word, mais c'est a) pas directement compatible avec uintptr (c'est un type différent), et b) un code qui utilise correctement Word et ne fait pas d'hypothèses sur ses implémentations pourra avec n'importe quel type d'implémentation de Word . Nous pouvons le changer. Je ne vois pas pourquoi cela devrait affecter la garantie de compatibilité API (je n'ai pas essayé cependant). Quoi qu'il en soit, laissons cela de côté. Nous pouvons gérer cela séparément.

Pouvons-nous simplement faire tous les types uint de taille explicite ? Si nous connaissons la taille uint/uintptr comme une constante, nous pouvons trivialement écrire une instruction if qui appelle la fonction de la bonne taille. Cette instruction if sera constamment repliée et la fonction wrapper respective appelle simplement la fonction sized qui sera intégrée.

Enfin, quelle est la bonne solution concernant le type Word (indépendant de math/big) ? Bien sûr, nous pourrions le définir en math/bits mais est-ce le bon endroit ? C'est un type spécifique à la plate-forme. Doit-il s'agir d'un 'mot' de type prédéclaré ? Et pourquoi pas?

Avoir des signatures de fonction (ou des noms) ici qui mentionnent directement uintptr semble être une erreur. Les gens ne sont pas censés faire ce genre de petit tour sur les pointeurs Go.

S'il doit y avoir des fonctions pour un type de mot machine naturel, je pense qu'elles peuvent faire référence à int/uint maintenant. Nous n'avons pas utilisé uint dans math/big car c'était du 32 bits sur les systèmes 64 bits, mais nous avons corrigé cela depuis. Et tandis que math/big est un monde cohérent en soi, où il est logique d'avoir son propre type big.Word, ce package est plus une collection de routines utilitaires à choisir. Définir un nouveau type dans ce contexte est moins contraignant.

Je ne sais pas du tout si nous avons besoin de variantes int/uint. S'ils sont généralement nécessaires, les définir dans ce package est plus logique que de forcer tous les appelants à écrire des instructions if. Mais je ne sais pas s'ils seront généralement nécessaires.

Pour remédier à l'inadéquation potentielle avec math/big, il existe au moins deux solutions qui n'impliquent pas de mentionner uintptr dans l'API ici.

  1. Définissez les fichiers word32.go et word64.go dans math/big avec des balises de construction et des wrappers acceptant uintptr qui redirigent de manière appropriée (et assurez-vous que l'inline du compilateur fait la bonne chose).

  2. Remplacez la définition de big.Word par uint. La définition exacte est un détail d'implémentation interne, même si elle est exposée dans l'API. Le changer ne peut casser aucun code, sauf sur amd64p32, et même là, il semble très peu probable que cela ait une importance en dehors de math/big.

@rsc : "Nous n'avons pas utilisé uint dans math/big car c'était du 32 bits sur les systèmes 64 bits, mais nous avons corrigé cela depuis. " Ah oui, j'ai complètement oublié la raison du choix de uintptr. L'intérêt des types Go uint/int était d'avoir des types entiers qui reflétaient la taille naturelle du registre d'une machine. Je vais essayer de changer big.Word en uint et voir ce qui se passe.

Tous : mise à jour de https://go-review.googlesource.com/#/c/36315/ pour exclure les versions de la fonction uintptr conformément à la discussion ci-dessus.

J'ai provisoirement mis à jour math/big pour utiliser math/bits pour voir l'effet (https://go-review.googlesource.com/36328).

Quelques commentaires :
1) Je penche pour que les résultats des fonctions Leading / TrailingZeros soient uint plutôt que int . Ces résultats ont tendance à être utilisés pour décaler une valeur en conséquence ; et math/big témoignent. Je suis également favorable à ce que Ones retourne un uint , par souci de cohérence. Contre-arguments?

2) Je ne suis pas fan du nom One , cependant compatible avec Leading / TrailingZeros . Que diriez-vous de Population ?

3) Certaines personnes se sont plaintes que Log ne rentre pas dans ce package. Log se trouve être équivalent à l'index du msb (avec -1 pour x == 0). Nous pourrions donc l'appeler MsbIndex (bien que Log semble plus agréable). Alternativement, nous pourrions avoir une fonction Len qui est la longueur d'un x en bits ; c'est-à-dire, `Log(x) == Len(x)-1).

Commentaires?

Je ne suis pas un fan du nom One, cependant compatible avec Leading/TrailingZeros. Et la population ?

Pourquoi pas le traditionnel Popcount ?

Alternativement, nous pourrions avoir une fonction Len qui est la longueur d'un x en bits ; c'est-à-dire, `Log(x) == Len(x)-1).

Len ou Length sonne comme un bon nom et une bonne idée.

1.

Je ne suis pas fan du nom One, cependant cohérent avec Leading/
TrailingZeros. Et la population ?

bits.Compter ?

@aclements Compter quoi ?

Population est peut-être le meilleur nom pour des raisons historiques, mais il ne dit pas vraiment plus que Ones si vous n'êtes pas familier avec le terme et que le même problème est simplement Count: Population of what?

C'est quelque peu univoque, même dans le package, mais peut-être CountOnes pour lui donner un nom clair et évident.

@aclements Compter quoi ?

Dans un package appelé "bits", je ne sais pas ce que vous pourriez compter d'autre à l'exception des bits définis, mais CountOnes est également bien et évidemment plus explicite. L'ajout de « Uns » est également parallèle aux « Zéros » dans LeadingZeros / TrailingZeros .

C'est l'interprétation la plus évidente mais il y a encore une ambiguïté. Il peut s'agir de compter des bits non définis ou des bits totaux (la dernière interprétation serait extrêmement improbable, mais resterait un piège potentiel pour quelqu'un qui lit du code utilisant des bits qui n'est pas familier avec les concepts impliqués et pourrait penser que le nombre sans suffixe est comme dangereux.SizeOf)

Peut-être quelque chose comme AllOnes ou TotalOnes pour refléter Trailing/LeadingZeroes mais précisez que, contrairement à ceux-ci, la position n'est pas prise en compte.

AllOnes donne l'impression qu'il renvoie tous les uns (dans un masque de bits peut-être ?), ou peut-être un mot qui n'est que des uns.

CountOnes et TotalOnes semblent à peu près identiques, mais comme le nom le plus connu de cette opération est "population count", CountOnes semble préférable.

J'ai téléchargé une nouvelle version (https://go-review.googlesource.com/#/c/36315/) avec quelques modifications :

1) J'ai renommé Ones en PopCount : La plupart des gens n'étaient pas trop enthousiasmés par le nom cohérent mais quelque peu terne Ones . Tous ceux qui vont utiliser ce package sauront exactement ce que fait PopCount : c'est le nom sous lequel cette fonction est communément connue. C'est légèrement incohérent avec le reste mais sa signification est devenue tellement plus claire. Je vais avec Ralph Waldo Emerson sur celui-ci ("Une cohérence stupide est le hobgoblin des petits esprits...").

2) J'ai renommé Log en Len comme dans bits.Len . La longueur en bits d'un nombre x est le nombre de bits qu'il faut pour représenter x en représentation binaire (https://en.wikipedia.org/wiki/Bit-length). Semble approprié, et supprime le besoin d'avoir Log ici qui a une qualité non "bit-fiddly". Je crois que c'est aussi une victoire parce que Len(x) == Log(x) + 1 étant donné la façon dont nous avons défini Log . Il a en outre l'avantage que le résultat est maintenant toujours >= 0, et il supprime quelques corrections +/-1 dans l'implémentation (triviale).

Dans l'ensemble, je suis assez satisfait de cette API à ce stade (nous voudrons peut-être ajouter plus de fonctions plus tard). La seule autre chose que je pense que nous pourrions envisager sérieusement est de savoir si tous les résultats doivent être non signés. Comme je l'ai souligné précédemment, les résultats de la fonction Trailing/Leading Zero ont tendance à être des entrées pour les opérations de décalage qui doivent être non signées. Cela ne laisse que Len et PopCount qui pourraient sans doute également renvoyer des valeurs non signées.

Commentaires?

Mon expérience avec les mathématiques/grand a été la frustration d'être forcé en mode uint par des fonctions lorsque je ne change pas de vitesse. Dans math/big/prime.go, j'ai écrit

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

même si s.bitLen() renvoie int pas uint, car je n'étais pas sûr sans regarder, et je n'étais pas sûr non plus qu'une future CL ne le modifierait pas pour renvoyer uint, transformant ainsi cette boucle for en une boucle infinie. Le fait d'avoir besoin d'être sur la défensive suggère qu'il y a un problème.

Les uints sont beaucoup plus sujets aux erreurs que les ints, c'est pourquoi nous les déconseillons dans la plupart des API. Je pense que ce serait beaucoup plus agréable si chaque fonction dans math/bits retournait int et laissait la conversion en uint à faire dans l'expression shift, comme c'est de toute façon nécessaire dans la plupart des expressions shift.

(Je ne propose pas de changement, mais je pense que le changement nécessitant uint a peut-être été une erreur rétrospectivement. Peut-être que nous pourrons le corriger à un moment donné. Ce serait bien si cela ne nuisait pas à nos autres API.)

Je suis en faveur du retour d'int après avoir récupéré mon code pour les appels des zéros à droite et des fonctions popcount. La majorité des appels sont des déclarations de variables courtes et des comparaisons de type int. Les appels en équipes nécessitent bien sûr le type uint, mais sont étonnamment rares.

Ajustements mineurs téléchargés. Pour les +1 et les commentaires, laissons les résultats des comptages à int .

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

J'ai laissé l'entrée compte pour RotateLeft/Right comme uint sinon nous devrons spécifier ce qui se passe lorsque la valeur est négative ou l'interdire.

Enfin, nous pourrions même laisser de côté Len après tout puisque LenN(x) == N - LeadingZerosN(x) . Des avis?

S'il n'y a plus de commentaires significatifs à ce stade, je suggérerais que nous avancions avec cette API et commencions une implémentation initiale après examen. Après cela, nous pouvons commencer à peaufiner la mise en œuvre.

Dans une prochaine étape, nous pourrions vouloir discuter si et quelles autres fonctions nous pourrions inclure, en particulier Add / Sub / etc. qui consomment 2 arguments et portent, et produisent un résultat et portent .

@gri pensées sur une fonction Len base 10 ? Ce serait juste ((N - clz(x) + 1) * 1233) >> 12

C'est moins "peu bidon" que la base 2 mais toujours utile.
Le vendredi 10 février 2017 à 17h03 Robert Griesemer [email protected]
a écrit:

Ajustements mineurs téléchargés. Par +1 et commentaires laissons les résultats de
compte comme int.

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

J'ai laissé le nombre d'entrées pour RotateLeft/Right comme uint sinon nous le ferons
besoin de spécifier ce qui se passe lorsque la valeur est négative ou de l'interdire.

Enfin, nous pourrions même laisser de côté Len après tout puisque LenN(x) == N -
LeadingZerosN(x). Des avis?

S'il n'y a plus de commentaires significatifs à ce stade, je suggérerais que nous
aller de l'avant avec cette API et engager une implémentation initiale après
revoir. Après cela, nous pouvons commencer à peaufiner la mise en œuvre.

I une prochaine étape, nous pouvons vouloir discuter si et quelles autres fonctions nous pourrions
voulez inclure, en particulier Add/Sub/ etc. qui consomment 2 arguments et
porter, et produire un résultat et transporter.

-
Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/golang/go/issues/18616#issuecomment-279107013 , ou couper le son
le fil
https://github.com/notifications/unsubscribe-auth/AFnwZ_QMhMtBZ_mzQAb7XZDucXrpliSYks5rbQjCgaJpZM4Lg5zU
.

Aussi. Excusez-moi si je m'éloigne du sujet de cette question, mais je serais en faveur de l'arithmétique vérifiée. Par exemple AddN(x, y T) (sum T, overflow bool)

Le vendredi 10 février 2017 à 20h16, Eric Lagergren [email protected]
a écrit:

Aussi. Excusez-moi si je m'éloigne du sujet de cette question, mais je serais dans
faveur de l'arithmétique vérifiée. Par exemple AddN(x, y T) (somme T, débordement bool)

Parlez-vous de débordement signé? ou débordement non signé (porter/emprunter) ?

Aussi, je préfère retourner overflow/carry/borrow en tant que type T, pour simplifier
arithmétique multi-précision.

@ericlagergren La fonction (base 2) Len , bien que presque log2, est toujours essentiellement une fonction de comptage de bits. Une fonction Len base 10 est en fait une fonction de journalisation. Il est indéniable que c'est utile, mais cela semble moins approprié pour ce package.

Oui, je pense que ce serait bien d'avoir checked arithmétique

Concernant les fonctions Add, Sub : je suis d'accord avec @minux que le carry doit être du même type que l'argument. De plus, je ne suis pas convaincu que nous ayons besoin d'autre chose que d'arguments uint pour ces fonctions : le fait est que (dans la plupart des cas) uint a la taille de registre naturelle de la plate-forme, et c'est le niveau auquel ces opérations ont le plus de sens.

Un package mathématique/vérifié pourrait être un meilleur foyer pour ceux-ci. checked.Add est plus clair que bits.Add .

@minux Je pensais que l'arithmétique vérifiée était signée, alors débordez.

@griesemer re: base 10 Len : logique !

Si vous le souhaitez, je peux soumettre un CL pour l'arithmétique vérifiée. J'aime l'idée de @jimmyfrasche de le faire sous un nom de package distinct.

Add/sub/mul peut appartenir ou non à bits , mais les maths vérifiées ne sont pas la seule utilisation pour ces opérations. Plus généralement, je dirais que ce sont des opérations arithmétiques "larges". Pour add/sub, il y a peu de différence entre le résultat d'un seul bit de report/overflow et un indicateur booléen de débordement, mais nous voudrions probablement aussi fournir add-with-carry et subtract-with-borrow pour les rendre chaînables. Et pour les mul larges, il y a beaucoup plus d'informations dans le résultat supplémentaire que le débordement.

C'est une bonne idée de se souvenir des opérations arithmétiques vérifiées/larges, mais laissons-les pour le deuxième tour.

"Tous ceux qui vont utiliser ce package sauront exactement ce que fait PopCount"

J'ai déjà utilisé cette fonctionnalité et d'une manière ou d'une autre, je ne connaissais pas le nom PopCount (bien que j'aurais dû le faire car je suis sûr d'avoir pincé l'implémentation de Hacker's Delight, qui utilise "pop").

Je sais que je suis à la fête, mais "OnesCount" me semble considérablement plus évident, et si le mot "PopCount" est mentionné dans le commentaire du doc, les personnes qui recherchent cela le trouveront de toute façon.

Lié à l'arithmétique vérifiée/large : #6815. Mais oui, passons au premier tour !

@griesemer a écrit :

La fonction Len (base 2), bien que presque log2, reste essentiellement une fonction de comptage de bits. Une fonction Len de base 10 est en réalité une fonction de journalisation. Il est indéniable que c'est utile, mais cela semble moins approprié pour ce package.

J'ai écrit un benchmark pour comparer quelques approches du problème de la "longueur décimale" en octobre dernier, que j'ai posté dans un résumé .

Accepté comme proposition ; il y aura probablement des ajustements mineurs à l'avenir, et c'est bien.

@rogpeppe : Changé PopCount en OnesCount car il a également reçu 4 pouces levés (comme ma suggestion d'utiliser PopCount ). Fait référence à « population count » dans la chaîne de documentation.

Par @rsc , en laissant de côté les opérations arithmétiques vérifiées/larges pour le moment.

Toujours selon @rsc , toutes les fonctions de comptage renvoient des valeurs int , et pour faciliter l'utilisation (et en gardant un œil sur #19113), utilisez les valeurs int pour le nombre de rotations.

On a laissé les fonctions LenN même si ce sont simplement des N - LeadingZerosN . Mais une symétrie similaire existe pour RotateLeft / Right et nous avons les deux.

Ajout d'une implémentation légèrement plus rapide pour TrailingZeroes et tests terminés.

À ce stade, je pense que nous avons une première implémentation utilisable. Veuillez consulter le code sur https://go-review.googlesource.com/36315 , en particulier l'API. J'aime que cela soit soumis si nous sommes tous heureux.

Prochaines étapes:

  • mise en œuvre plus rapide (primaire)
  • ajout de fonctionnalités supplémentaires (secondaire)

Nous concevons un nouveau package

@minux Vous voulez dire nouveaux maths/grands ? Est-il possible de suivre le processus quelque part?

@TuomLarsen : @minux fait référence à math/bits avec "nouveau package". Il a mentionné math/big comme un cas où math/bits serait utilisé. (À l'avenir, soyez plus précis dans vos commentaires afin que nous n'ayons pas à chercher et à deviner à quoi vous faites référence - merci).

CL https://golang.org/cl/37140 mentionne ce problème.

Y aura-t-il une intégration assistée par le compilateur dans les mathématiques/bits pour Go 1.9 ?

@cespare Cela dépend si nous y arrivons (@khr?). Indépendamment de cela, nous voulons avoir une implémentation décente et indépendante de la plate-forme. (L'une des raisons pour lesquelles nous ne voulons pas passer entièrement de math/big à l'utilisation de math/bits est qu'actuellement nous avons un assembly spécifique à la plate-forme dans math/big qui est plus rapide.)

Dans mon assiette, au moins pour les arches on fait déjà des intrinsèques pour
(386,amd64,arm,arm64,s390x,mips, probablement ppc64).

Le ven 17 février 2017 à 12:54, Robert Griesemer < [email protected]

a écrit:

@cespare https://github.com/cespare Cela dépend si nous y arrivons (
@khr https://github.com/khr ?). Indépendamment de cela, nous voulons avoir un
implémentation décente indépendante de la plate-forme. (L'une des raisons pour lesquelles nous ne
vouloir déplacer complètement math/big vers l'utilisation de math/bits est qu'actuellement nous
avoir un assembly spécifique à la plate-forme dans math/big qui est plus rapide.)

-
Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/golang/go/issues/18616#issuecomment-280763679 , ou couper le son
le fil
https://github.com/notifications/unsubscribe-auth/AGkgIIb8v1X5Cr-ljDgf8tQtT4Dg2MGiks5rdgkegaJpZM4Lg5zU
.

Cet article sur le moyen le plus rapide d'implémenter le décompte de population sur x86-64 peut être utile : l' assemblage codé à la main bat les valeurs intrinsèques en termes de vitesse et de simplicité - Dan Luu, octobre 2014

CL https://golang.org/cl/38155 mentionne ce problème.

CL https://golang.org/cl/38166 mentionne ce problème.

CL https://golang.org/cl/38311 mentionne ce problème.

CL https://golang.org/cl/38320 mentionne ce problème.

CL https://golang.org/cl/38323 mentionne ce problème.

Quelques discussions supplémentaires :

Moi:

math/bits.RotateLeft est actuellement défini pour paniquer si son argument est inférieur à zéro.
J'aimerais changer cela pour définir RotateLeft pour faire une rotation à droite si son argument est inférieur à zéro.

Avoir une routine de base comme celle-ci semble paniquer inutilement. Je dirais qu'une rotation d'un montant négatif est plus analogue à un décalage plus grand que la taille du mot (ce qui ne panique pas) au lieu d'une division par zéro (ce qui fait paniquer). La division par zéro doit vraiment paniquer, car il n'y a pas de résultat raisonnable que vous reviendriez à la place. Les rotations de montants négatifs ont un résultat parfaitement bien défini que nous pouvons renvoyer.

Je pense que nous devrions conserver RotateLeft et RotateRight en tant que fonctions distinctes, même si l'une peut désormais être implémentée avec l'autre. L'utilisation standard sera toujours avec des arguments non négatifs.

Si nous voulons faire quelque chose ici, nous devrions le faire avant le gel. Une fois que go 1.9 est sorti, nous ne pouvons pas changer d'avis.

Rob:

Si vous voulez vraiment quelque chose comme ça, j'aurais juste une fonction, Rotation, qui prend un positif pour la gauche et un négatif pour la droite.

Moi:

Le problème est
bits.Rotation(x, -5)

Il n'est pas clair à la lecture de ce code si nous finissons par tourner à gauche ou à droite.
bits.RotationDroite(5)
est beaucoup plus clair, même si cela signifie qu'il y a deux fois plus de fonctions Rotate* en math/bits.

Michael Jones :

La rotation signée signifie des sauts dans le code, non ? Cela semble dommage.

Moi:

Non, la mise en œuvre est en fait plus simple avec la sémantique proposée. Masquez les 6 bits faibles (pour les rotations de 64 bits) et cela fonctionne.

Je préfère le seul Rotate avec un argument signé car cela signifie la moitié du nombre de fonctions et peut être effectué très efficacement dans tous les cas sans panique ni même branche conditionnelle.

La rotation est de toute façon une fonction spécialisée, donc les personnes qui l'utilisent seront sûrement à l'aise de se rappeler qu'un argument positif se déplace vers la gauche et un argument négatif vers la droite.

Vous pouvez toujours diviser la différence et fournir uniquement RotateLeft avec un argument signé. Cela donne un mnémonique pratique pour la direction mais évite de dupliquer les fonctions.

@bcmills @robpike voir aussi la discussion précédente sur ce sujet précis commençant à https://github.com/golang/go/issues/18616#issuecomment -275598092 et continuant pour quelques commentaires

@josharian J'ai vu les commentaires et je préfère toujours ma version. Cela peut être perdu pour toujours, mais j'essaie de devenir simple à mettre en œuvre, simple à définir, simple à comprendre et simple à documenter. Je pense qu'une seule fonction Rotate avec un argument signé satisfait tout cela, à l'exception de la nécessité de définir le signe, mais ce qui reste positif devrait être intuitif pour quiconque est capable d'utiliser une instruction de rotation.

mais ce qui reste est positif devrait être intuitif pour quiconque est capable d'utiliser une instruction de rotation.

J'aime me considérer capable d'utiliser une instruction de rotation et mon intuition est "Bon sang, pourquoi cela ne dit-il pas dans quelle direction c'est? C'est probablement à gauche, mais je vais devoir regarder la documentation pour être sûr ." Je suis d'accord qu'il est intuitif que si vous deviez choisir une direction positive, ce serait une rotation à gauche, mais il y a un seuil beaucoup plus élevé pour que quelque chose soit si évidemment la bonne réponse qu'il est clair sur chaque site d'appel dans quelle direction vous tournez sans dire ce.

En ce qui concerne la lisibilité, que diriez-vous de quelque chose dans le style de l'API time.Duration :

const RotateRight = -1

bits.Rotate(x, 5 * RotateRight)

Peut-être une constante définie par des bits ou peut-être un exercice pour le lecteur (appel site) ?

@aclements Et vous vous retrouvez donc avec deux fonctions (

Sur l'axe des nombres, les nombres positifs augmentent vers la droite, donc je m'attends à ce qu'une rotation/décalage dans le sens défini par le signe déplace les bits vers la droite pour les nombres positifs.

Je n'ai aucun problème si ça va être le contraire [documenté], mais je n'appellerais pas cela intuitif.

Les bits

Je suis également en faveur de seulement Rotate (https://github.com/golang/go/issues/18616#issuecomment-275016583) si nous le faisons fonctionner dans les deux sens.

En tant que contre-argument @aclements préoccupation au sujet de la direction: Fournir un RotateLeft qui fonctionne également lorsque le droit de rotation peut également fournir un faux sentiment de sécurité: « Il dit RotateLeft si sûrement ce n'est pas tourner droit!". En d'autres termes, s'il dit RotateLeft il vaut mieux ne rien faire d'autre.

De plus, l'utilisation de bits.Rotate n'est en réalité que dans le code spécialisé. Ce n'est pas une fonction qui est utilisée par un grand nombre de programmeurs. Les utilisateurs qui en ont vraiment besoin comprendront la symétrie des rotations.

@nathanie

les bits sont écrits de droite à gauche cependant

Les bits ne sont que des chiffres binaires. Les chiffres en nombre dans n'importe quelle base sont écrits de gauche à droite - même dans la plupart, sinon tous, les systèmes d'écriture de droite à gauche. 123 vaut cent vingt-trois, pas trois cent vingt et un.

Que la puissance du multiplicande pour le chiffre décroisse vers la droite est une autre chose.

Encore une fois : je me fiche de la direction, c'est juste que l'intuitif est une question d'imagination personnelle.

J'aime Rotation. Le bit le moins significatif est intuitivement assez 0 à mon avis.

Veuillez conserver RotateLeft et RotateRight au lieu de faire quelque chose dont la moitié des développeurs se souviendra mal. Cependant, il semble bien de gérer des nombres négatifs.

99% des développeurs ne programmeront jamais une instruction de rotation, donc le besoin d'une direction sans ambiguïté est au mieux faible. Un seul appel suffit.

Le problème qui a relancé cette discussion est qu'avoir les deux nécessite de se demander si les valeurs négatives sont acceptables, et sinon, que faire à leur sujet. En n'en ayant qu'un, tout cet argument tombe. C'est un design plus propre.

Je sympathise quelque peu avec l'argument concernant la conception épurée, mais il semble étrange que vous deviez supprimer "Right" de "RotateRight", tout en conservant la même implémentation, afin d'y parvenir. Concrètement, la seule façon dont il semble répondre aux questions est de forcer les personnes qui le voient à lire la documentation, à travers les questions que soulève son nom.
En fin de compte, c'est une question de direction sans ambiguïté par rapport à un comportement sans ambiguïté pour les valeurs négatives. Les valeurs négatives devraient probablement être moins préoccupantes dans le cas courant.

Ce que je dis, c'est que Rotate soulève une question pour tout le monde, y répondant indirectement par le biais de la documentation.
RotateRight soulève une question pour très peu de personnes qui peuvent (et devraient) lire la documentation si elles s'en soucient.

D'un autre côté, Rotate empêcherait probablement les gens d'écrire if n < 0 { RotateLeft(...) } else { RotateRight(...) } .

@golang/proposal-review en a discuté et a fini par n'avoir qu'une seule fonction, mais la nommant RotateLeft , pas seulement Rotate , pour être plus clair sur les sites d'appel. Les nombres négatifs tournent à droite, et la documentation le montrera clairement.

CL https://golang.org/cl/40394 mentionne ce problème.

CL https://golang.org/cl/41630 mentionne ce problème.

La proposition originale ainsi que quelques fonctions supplémentaires ont été conçues et mises en œuvre à ce stade. Nous pouvons ajouter à cette bibliothèque au fil du temps, mais elle semble raisonnablement "complète" pour le moment.

Plus particulièrement, nous n'avons pas décidé ni mis en œuvre de fonctionnalités pour :

  • tester si add/mul/etc overflow
  • fournir des fonctions pour implémenter add/mul/etc qui acceptent un report entrant et produisent un résultat plus report (ou mot supérieur)

Personnellement, je ne suis pas convaincu que ceux-ci appartiennent à un package "bits" (peut-être que les tests le font). Les fonctions pour implémenter add/sub/mul multi-précision permettraient une implémentation Go pure de certains des noyaux mathématiques/big, mais je ne pense pas que la granularité soit correcte : ce que nous voulons, ce sont des noyaux optimisés fonctionnant sur des vecteurs, et un maximum performances pour ces noyaux. Je ne crois pas que nous puissions y parvenir avec le code Go en fonction des "intrinsèques" add/sub/mul uniquement.

Ainsi, pour l'instant, j'aime clore ce problème comme "terminé" à moins qu'il n'y ait des objections majeures. Veuillez vous exprimer au cours de la semaine prochaine si vous êtes contre la fermeture de ceci.

Je suis en faveur de l'ajout de fonctions dans ce sens.

Je crois fermement qu'ils appartiennent à leur propre package, ne serait-ce que pour lui donner un nom qui reflète mieux leur fonctionnalité collective.

:+1: sur la fermeture de ce problème et :heart: pour le travail accompli jusqu'à présent.

Clôture car il n'y a pas eu d'objections.

Ceci est un commentaire pour les décisions futures concernant l'API, je comprends que celui-ci est défini.

Rotate est une fonction spécialisée ; LTR ou RTL n'est pertinent qu'en fonction du contexte. @aclements a soulevé une question valide, pas un point valide sans fin. Sa question aurait pu être répondue comme "c'est RTL, tout comme les entiers s'incrémentent" ; facile, non?

Mais au lieu de cela, l'intelligence s'ensuit.

Ce que signifie « une fonction de spécialiste » est une chose si simple qu'elle a probablement été rejetée trop rapidement. Étant donné un échantillon de code, on comprend probablement déjà la rotation à effectuer et la direction avant même de rencontrer la ligne de code. Un tel code est généralement déjà précédé d'une documentation ascii illustrative telle qu'elle est.

Ce qui est mentalement turbulent, ce n'est pas que Go aurait simplement pu choisir RTL comme moyen standard d'interpréter les bits du point de vue de l'API, mais plutôt, j'ai d'abord relevé les modifications de 1.9 et trouvé un RotateLeft sans contrepartie et le doc donnant un exemple d'un foulée négative. Il s'agit d'une décision abrutissante de type comité qui est très malheureuse d'arriver dans la 1.9.

Je plaide seulement pour m'en tenir au contexte d'utilisation pour l'avenir. Tout cela aurait dû aller de soi avec des questions telles que « pourquoi ne fournissons-nous pas de contrepartie à RotateLeft, pourquoi paniquons-nous sur des progrès négatifs ou débattons-nous de l'int contre le uint pour une foulée » ; en fin de compte, parce que je pense que ce que signifie "une fonction de spécialiste" a été simplement rejeté trop facilement pour ne pas être intelligent.

Évitons s'il vous plaît l'intelligence dans notre justification des API. Cela se voit dans cette mise à jour 1.9.

Le changement https://golang.org/cl/90835 mentionne ce problème : cmd/compile: arm64 intrinsics for math/bits.OnesCount

Cette page vous a été utile?
0 / 5 - 0 notes