[algorithm] Comment compter le nombre de bits définis dans un entier de 32 bits?



Answers

Considérez également les fonctions intégrées de vos compilateurs.

Sur le compilateur GNU par exemple, vous pouvez simplement utiliser:

int __builtin_popcount (unsigned int x);
int __builtin_popcountll (unsigned long long x);

Dans le pire des cas, le compilateur va générer un appel à une fonction. Dans le meilleur des cas, le compilateur émet une instruction cpu pour effectuer le même travail plus rapidement.

Les intrinsèques de GCC fonctionnent même sur plusieurs plates-formes. Popcount deviendra grand public dans l'architecture x86, il est donc logique de commencer à utiliser l'intrinsèque maintenant. D'autres architectures ont le popcount depuis des années.

Sur x86, vous pouvez dire au compilateur qu'il peut prendre en charge l'instruction -mpopcnt avec -mpopcnt ou -msse4.2 pour activer également les instructions vectorielles qui ont été ajoutées dans la même génération. Voir les options de GCC x86 . -march=nehalem (ou -march= quel que soit le CPU que vous voulez que votre code prenne et pour syntoniser) pourrait être un bon choix. L'exécution du binaire résultant sur un ancien processeur entraînera une erreur d'instruction illégale.

Pour rendre les binaires optimisés pour la machine sur laquelle vous les construisez, utilisez -march=native (avec gcc, clang ou ICC).

MSVC fournit un intrinsèque pour l'instruction popcnt x86 , mais contrairement à gcc c'est vraiment un intrinsèque pour l'instruction matérielle et nécessite un support matériel.

Utiliser std::bitset<>::count() au lieu d'un built-in

En théorie, tout compilateur qui sait calculer efficacement le processeur cible devrait exposer cette fonctionnalité via ISO C ++ std::bitset<> . En pratique, vous pourriez être mieux avec le bit-hack AND / shift / ADD dans certains cas pour certains processeurs cibles.

Pour les architectures cibles où le popcount matériel est une extension facultative (comme x86), tous les compilateurs n'ont pas un std::bitset qui en profite quand il est disponible. Par exemple, MSVC n'a aucun moyen d'activer le support popcnt au moment de la compilation, et utilise toujours une recherche de table , même avec /Ox /arch:AVX (ce qui implique SSE4.2, bien que techniquement il existe un bit de fonctionnalité séparé pour popcnt ).

Mais au moins, vous obtenez quelque chose de portable qui fonctionne partout, et avec gcc / clang avec les bonnes options de cible, vous obtenez du popcount matériel pour les architectures qui le supportent.

#include <bitset>
#include <limits>
#include <type_traits>

template<typename T>
//static inline  // static if you want to compile with -mpopcnt in one compilation unit but not others
typename std::enable_if<std::is_integral<T>::value,  unsigned >::type 
popcount(T x)
{
    static_assert(std::numeric_limits<T>::radix == 2, "non-binary type");

    // sizeof(x)*CHAR_BIT
    constexpr int bitwidth = std::numeric_limits<T>::digits + std::numeric_limits<T>::is_signed;
    // std::bitset constructor was only unsigned long before C++11.  Beware if porting to C++03
    static_assert(bitwidth <= std::numeric_limits<unsigned long long>::digits, "arg too wide for std::bitset() constructor");

    typedef typename std::make_unsigned<T>::type UT;        // probably not needed, bitset width chops after sign-extension

    std::bitset<bitwidth> bs( static_cast<UT>(x) );
    return bs.count();
}

Voir asm de gcc, clang, icc et MSVC sur l'explorateur du compilateur Godbolt.

x86-64 gcc -O3 -std=gnu++11 -mpopcnt émet ceci:

unsigned test_short(short a) { return popcount(a); }
    movzx   eax, di      # note zero-extension, not sign-extension
    popcnt  rax, rax
    ret
unsigned test_int(int a) { return popcount(a); }
    mov     eax, edi
    popcnt  rax, rax
    ret
unsigned test_u64(unsigned long long a) { return popcount(a); }
    xor     eax, eax     # gcc avoids false dependencies for Intel CPUs
    popcnt  rax, rdi
    ret

PowerPC64 gcc -O3 -std=gnu++11 émet (pour la version int arg):

    rldicl 3,3,0,32     # zero-extend from 32 to 64-bit
    popcntd 3,3         # popcount
    blr

