2013-06-27 12 views
8

में सेट बिट्स की संख्या को तेजी से गिनती है मुझे __m128i रजिस्टर के सेट बिट्स की संख्या गिननी चाहिए। विशेष रूप से, मुझे दो फ़ंक्शन लिखना चाहिए जो निम्न तरीकों का उपयोग करके रजिस्टर के बिट्स की संख्या को गिनने में सक्षम हैं।__m128i रजिस्टर

  1. रजिस्टर के सेट बिट्स की कुल संख्या।
  2. रजिस्टर के प्रत्येक बाइट के लिए सेट बिट्स की संख्या।

क्या आंतरिक कार्य हैं जो उपरोक्त परिचालनों को पूरी तरह से या आंशिक रूप से कर सकते हैं?

+3

हाल CPU के लिए अन्य आंतरिक कार्यों के साथ-साथ कुछ अतिरिक्त फ़ंक्शन के समान नामकरण के साथ एक 'POPCNT' (जनसंख्या गिनती) है अनुदेश; जीसीसी ने इसे ''__builtin_popcount'] (http://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html) के माध्यम से अंतर्निहित किया है। –

+2

इसके लिए http://graphics.stanford.edu/~seander/bithacks.html देखें और बहुत कुछ। –

+1

एमएस में पॉपकैंट फ़ंक्शन भी हैं ... http://stackoverflow.com/questions/11114017/whats-the-difference-between-popcnt-and-mm-popcnt-u32 देखें ... ध्यान दें कि ये आवश्यक रूप से तेज़ नहीं हैं बिथैक; और अगर सरणी में बिट्स गिनती है, तो कुछ बिथैक फ़ंक्शंस कुछ हद तक तेज हैं। –

उत्तर

21

यहां कुछ कोड हैं जिनका उपयोग मैंने पुराने प्रोजेक्ट (there is a research paper about it) में किया था। फंक्शन popcnt8 नीचे प्रत्येक बाइट में सेट बिट्स की संख्या की गणना करता है।