Cette source n'est pas spécifique au x86 ni spécifique au GNU, mais ne se compile bien que pour x86 avec gcc / clang / icc.

Notez également que le repli de gcc pour les architectures sans popcount à instruction unique est une recherche de table en octets à la fois. Ce n'est pas merveilleux pour ARM, par exemple .

Question

8 bits représentant le numéro 7 ressemblent à ceci:

00000111

Trois bits sont définis.

Que sont les algorithmes pour déterminer le nombre de bits définis dans un entier de 32 bits?




D'après Hacker's Delight, p. 66, Figure 5-2

int pop(unsigned x)
{
    x = x - ((x >> 1) & 0x55555555);
    x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
    x = (x + (x >> 4)) & 0x0F0F0F0F;
    x = x + (x >> 8);
    x = x + (x >> 16);
    return x & 0x0000003F;
}

Exécute dans les instructions ~ 20-ish (dépend de l'arche), pas de branchement.

Hacker's Delight est délicieux! Hautement recommandé.




Le Hacker's Delight devient de plus en plus clair lorsque vous écrivez les modèles de bits.

unsigned int bitCount(unsigned int x)
{
  x = (((x >> 1) & 0b01010101010101010101010101010101)
       + x       & 0b01010101010101010101010101010101);
  x = (((x >> 2) & 0b00110011001100110011001100110011)
       + x       & 0b00110011001100110011001100110011); 
  x = (((x >> 4) & 0b00001111000011110000111100001111)
       + x       & 0b00001111000011110000111100001111); 
  x = (((x >> 8) & 0b00000000111111110000000011111111)
       + x       & 0b00000000111111110000000011111111); 
  x = (((x >> 16)& 0b00000000000000001111111111111111)
       + x       & 0b00000000000000001111111111111111); 
  return x;
}

La première étape ajoute les bits pairs aux bits impairs, produisant une somme de bits dans chaque deux. Les autres étapes ajoutent des blocs d'ordre supérieur à des blocs de poids faible, doublant ainsi la taille du bloc jusqu'à ce que le nombre final prenne la totalité de l'entier.




I found an implementation of bit counting in an array with using of SIMD instruction (SSSE3 and AVX2). It has in 2-2.5 times better performance than if it will use __popcnt64 intrinsic function.

SSSE3 version:

#include <smmintrin.h>
#include <stdint.h>

const __m128i Z = _mm_set1_epi8(0x0);
const __m128i F = _mm_set1_epi8(0xF);
//Vector with pre-calculated bit count:
const __m128i T = _mm_setr_epi8(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4);

uint64_t BitCount(const uint8_t * src, size_t size)
{
    __m128i _sum =  _mm128_setzero_si128();
    for (size_t i = 0; i < size; i += 16)
    {
        //load 16-byte vector
        __m128i _src = _mm_loadu_si128((__m128i*)(src + i));
        //get low 4 bit for every byte in vector
        __m128i lo = _mm_and_si128(_src, F);
        //sum precalculated value from T
        _sum = _mm_add_epi64(_sum, _mm_sad_epu8(Z, _mm_shuffle_epi8(T, lo)));
        //get high 4 bit for every byte in vector
        __m128i hi = _mm_and_si128(_mm_srli_epi16(_src, 4), F);
        //sum precalculated value from T
        _sum = _mm_add_epi64(_sum, _mm_sad_epu8(Z, _mm_shuffle_epi8(T, hi)));
    }
    uint64_t sum[2];
    _mm_storeu_si128((__m128i*)sum, _sum);
    return sum[0] + sum[1];
}

AVX2 version:

#include <immintrin.h>
#include <stdint.h>

const __m256i Z = _mm256_set1_epi8(0x0);
const __m256i F = _mm256_set1_epi8(0xF);
//Vector with pre-calculated bit count:
const __m256i T = _mm256_setr_epi8(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 
                                   0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4);

uint64_t BitCount(const uint8_t * src, size_t size)
{
    __m256i _sum =  _mm256_setzero_si256();
    for (size_t i = 0; i < size; i += 32)
    {
        //load 32-byte vector
        __m256i _src = _mm256_loadu_si256((__m256i*)(src + i));
        //get low 4 bit for every byte in vector
        __m256i lo = _mm256_and_si256(_src, F);
        //sum precalculated value from T
        _sum = _mm256_add_epi64(_sum, _mm256_sad_epu8(Z, _mm256_shuffle_epi8(T, lo)));
        //get high 4 bit for every byte in vector
        __m256i hi = _mm256_and_si256(_mm256_srli_epi16(_src, 4), F);
        //sum precalculated value from T
        _sum = _mm256_add_epi64(_sum, _mm256_sad_epu8(Z, _mm256_shuffle_epi8(T, hi)));
    }
    uint64_t sum[4];
    _mm256_storeu_si256((__m256i*)sum, _sum);
    return sum[0] + sum[1] + sum[2] + sum[3];
}



This can be done in O(k) , where k is the number of bits set.

int NumberOfSetBits(int n)
{
    int count = 0;

    while (n){
        ++ count;
        n = (n - 1) & n;
    }

    return count;
}



Fast C# solution using pre-calculated table of Byte bit counts with branching on input size.

public static class BitCount
{
    public static uint GetSetBitsCount(uint n)
    {
        var counts = BYTE_BIT_COUNTS;
        return n <= 0xff ? counts[n]
             : n <= 0xffff ? counts[n & 0xff] + counts[n >> 8]
             : n <= 0xffffff ? counts[n & 0xff] + counts[(n >> 8) & 0xff] + counts[(n >> 16) & 0xff]
             : counts[n & 0xff] + counts[(n >> 8) & 0xff] + counts[(n >> 16) & 0xff] + counts[(n >> 24) & 0xff];
    }

    public static readonly uint[] BYTE_BIT_COUNTS = 
    {
        0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,
        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
        4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8
    };
}



Pourquoi ne pas diviser itérativement par 2?

count = 0
while n > 0
  if (n % 2) == 1
    count += 1
  n /= 2  

Je suis d'accord que ce n'est pas le plus rapide, mais le «meilleur» est quelque peu ambigu. Je dirais que le «meilleur» devrait avoir un élément de clarté




I'm particularly fond of this example from the fortune file:

#define BITCOUNT(x)    (((BX_(x)+(BX_(x)>>4)) & 0x0F0F0F0F) % 255)
#define BX_(x)         ((x) - (((x)>>1)&0x77777777)
                             - (((x)>>2)&0x33333333)
                             - (((x)>>3)&0x11111111))