SSE2-केवल संस्करण (Hacker's Delight book में एल्गोरिथ्म 3 के आधार पर):

static const __m128i popcount_mask1 = _mm_set1_epi8(0x77); 
static const __m128i popcount_mask2 = _mm_set1_epi8(0x0F); 
static inline __m128i popcnt8(__m128i x) { 
    __m128i n; 
    // Count bits in each 4-bit field. 
    n = _mm_srli_epi64(x, 1); 
    n = _mm_and_si128(popcount_mask1, n); 
    x = _mm_sub_epi8(x, n); 
    n = _mm_srli_epi64(n, 1); 
    n = _mm_and_si128(popcount_mask1, n); 
    x = _mm_sub_epi8(x, n); 
    n = _mm_srli_epi64(n, 1); 
    n = _mm_and_si128(popcount_mask1, n); 
    x = _mm_sub_epi8(x, n); 
    x = _mm_add_epi8(x, _mm_srli_epi16(x, 4)); 
    x = _mm_and_si128(popcount_mask2, x); 
    return x; 
} 

SSSE3 संस्करण (Wojciech Mula के कारण):

static const __m128i popcount_mask = _mm_set1_epi8(0x0F); 
static const __m128i popcount_table = _mm_setr_epi8(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4); 
static inline __m128i popcnt8(__m128i n) { 
    const __m128i pcnt0 = _mm_shuffle_epi8(popcount_table, _mm_and_si128(n, popcount_mask)); 
    const __m128i pcnt1 = _mm_shuffle_epi8(popcount_table, _mm_and_si128(_mm_srli_epi16(n, 4), popcount_mask)); 
    return _mm_add_epi8(pcnt0, pcnt1); 
} 

XOP संस्करण (SSSE3 के बराबर है, लेकिन XOP निर्देश का उपयोग करता है जो एएमडी बुलडोजर पर तेज़ हैं)

static const __m128i popcount_mask = _mm_set1_epi8(0x0F); 
static const __m128i popcount_table = _mm_setr_epi8(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4); 
static const __m128i popcount_shift = _mm_set1_epi8(-4); 
static inline __m128i popcount8(__m128i n) { 
    const __m128i pcnt0 = _mm_perm_epi8(popcount_table, popcount_table, _mm_and_si128(n, popcount_mask)); 
    const __m128i pcnt1 = _mm_perm_epi8(popcount_table, popcount_table, _mm_shl_epi8(n, popcount_shift)); 
    return _mm_add_epi8(pcnt0, pcnt1); 
} 

फ़ंक्शन आयन popcnt64 नीचे SSE के निम्न और उच्च 64-बिट हिस्सों में बिट्स की संख्या की गणना रजिस्टर:

SSE2 संस्करण:

static inline __m128i popcnt64(__m128i n) { 
    const __m128i cnt8 = popcnt8(n); 
    return _mm_sad_epu8(cnt8, _mm_setzero_si128()); 
} 

XOP संस्करण:

static inline __m128i popcnt64(__m128i n) { 
    const __m128i cnt8 = popcnt8(n); 
    return _mm_haddq_epi8(cnt8); 
} 

अंत में, समारोह popcnt128 पूरे 128-बिट रजिस्टर में बिट्स की संख्या की गणना करें:

static inline int popcnt128(__m128i n) { 
    const __m128i cnt64 = popcnt64(n); 
    const __m128i cnt64_hi = _mm_unpackhi_epi64(cnt64, cnt64); 
    const __m128i cnt128 = _mm_add_epi32(cnt64, cnt64_hi); 
    return _mm_cvtsi128_si32(cnt128); 
} 

हालांकि, popcnt128 लागू करने के लिए एक अधिक कुशल तरीका (जो इसे समर्थन प्रोसेसर पर) हार्डवेयर POPCNT अनुदेश का उपयोग करने के लिए है:

static inline int popcnt128(__m128i n) { 
    const __m128i n_hi = _mm_unpackhi_epi64(n, n); 
    #ifdef _MSC_VER 
     return __popcnt64(_mm_cvtsi128_si64(n)) + __popcnt64(_mm_cvtsi128_si64(n_hi)); 
    #else 
     return __popcntq(_mm_cvtsi128_si64(n)) + __popcntq(_mm_cvtsi128_si64(n_hi)); 
    #endif 
} 
+2

ऐसा लगता है कि आप उल्लिखित शोध पत्र के सह-लेखकों में से एक हैं :-) कट के लिए अच्छा सारांश ' n'paste चालक दल भी। आपके समाधान अद्यतित हैं। Hakem चाल अब तक अद्यतित नहीं हैं। कुडोस, दोस्त! –

+2

ओह, बहुत बुरा। आपने एसीएम पर अपना पेपर प्रकाशित किया है, इसलिए दुर्भाग्य से मैं $ 15 का भुगतान किए बिना इसे पढ़ नहीं सकता :-( –

+1

@ निल्सपिपेनब्रिनक, पेपर स्वतंत्र रूप से सम्मेलन वेबसाइट पर उपलब्ध है: conferences.computer.org/sc/2012/papers/1000a033। पीडीएफ –

-2

संपादित करें: मुझे लगता है कि मुझे समझ में नहीं आया कि ओपी क्या ढूंढ रहा था, लेकिन अगर मैं किसी और के लिए यह उपयोगी हो रहा हूं तो मैं अपना जवाब रख रहा हूं।

सी कुछ अच्छे bitwise संचालन प्रदान करता है।

countBitsSet(int toCount) 
{ 
    int numBitsSet = 0; 
    while(toCount != 0) 
    { 
     count += toCount % 2; 
     toCount = toCount >> 1; 
    } 
    return numBitsSet; 
} 

स्पष्टीकरण::

toCount % 2 

हमारे पूर्णांक में पिछले सा रिटर्न

यहाँ एक पूर्णांक में सेट बिट्स की संख्या गिनती करने के लिए कोड है। (दो से विभाजित करके और शेष की जांच करके)। हम इसे अपनी कुल गिनती में जोड़ते हैं, और उसके बाद हमारे toCount मान के बिट्स को एक से स्थानांतरित करते हैं। इस ऑपरेशन को तब तक जारी रखा जाना चाहिए जब तक कि कोई काउंटर नहीं है (जब टोकन 0 के बराबर है)

किसी विशिष्ट बाइट में बिट्स की संख्या को गिनने के लिए, आप एक मुखौटा का उपयोग करना चाहेंगे। यहाँ एक उदाहरण है:

countBitsInByte(int toCount, int byteNumber) 
{ 
    int mask = 0x000F << byteNumber * 8 
    return countBitsSet(toCount & mask) 
} 

का कहना है कि हमारी प्रणाली में, हम बाइट 0 एक छोटे endian प्रणाली में कम से कम महत्वपूर्ण बाइट पर विचार करने देता है। हम 0 पर सेट की गई बिट्स को मास्क करके हमारे पहले गिनती बिट्ससेट फ़ंक्शन को पास करने के लिए एक नया टूकाउंट बनाना चाहते हैं। हम इसे उस स्थिति में से एक बाइट (अक्षर एफ द्वारा दर्शाए गए) को स्थानांतरित करके करते हैं (बाइटनंबर * एक बाइट में 8 बिट्स के लिए 8) और हमारे toCount चर के साथ थोड़ा सा और ऑपरेशन कर रहा है।

+3

वहां * अंतर्निहित हैं (अंतर्निहित जो सीपीयू निर्देशों जैसे कि 'पीओपीसीएनटी' के लिए मानचित्र हैं) और सवाल 128 बिट एसएसई (एक्सएमएम) रजिस्टर में सेट बिट्स की गणना करने के बारे में है, न कि 'int'। –

+0

आह, मुझे लगता है कि मैंने सवाल पूरी तरह से समझ नहीं लिया। यदि यह उचित है तो मैं अपनी प्रतिक्रिया संपादित करूँगा और अगर इसे किसी के लिए ठोकर खा रहा है तो इसे बनाए रखें। –

+0

सी "अच्छा" bitwise संचालन प्रदान नहीं करता है। आप पोर्टेबल रूप से अंकगणित सही शिफ्ट भी नहीं प्राप्त कर सकते हैं! कार्यान्वयन को 2 पूरक होने की अनुमति है लेकिन हस्ताक्षर किए गए प्रकार पर '>>' एक तार्किक बदलाव हो। लेकिन व्यावहारिक रूप से सभी कंपाइलर लोग वास्तव में उपयोग करना चाहते हैं, आपको हस्ताक्षर किए गए प्रकारों पर अंकगणित सही बदलाव दें, और इस प्रकार आपका कार्य ऋणात्मक 'toCount' के लिए एक अनंत लूप है। और '% 2' पर हस्ताक्षर किए गए' & 1' से अधिक काम लेते हैं, क्योंकि इसे नकारात्मक विषम संख्याओं के लिए '-1' बनाना होता है।लेकिन (सामान्य कंपाइलर्स पर) यदि आपका 'टूकाउंट' नकारात्मक था, तो आपका फ़ंक्शन कभी वापस नहीं लौटाता है, इसलिए समस्या छिपी हुई है ... –

0

के रूप में पहली टिप्पणी में कहा, जीसीसी 3.4+ एक (उम्मीद इष्टतम के लिए एक आसान पहुँच प्रदान करता है) में निर्मित

int __builtin_popcount (unsigned int x) /* Returns the number of 1-bits in x. */ 

के माध्यम से यहां कहा गया है: http://gcc.gnu.org/onlinedocs/gcc-3.4.3/gcc/Other-Builtins.html#Other%20Builtins

बिल्कुल 128bits के लिए इस सवाल का जवाब नहीं है, लेकिन सवाल मैं था जब मैं यहाँ उतरा के लिए एक अच्छा जवाब दे :)

1

यहाँ एक संस्करण आधार Bit Twiddling Hacks - Counting Set Bits in Parallel पर 16 32 और 64 बिट वैक्टर