I like it best because it's so pretty!




I wrote a fast bitcount macro for RISC machines in about 1990. It does not use advanced arithmetic (multiplication, division, %), memory fetches (way too slow), branches (way too slow), but it does assume the CPU has a 32-bit barrel shifter (in other words, >> 1 and >> 32 take the same amount of cycles.) It assumes that small constants (such as 6, 12, 24) cost nothing to load into the registers, or are stored in temporaries and reused over and over again.

With these assumptions, it counts 32 bits in about 16 cycles/instructions on most RISC machines. Note that 15 instructions/cycles is close to a lower bound on the number of cycles or instructions, because it seems to take at least 3 instructions (mask, shift, operator) to cut the number of addends in half, so log_2(32) = 5, 5 x 3 = 15 instructions is a quasi-lowerbound.

#define BitCount(X,Y)           \
                Y = X - ((X >> 1) & 033333333333) - ((X >> 2) & 011111111111); \
                Y = ((Y + (Y >> 3)) & 030707070707); \
                Y =  (Y + (Y >> 6)); \
                Y = (Y + (Y >> 12) + (Y >> 24)) & 077;

Here is a secret to the first and most complex step:

input output
AB    CD             Note
00    00             = AB
01    01             = AB
10    01             = AB - (A >> 1) & 0x1
11    10             = AB - (A >> 1) & 0x1

so if I take the 1st column (A) above, shift it right 1 bit, and subtract it from AB, I get the output (CD). The extension to 3 bits is similar; you can check it with an 8-row boolean table like mine above if you wish.

  • Don Gillies



I think the Brian Kernighan's method will be useful too... It goes through as many iterations as there are set bits. So if we have a 32-bit word with only the high bit set, then it will only go once through the loop.

int countSetBits(unsigned int n) { 
    unsigned int n; // count the number of bits set in n
    unsigned int c; // c accumulates the total bits set in n
    for (c=0;n>0;n=n&(n-1)) c++; 
    return c; 
}

Published in 1988, the C Programming Language 2nd Ed. (by Brian W. Kernighan and Dennis M. Ritchie) mentions this in exercise 2-9. On April 19, 2006 Don Knuth pointed out to me that this method "was first published by Peter Wegner in CACM 3 (1960), 322. (Also discovered independently by Derrick Lehmer and published in 1964 in a book edited by Beckenbach.)"