#include "immintrin.h" 

/* bit masks: 0x55 = 01010101, 0x33 = 00110011, 0x0f = 00001111 */ 
static const __m128i m1 = {0x5555555555555555ULL,0x5555555555555555ULL}; 
static const __m128i m2 = {0x3333333333333333ULL,0x3333333333333333ULL}; 
static const __m128i m3 = {0x0f0f0f0f0f0f0f0fULL,0x0f0f0f0f0f0f0f0fULL}; 
static const __m128i m4 = {0x001f001f001f001fULL,0x001f001f001f001fULL}; 
static const __m128i m5 = {0x0000003f0000003fULL,0x0000003f0000003fULL}; 

__m128i _mm_popcnt_epi8(__m128i x) { 
    /* Note: if we returned x here it would be like _mm_popcnt_epi1(x) */ 
    __m128i y; 
    /* add even and odd bits*/ 
    y = _mm_srli_epi64(x,1); //put even bits in odd place 
    y = _mm_and_si128(y,m1); //mask out the even bits (0x55) 
    x = _mm_subs_epu8(x,y); //shortcut to mask even bits and add 
    /* if we just returned x here it would be like _mm_popcnt_epi2(x) */ 
    /* now add the half nibbles */ 
    y = _mm_srli_epi64 (x,2); //move half nibbles in place to add 
    y = _mm_and_si128(y,m2); //mask off the extra half nibbles (0x0f) 
    x = _mm_and_si128(x,m2); //ditto 
    x = _mm_adds_epu8(x,y); //totals are a maximum of 5 bits (0x1f) 
    /* if we just returned x here it would be like _mm_popcnt_epi4(x) */ 
    /* now add the nibbles */ 
    y = _mm_srli_epi64(x,4); //move nibbles in place to add 
    x = _mm_adds_epu8(x,y); //totals are a maximum of 6 bits (0x3f) 
    x = _mm_and_si128(x,m3); //mask off the extra bits 
    return x; 
} 

__m128i _mm_popcnt_epi16(__m128i x) { 
    __m128i y; 
    x = _mm_popcnt_epi8(x); //get byte popcount 
    y = _mm_srli_si128(x,1); //copy even bytes for adding 
    x = _mm_add_epi16(x,y); //add even bytes into the odd bytes 
    return _mm_and_si128(x,m4);//mask off the even byte and return 
} 

__m128i _mm_popcnt_epi32(__m128i x) { 
    __m128i y; 
    x = _mm_popcnt_epi16(x); //get word popcount 
    y = _mm_srli_si128(x,2); //copy even words for adding 
    x = _mm_add_epi32(x,y); //add even words into odd words 
    return _mm_and_si128(x,m5);//mask off the even words and return 
} 

__m128i _mm_popcnt_epi64(__m128i x){ 
    /* _mm_sad_epu8() is weird 
     It takes the absolute difference of bytes between 2 __m128i 
     then horizontal adds the lower and upper 8 differences 
     and stores the sums in the lower and upper 64 bits 
    */ 
    return _mm_sad_epu8(_mm_popcnt_epi8(x),(__m128i){0}); 
} 

int _mm_popcnt_si128(__m128i x){ 
    x = _mm_popcnt_epi64(x); 
    __m128i y = _mm_srli_si128(x,8); 
    return _mm_add_epi64(x,y)[0]; 
    //alternative: __builtin_popcntll(x[0])+__builtin_popcntll(x[1]); 
} 
+0

आपको पहले के बाद के चरणों के लिए नियमित 'एड' के बजाय 'जोड़' को संतृप्त करने की आवश्यकता क्यों है? (हालांकि एग्नेर कोहरे की निर्देश तालिकाओं के अनुसार, 'पैडसब 'सबकुछ पर 'पैडब' के समान प्रदर्शन है, इसलिए संतृप्त जोड़ने से बचने के लिए कोई परफ कारण नहीं है। यह आश्चर्यजनक है।) –

संबंधित मुद्दे