Here is a portable module ( ANSI-C ) which can benchmark each of your algorithms on any architecture.

Your CPU has 9 bit bytes? No problem :-) At the moment it implements 2 algorithms, the K&R algorithm and a byte wise lookup table. The lookup table is on average 3 times faster than the K&R algorithm. If someone can figure a way to make the "Hacker's Delight" algorithm portable feel free to add it in.

#ifndef _BITCOUNT_H_
#define _BITCOUNT_H_

/* Return the Hamming Wieght of val, i.e. the number of 'on' bits. */
int bitcount( unsigned int );

/* List of available bitcount algorithms.  
 * onTheFly:    Calculate the bitcount on demand.
 *
 * lookupTalbe: Uses a small lookup table to determine the bitcount.  This
 * method is on average 3 times as fast as onTheFly, but incurs a small
 * upfront cost to initialize the lookup table on the first call.
 *
 * strategyCount is just a placeholder. 
 */
enum strategy { onTheFly, lookupTable, strategyCount };

/* String represenations of the algorithm names */
extern const char *strategyNames[];

/* Choose which bitcount algorithm to use. */
void setStrategy( enum strategy );

#endif

.

#include <limits.h>

#include "bitcount.h"

/* The number of entries needed in the table is equal to the number of unique
 * values a char can represent which is always UCHAR_MAX + 1*/
static unsigned char _bitCountTable[UCHAR_MAX + 1];
static unsigned int _lookupTableInitialized = 0;

static int _defaultBitCount( unsigned int val ) {
    int count;

    /* Starting with:
     * 1100 - 1 == 1011,  1100 & 1011 == 1000
     * 1000 - 1 == 0111,  1000 & 0111 == 0000
     */
    for ( count = 0; val; ++count )
        val &= val - 1;

    return count;
}

/* Looks up each byte of the integer in a lookup table.
 *
 * The first time the function is called it initializes the lookup table.
 */
static int _tableBitCount( unsigned int val ) {
    int bCount = 0;

    if ( !_lookupTableInitialized ) {
        unsigned int i;
        for ( i = 0; i != UCHAR_MAX + 1; ++i )
            _bitCountTable[i] =
                ( unsigned char )_defaultBitCount( i );

        _lookupTableInitialized = 1;
    }

    for ( ; val; val >>= CHAR_BIT )
        bCount += _bitCountTable[val & UCHAR_MAX];

    return bCount;
}

static int ( *_bitcount ) ( unsigned int ) = _defaultBitCount;

const char *strategyNames[] = { "onTheFly", "lookupTable" };

void setStrategy( enum strategy s ) {
    switch ( s ) {
    case onTheFly:
        _bitcount = _defaultBitCount;
        break;
    case lookupTable:
        _bitcount = _tableBitCount;
        break;
    case strategyCount:
        break;
    }
}

/* Just a forwarding function which will call whichever version of the
 * algorithm has been selected by the client 
 */
int bitcount( unsigned int val ) {
    return _bitcount( val );
}

#ifdef _BITCOUNT_EXE_

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/* Use the same sequence of pseudo random numbers to benmark each Hamming
 * Weight algorithm.
 */
void benchmark( int reps ) {
    clock_t start, stop;
    int i, j;
    static const int iterations = 1000000;

    for ( j = 0; j != strategyCount; ++j ) {
        setStrategy( j );

        srand( 257 );

        start = clock(  );

        for ( i = 0; i != reps * iterations; ++i )
            bitcount( rand(  ) );

        stop = clock(  );

        printf
            ( "\n\t%d psudoe-random integers using %s: %f seconds\n\n",
              reps * iterations, strategyNames[j],
              ( double )( stop - start ) / CLOCKS_PER_SEC );
    }
}

int main( void ) {
    int option;

    while ( 1 ) {
        printf( "Menu Options\n"
            "\t1.\tPrint the Hamming Weight of an Integer\n"
            "\t2.\tBenchmark Hamming Weight implementations\n"
            "\t3.\tExit ( or cntl-d )\n\n\t" );

        if ( scanf( "%d", &option ) == EOF )
            break;

        switch ( option ) {
        case 1:
            printf( "Please enter the integer: " );
            if ( scanf( "%d", &option ) != EOF )
                printf
                    ( "The Hamming Weight of %d ( 0x%X ) is %d\n\n",
                      option, option, bitcount( option ) );
            break;
        case 2:
            printf
                ( "Please select number of reps ( in millions ): " );
            if ( scanf( "%d", &option ) != EOF )
                benchmark( option );
            break;
        case 3:
            goto EXIT;
            break;
        default:
            printf( "Invalid option\n" );
        }

    }

 EXIT:
    printf( "\n" );

    return 0;
}

#endif



Few open questions:-

  1. If the number is negative then?
  2. If the number is 1024 , then the "iteratively divide by 2" method will iterate 10 times.

we can modify the algo to support the negative number as follows:-

count = 0
while n != 0
if ((n % 2) == 1 || (n % 2) == -1
    count += 1
  n /= 2  
return count

now to overcome the second problem we can write the algo like:-

int bit_count(int num)
{
    int count=0;
    while(num)
    {
        num=(num)&(num-1);
        count++;
    }
    return count;
}

for complete reference see :

http://goursaha.freeoda.com/Miscellaneous/IntegerBitCount.html




C'est l'une de ces questions où il est utile de connaître votre micro-architecture. J'ai juste chronométré deux variantes sous gcc 4.3.3 compilé avec -O3 en utilisant C ++ inline pour éliminer la surcharge des appels de fonctions, un milliard d'itérations, en gardant la somme de tous les comptes pour s'assurer que le compilateur ne supprime rien d'important, en utilisant rdtsc pour le timing cycle d'horloge précis).

inline int pop2(unsigned x, unsigned y)
{
    x = x - ((x >> 1) & 0x55555555);
    y = y - ((y >> 1) & 0x55555555);
    x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
    y = (y & 0x33333333) + ((y >> 2) & 0x33333333);
    x = (x + (x >> 4)) & 0x0F0F0F0F;
    y = (y + (y >> 4)) & 0x0F0F0F0F;
    x = x + (x >> 8);
    y = y + (y >> 8);
    x = x + (x >> 16);
    y = y + (y >> 16);
    return (x+y) & 0x000000FF;
}

Le délice de Hacker's non modifié a pris 12,2 gigacycles. Ma version parallèle (comptant deux fois plus de bits) s'exécute dans 13.0 gigacycles. Un total de 10,5 secondes s'est écoulé pour les deux sur un Core Duo 2,4 GHz. 25 gigacycles = un peu plus de 10 secondes à cette fréquence d'horloge, donc je suis confiant que mes timings sont bons.

Cela a à voir avec les chaînes de dépendance d'instruction, qui sont très mauvaises pour cet algorithme. Je pourrais presque doubler encore la vitesse en utilisant une paire de registres 64 bits. En fait, si j'étais intelligent et que j'ajoutais x + ya un peu plus tôt, je pourrais raser quelques quarts de travail. La version 64 bits avec quelques petites modifications sortirait même à peu près, mais compter à nouveau deux fois plus de bits.

Avec les registres SIMD 128 bits, un autre facteur de deux, et les jeux d'instructions SSE ont souvent aussi des raccourcis intelligents.

Il n'y a aucune raison que le code soit particulièrement transparent. L'interface est simple, l'algorithme peut être référencé en ligne dans de nombreux endroits, et peut faire l'objet de tests unitaires complets. Le programmeur qui trébuche dessus pourrait même apprendre quelque chose. Ces opérations de bits sont extrêmement naturelles au niveau de la machine.

OK, j'ai décidé de tester la version 64 bits modifiée. Pour celui-ci sizeof (unsigned long) == 8

inline int pop2(unsigned long x, unsigned long y)
{
    x = x - ((x >> 1) & 0x5555555555555555);
    y = y - ((y >> 1) & 0x5555555555555555);
    x = (x & 0x3333333333333333) + ((x >> 2) & 0x3333333333333333);
    y = (y & 0x3333333333333333) + ((y >> 2) & 0x3333333333333333);
    x = (x + (x >> 4)) & 0x0F0F0F0F0F0F0F0F;
    y = (y + (y >> 4)) & 0x0F0F0F0F0F0F0F0F;
    x = x + y; 
    x = x + (x >> 8);
    x = x + (x >> 16);
    x = x + (x >> 32); 
    return x & 0xFF;
}

Cela ressemble à peu près (je ne suis pas en train de tester soigneusement, cependant). Maintenant, les horaires sortent à 10,70 gigacycles / 14,1 gigacycles. Ce dernier numéro a totalisé 128 milliards de bits et correspond à 5,9 s écoulés sur cette machine. La version non parallèle accélère un tout petit peu car je cours en mode 64 bits et j'aime mieux les registres 64 bits que les registres 32 bits.

Voyons voir s'il y a un peu plus de pipelining OOO ici. C'était un peu plus complexe, alors j'ai testé un peu. Chaque terme seul s'élève à 64, somme totale de 256.

inline int pop4(unsigned long x, unsigned long y, 
                unsigned long u, unsigned long v)
{
  enum { m1 = 0x5555555555555555, 
         m2 = 0x3333333333333333, 
         m3 = 0x0F0F0F0F0F0F0F0F, 
         m4 = 0x000000FF000000FF };

    x = x - ((x >> 1) & m1);
    y = y - ((y >> 1) & m1);
    u = u - ((u >> 1) & m1);
    v = v - ((v >> 1) & m1);
    x = (x & m2) + ((x >> 2) & m2);
    y = (y & m2) + ((y >> 2) & m2);
    u = (u & m2) + ((u >> 2) & m2);
    v = (v & m2) + ((v >> 2) & m2);
    x = x + y; 
    u = u + v; 
    x = (x & m3) + ((x >> 4) & m3);
    u = (u & m3) + ((u >> 4) & m3);
    x = x + u; 
    x = x + (x >> 8);
    x = x + (x >> 16);
    x = x & m4; 
    x = x + (x >> 32);
    return x & 0x000001FF;
}

J'étais excité pour un moment, mais il s'avère que gcc joue des trucs en ligne avec -O3 même si je n'utilise pas le mot-clé inline dans certains tests. Quand je laisse gcc jouer des tours, un milliard d'appels à pop4 () prend 12,56 gigacycles, mais j'ai déterminé que c'était plier les arguments comme des expressions constantes. Un nombre plus réaliste semble être 19.6gc pour une autre accélération de 30%. Ma boucle de test ressemble maintenant à ceci, en s'assurant que chaque argument est suffisamment différent pour empêcher gcc de jouer des tours.

   hitime b4 = rdtsc(); 
   for (unsigned long i = 10L * 1000*1000*1000; i < 11L * 1000*1000*1000; ++i) 
      sum += pop4 (i,  i^1, ~i, i|1); 
   hitime e4 = rdtsc(); 

256 milliards de bits additionnés en 8.17s se sont écoulés. Fonctionne à 1.02s pour 32 millions de bits comme étalonné dans la recherche de table 16 bits. Impossible de comparer directement, parce que l'autre banc ne donne pas une vitesse d'horloge, mais on dirait que j'ai jeté la morve hors de l'édition de 64 Ko de table, ce qui est une utilisation tragique du cache L1 en premier lieu.

Mise à jour: décidé de faire l'évidence et de créer pop6 () en ajoutant quatre lignes supplémentaires. Entré à 22.8gc, 384 milliards de bits additionnés en 9.5s écoulés. Il y a donc 20% de plus maintenant à 800ms pour 32 milliards de bits.




Je m'ennuyais, et chronométré un milliard d'itérations de trois approches. Le compilateur est gcc -O3. CPU est tout ce qu'ils mettent dans le 1er gen MacBook Pro.

Le plus rapide est le suivant, à 3,7 secondes:

static unsigned char wordbits[65536] = { bitcounts of ints between 0 and 65535 };
static int popcount( unsigned int i )
{
    return( wordbits[i&0xFFFF] + wordbits[i>>16] );
}

La deuxième place va au même code mais recherche 4 octets au lieu de 2 demi-mots. Cela a pris environ 5,5 secondes.

La troisième place revient à l'approche «ajout latéral», qui a pris 8,6 secondes.

La quatrième place revient à __builtin_popcount () de GCC, à 11 secondes honteuses.

Le comptage d'un bit à la fois était plus lent, et je m'ennuyais d'attendre que cela se termine.

Donc, si vous vous souciez de la performance par-dessus tout, utilisez la première approche. Si cela vous intéresse, mais pas assez pour dépenser 64 Ko de RAM, utilisez la seconde approche. Sinon, utilisez l'approche un bit à la fois lisible (mais lente).

Il est difficile de penser à une situation où vous voudriez utiliser l'approche du twittling.

Edit: Résultats similaires here .




  private int get_bits_set(int v)
    {
      int c; // c accumulates the total bits set in v
        for (c = 0; v>0; c++)
        {
            v &= v - 1; // clear the least significant bit set
        }
        return c;
    }





Related