पैक्ड 8-बिट पूर्णांक को 64-बिट पूर्णांक में 1, समानांतर में, SWAR बिना हार्डवेयर SIMD के


77

अगर मेरे पास 64-बिट पूर्णांक है जो मैं 8 तत्वों के साथ पैक किए गए 8-बिट पूर्णांक की एक सरणी के रूप में व्याख्या कर रहा हूं। 1एक तत्व के दूसरे तत्व के परिणाम को प्रभावित किए बिना अतिप्रवाह से निपटने के दौरान मुझे प्रत्येक पैक किए गए पूर्णांक से निरंतर को घटाना होगा ।

मेरे पास इस समय यह कोड है और यह काम करता है लेकिन मुझे एक समाधान की आवश्यकता है जो समानांतर में प्रत्येक पैक किए गए 8-बिट पूर्णांक को घटाता है और मेमोरी एक्सेस नहीं करता है। X86 पर मैं SIMD निर्देशों का उपयोग कर सकता हूं, जैसे psubbकि 8-बिट पूर्णांकों को पैरलल में घटाता है, लेकिन जिस प्लेटफॉर्म के लिए मैं कोडिंग कर रहा हूं वह SIMD निर्देशों का समर्थन नहीं करता है। (इस मामले में आरआईएससी-वी)।

तो मैं SWAR (रजिस्टर के भीतर SIMD) करने की कोशिश कर रहा हूँ, मैन्युअल रूप से रद्द करने के लिए बाइट्स के बीच प्रसार को रद्द करना uint64_t, इसके बराबर कुछ करना:

uint64_t sub(uint64_t arg) {
    uint8_t* packed = (uint8_t*) &arg;

    for (size_t i = 0; i < sizeof(uint64_t); ++i) {
        packed[i] -= 1;
    }

    return arg;
}

मुझे लगता है कि आप बिटकॉइन ऑपरेटरों के साथ ऐसा कर सकते हैं लेकिन मुझे यकीन नहीं है। मैं एक ऐसे समाधान की तलाश में हूं जो SIMD निर्देशों का उपयोग न करे। मैं सी या सी ++ में एक समाधान की तलाश कर रहा हूं जो काफी पोर्टेबल है या इसके पीछे सिर्फ सिद्धांत है ताकि मैं अपने समाधान को लागू कर सकूं।


5
क्या उन्हें 8-बिट होने की आवश्यकता है या क्या वे इसके बजाय 7-बिट हो सकते हैं?
तदमन

उन्हें 8-बिट सॉरी मिला :(
कैम-व्हाइट

12
इस तरह की चीजों के लिए तकनीकों को स्वार
हेरोल्ड


1
क्या आपको उम्मीद है कि 0xff पर रैप करने के लिए बाइट में शून्य होता है?
अलनीतक

जवाबों:


75

यदि आपके पास कुशल SIMD निर्देशों के साथ CPU है, तो SSE / MMX paddb( _mm_add_epi8) भी व्यवहार्य है। पीटर कॉर्ड्स का उत्तर GNU C (gcc / clang) वेक्टर सिंटैक्स और सख्त-अलियासिंग UB के लिए सुरक्षा का भी वर्णन करता है। मैं दृढ़तापूर्वक उस उत्तर की समीक्षा करने के लिए प्रोत्साहित करता हूं।

अपने आप से ऐसा करना uint64_tपूरी तरह से पोर्टेबल है, लेकिन फिर भी ए को एलाइन करते समय एलाइनमेंट की समस्या और सख्त-अलियासिंग यूबी से बचने के लिए देखभाल की आवश्यकता होती uint8_tहै uint64_t*। आपने uint64_tपहले ही अपने डेटा के साथ शुरुआत करके इस प्रश्न को छोड़ दिया था , लेकिन GNU C के लिए एक may_aliasसमस्या हल हो गई है (उस या इसके लिए पीटर का उत्तर देखें memcpy)।

अन्यथा आप अपने डेटा को आवंटित या घोषित कर सकते हैं uint64_tऔर uint8_t*जब आप अलग-अलग बाइट्स चाहते हैं, तब तक इसे एक्सेस कर सकते हैं। unsigned char*कुछ भी उर्फ ​​करने की अनुमति दी जाती है ताकि 8-बिट तत्वों के विशिष्ट मामले के लिए समस्या को दूर किया जाए। (यदि uint8_tयह सब मौजूद है, तो यह मान लेना सुरक्षित है कि यह एक है unsigned char।)


ध्यान दें कि यह एक पूर्व गलत एल्गोरिथ्म से परिवर्तन है (इतिहास संशोधन देखें)।

यह मनमाना घटाव के लिए लूपिंग के बिना संभव है, और 1प्रत्येक बाइट की तरह ज्ञात स्थिर के लिए अधिक कुशल हो जाता है । मुख्य चाल उच्च बिट सेट करके प्रत्येक बाइट से कैरी-आउट को रोकने के लिए है, फिर घटाव परिणाम को सही करें।

हम यहां दी गई घटाव तकनीक को थोड़ा अनुकूलित करने जा रहे हैं । वे परिभाषित करते हैं:

SWAR sub z = x - y
    z = ((x | H) - (y &~H)) ^ ((x ^~y) & H)

के Hरूप में परिभाषित किया गया है 0x8080808080808080U(यानी प्रत्येक पैक पूर्णांक के MSBs)। एक गिरावट के लिए, yहै 0x0101010101010101U

हम जानते हैं कि yइसके सभी MSB स्पष्ट हैं, इसलिए हम मास्क चरणों में से एक को छोड़ सकते हैं (यानी हमारे मामले में y & ~Hजैसा yहै)। गणना इस प्रकार है:

  1. हम प्रत्येक घटक के xMSB को 1 पर सेट करते हैं , ताकि एक उधार MSB को अगले घटक पर न फैला सके। इसे समायोजित इनपुट कहें।
  2. हम प्रत्येक घटक से 1 घटाते 0x01010101010101हैं, सही इनपुट से घटाकर । यह चरण 1 के लिए अंतर-घटक उधार का कारण नहीं बनता है। इसे समायोजित आउटपुट कहें।
  3. हमें अब परिणाम के MSB को सही करने की आवश्यकता है। हम परिणाम को ठीक करने के लिए मूल इनपुट के उल्टे MSBs के साथ समायोजित आउटपुट को प्राप्त करते हैं।

ऑपरेशन के रूप में लिखा जा सकता है:

#define U64MASK 0x0101010101010101U
#define MSBON 0x8080808080808080U
uint64_t decEach(uint64_t i){
      return ((i | MSBON) - U64MASK) ^ ((i ^ MSBON) & MSBON);
}

अधिमानतः, यह संकलक द्वारा उपयोग किया जाता है ( इसे लागू करने के लिए संकलक निर्देशों का उपयोग करें), या अभिव्यक्ति को किसी अन्य फ़ंक्शन के भाग के रूप में इनलाइन लिखा जाता है।

परीक्षण के मामलों:

in:  0000000000000000
out: ffffffffffffffff

in:  f200000015000013
out: f1ffffff14ffff12

in:  0000000000000100
out: ffffffffffff00ff

in:  808080807f7f7f7f
out: 7f7f7f7f7e7e7e7e

in:  0101010101010101
out: 0000000000000000

प्रदर्शन विवरण

यहाँ फ़ंक्शन के एकल मंगलाचरण के लिए x86_64 असेंबली है। बेहतर प्रदर्शन के लिए इसे इस उम्मीद के साथ इनलाइन किया जाना चाहिए कि कॉन्स्टेंट यथासंभव लंबे समय तक रजिस्टर में रह सकते हैं। एक तंग लूप में जहां स्थिरांक एक रजिस्टर में रहते हैं, वास्तविक वृद्धि पांच निर्देश लेती है: या + और + और + जोड़ + अनुकूलन के बाद। मुझे ऐसे विकल्प नहीं दिखते जो कंपाइलर के अनुकूलन को हरा दे।

uint64t[rax] decEach(rcx):
    movabs  rcx, -9187201950435737472
    mov     rdx, rdi
    or      rdx, rcx
    movabs  rax, -72340172838076673
    add     rax, rdx
    and     rdi, rcx
    xor     rdi, rcx
    xor     rax, rdi
    ret

निम्नलिखित स्निपेट के कुछ IACA परीक्षण के साथ:

// Repeat the SWAR dec in a loop as a microbenchmark
uint64_t perftest(uint64_t dummyArg){
    uint64_t dummyCounter = 0;
    uint64_t i = 0x74656a6d27080100U; // another dummy value.
    while(i ^ dummyArg) {
        IACA_START
        uint64_t naive = i - U64MASK;
        i = naive + ((i ^ naive ^ U64MASK) & U64MASK);
        dummyCounter++;
    }
    IACA_END
    return dummyCounter;
}

हम दिखा सकते हैं कि एक स्काइलेक मशीन पर, घटाव, xor, और तुलना + कूद का प्रदर्शन केवल प्रति चक्र 5 चक्र के तहत किया जा सकता है:

Throughput Analysis Report
--------------------------
Block Throughput: 4.96 Cycles       Throughput Bottleneck: Backend
Loop Count:  26
Port Binding In Cycles Per Iteration:
--------------------------------------------------------------------------------------------------
|  Port  |   0   -  DV   |   1   |   2   -  D    |   3   -  D    |   4   |   5   |   6   |   7   |
--------------------------------------------------------------------------------------------------
| Cycles |  1.5     0.0  |  1.5  |  0.0     0.0  |  0.0     0.0  |  0.0  |  1.5  |  1.5  |  0.0  |
--------------------------------------------------------------------------------------------------

(बेशक, x86-64 पर आप सिर्फ लोड करेंगे या movqएक एक्सएमएम रेज के लिए paddb, इसलिए यह देखना दिलचस्प हो सकता है कि यह आईएसआईएस जैसे आरआईएससी-वी के लिए कैसे संकलित है।)


4
मुझे RISC-V मशीनों पर चलने के लिए मेरे कोड की आवश्यकता है, जिसमें SIMD के निर्देश नहीं हैं (अभी तक) MMX के लिए अकेले समर्थन करते हैं
कैम-व्हाईट

2
@ कैम-व्हाईट गॉट इट - यह शायद सबसे अच्छा है जो आप तब कर सकते हैं। मुझे आशा है कि RISC के लिए असेंबली की जाँच करने के लिए मैं गॉडबॉल्ट से पवित्रता की उम्मीद करूँगा। संपादित करें: Godbolt पर कोई RISC-V समर्थन नहीं :(
nanofarad

7
वास्तव में गॉडबोल्ट पर आरआईएससी-वी का समर्थन है, उदाहरण के लिए इस तरह (ई: ऐसा लगता है कि कंपाइलर मुखौटा बनाने में अत्यधिक रचनात्मक हो जाता है ..)
हेरोल्ड

4
आगे पढ़ने पर कि कैसे समता (जिसे "कैरी-आउट वेक्टर" भी कहा जाता है) ट्रिक का उपयोग विभिन्न स्थितियों में किया जा सकता है: emulators.com/docs/LazyOverflowDetect_Final.pdf
jpa

4
मैंने एक और संपादन किया; GNU C देशी वैक्टर वास्तव में सख्त-अलियासिंग समस्याओं से बचते हैं ; डेटा uint8_tको अलियास करने के लिए एक वेक्टर-इन- की अनुमति है uint8_t। आपके फ़ंक्शन के कॉलर (जिसमें uint8_tडेटा प्राप्त करने की आवश्यकता है uint64_t) वे हैं जिन्हें सख्त-अलियासिंग के बारे में चिंता करनी है! इसलिए शायद ओपी को केवल सारणी घोषित करना / आवंटित करना चाहिए uint64_tक्योंकि char*आईएसओ सी ++ में किसी भी चीज को अन्य के लिए अनुमति दी जाती है, लेकिन इसके विपरीत नहीं।
पीटर कॉर्ड्स

16

आरआईएससी-वी के लिए आप शायद जीसीसी / क्लैंग का उपयोग कर रहे हैं।

मजेदार तथ्य: GCC को इन SWAR बिथक ट्रिकों में से कुछ को पता है (अन्य उत्तरों में दिखाया गया है) और GNU C देशी वैक्टर के साथ कोड संकलित करते समय उनका उपयोग आपके लिए कर सकते हैं हार्डवेयर सिमड निर्देशों के बिना लक्ष्य के लिए । (लेकिन आरआईएससी-वी के लिए क्लैंग सिर्फ स्केलर ऑपरेशन के लिए इसे अनियंत्रित रूप से अनियंत्रित करेगा, इसलिए आपको इसे स्वयं करना होगा यदि आप संकलक में अच्छा प्रदर्शन चाहते हैं)।

देशी वेक्टर सिंटैक्स का एक फायदा यह है कि हार्डवेयर SIMD के साथ मशीन को लक्षित करते समय , यह ऑटो-वेक्टर के बजाय आपकी बिटकॉइन या उस जैसी भयानक चीज़ का उपयोग करेगा।

इससे vector -= scalarऑपरेशन लिखना आसान हो जाता है ; सिंटैक्स जस्ट वर्क्स, आपके लिए स्केलर को स्पष्ट रूप से प्रसारित करने वाला उर्फ ​​प्रसारण।


यह भी ध्यान दें कि एक uint64_t*लोड से uint8_t array[]सख्त-उर्फिंग यूबी है, इसलिए उससे सावधान रहें। (यह भी देखें कि ग्लिबक की स्ट्रैलेन को जल्दी से चलाने के लिए इतना जटिल होने की आवश्यकता क्यों है? पुन:: स्वार बीथक्स को सख्त-अलियासिंग शुद्ध सी में सुरक्षित करना)। आप कुछ इस तरह की घोषणा uint64_tकर सकते हैं कि आप किसी अन्य ऑब्जेक्ट तक पहुंचने के लिए पॉइंटर-कास्ट कर सकते हैं, जैसे कि char*आईएसओ सी / सी ++ में कैसे काम करता है।

अन्य जवाबों के साथ उपयोग के लिए uint8_t डेटा प्राप्त करने के लिए इनका उपयोग करें:

// GNU C: gcc/clang/ICC but not MSVC
typedef uint64_t  aliasing_u64 __attribute__((may_alias));  // still requires alignment
typedef uint64_t  aliasing_unaligned_u64 __attribute__((may_alias, aligned(1)));

अलियासिंग-सुरक्षित लोड करने का दूसरा तरीका memcpyएक में है uint64_t, जो alignof(uint64_tसंरेखण आवश्यकता को भी हटा देता है । लेकिन कुशल बिना भार वाले आईएसएएस पर, जीसीसी / क्लैंग इनलाइन नहीं करते हैं और दूर memcpyकरते हैं, जब वे साबित नहीं कर सकते हैं कि सूचक संरेखित है, जो प्रदर्शन के लिए विनाशकारी होगा।

TL: DR: आपका सबसे अच्छा शर्त यह है कि आप डेटा को घोषित करेंuint64_t array[...] या इसे गतिशील रूप से आवंटित करें uint64_t, या अधिमानतःalignas(16) uint64_t array[]; यदि आप निर्दिष्ट करते हैं तो कम से कम 8 बाइट्स या 16 के लिए संरेखण सुनिश्चित करता है alignas

चूंकि uint8_tयह लगभग निश्चित रूप से है unsigned char*, इसलिए इसके uint64_tमाध्यम से बाइट्स का उपयोग करना सुरक्षित है uint8_t*(लेकिन uint8_t सरणी के लिए इसके विपरीत नहीं)। तो इस विशेष मामले के लिए जहां संकीर्ण तत्व प्रकार है unsigned char, आप सख्त-अलियासिंग समस्या को दूर कर सकते हैं क्योंकि charविशेष है।


GNU C देशी वेक्टर सिंटैक्स उदाहरण:

GNU सी देशी वैक्टर हमेशा अपने अंतर्निहित प्रकार के साथ उर्फ करने की अनुमति है (उदाहरण के लिए int __attribute__((vector_size(16)))सुरक्षित रूप से अन्य नाम कर सकते हैं intलेकिन नहीं floatया uint8_tया कुछ और।

#include <stdint.h>
#include <stddef.h>

// assumes array is 16-byte aligned
void dec_mem_gnu(uint8_t *array) {
    typedef uint8_t v16u8 __attribute__ ((vector_size (16), may_alias));
    v16u8 *vecs = (v16u8*) array;
    vecs[0] -= 1;
    vecs[1] -= 1;   // can be done in a loop.
}

आरआईएससी-वी के लिए बिना किसी एचडब्ल्यू सिमडी के, आप vector_size(8)केवल दानेदारता को व्यक्त करने के लिए उपयोग कर सकते हैं जिसे आप कुशलतापूर्वक उपयोग कर सकते हैं, और दो बार कई छोटे वैक्टर भी कर सकते हैं।

लेकिन vector_size(8)जीसीसी और क्लैंग दोनों के साथ x86 के लिए बहुत ही मूर्खतापूर्ण रूप से संकलित किया गया: जीसीसी जीपी-पूर्णांक रजिस्टरों में SWAR बिटहॉक का उपयोग करता है, 16-बाइट एक्सएमएम रजिस्टर को फिर से भरने के लिए 2-बाइट तत्वों के लिए अनपैक को क्लैप करता है। (एमएमएक्स इतना अप्रचलित है कि जीसीसी / क्लैंग भी इसका उपयोग करने से परेशान नहीं है, कम से कम x86-64 के लिए नहीं)।

लेकिन vector_size (16)( गॉडबोल्ट ) के साथ हमें उम्मीद है movdqa/ paddb। (सभी लोगों द्वारा उत्पन्न वेक्टर के साथ pcmpeqd same,same)। साथ -march=skylakeहम अभी भी एक YMM के बजाय दो अलग-अलग XMM ऑप्स मिलता है, तो दुर्भाग्य से वर्तमान compilers भी व्यापक वैक्टर में "स्वत: vectorize" वेक्टर ऑप्स कार्य करें: /

AArch64 के लिए, यह vector_size(8)( Godbolt ) उपयोग करने के लिए इतना बुरा नहीं है ; ARM / AAr6464 मूल रूप से 8 या 16-बाइट विखंडू में dया qरजिस्टरों के साथ काम कर सकते हैं ।

तो आप शायद vector_size(16)x86, RISC-V, ARM / AArch64 और POWER भर में पोर्टेबल प्रदर्शन चाहते हैं, तो आप वास्तव में इसका संकलन करना चाहते हैं । हालाँकि, कुछ अन्य ISAs 64-बिट पूर्णांक रजिस्टरों के भीतर SIMD करते हैं, जैसे MIPS MSA मुझे लगता है।

vector_size(8)यह asm को देखने के लिए आसान बनाता है (केवल एक रजिस्टर डेटा के लायक): Godbolt संकलक एक्सप्लोरर

# GCC8.2 -O3 for RISC-V for vector_size(8) and only one vector

dec_mem_gnu(unsigned char*):
        lui     a4,%hi(.LC1)           # generate address for static constants.
        ld      a5,0(a0)                 # a5 = load from function arg
        ld      a3,%lo(.LC1)(a4)       # a3 = 0x7F7F7F7F7F7F7F7F
        lui     a2,%hi(.LC0)
        ld      a2,%lo(.LC0)(a2)       # a2 = 0x8080808080808080
                             # above here can be hoisted out of loops
        not     a4,a5                  # nx = ~x
        and     a5,a5,a3               # x &= 0x7f... clear high bit
        and     a4,a4,a2               # nx = (~x) & 0x80... inverse high bit isolated
        add     a5,a5,a3               # x += 0x7f...   (128-1)
        xor     a5,a4,a5               # x ^= nx  restore high bit or something.

        sd      a5,0(a0)               # store the result
        ret

मुझे लगता है कि यह अन्य गैर-लूपिंग उत्तरों के समान मूल विचार है; फिर कैरी को रोकना परिणाम को ठीक करता है।

यह 5 एएलयू निर्देश है, जो मुझे लगता है कि शीर्ष उत्तर से भी बदतर है। लेकिन ऐसा लगता है कि महत्वपूर्ण पथ विलंबता केवल 3 चक्र है, जिसमें 2 निर्देश की दो श्रृंखलाएं हैं जो प्रत्येक XOR की ओर जाती हैं। @Reinstate Monica - '- का उत्तर एक 4-चक्र dep श्रृंखला (x86 के लिए) के लिए संकलित करता है। 5-चक्र लूप थ्रूपुट भी एक subमहत्वपूर्ण मार्ग पर एक भोले सहित अड़चन है , और लूप विलंबता पर अड़चन करता है।

हालांकि, यह क्लैंग के साथ बेकार है। यह उसी क्रम में इसे जोड़ता और संग्रहीत नहीं करता है जो इसे लोड करता है इसलिए यह अच्छा सॉफ्टवेयर पाइपलाइनिंग भी नहीं कर रहा है!

# RISC-V clang (trunk) -O3
dec_mem_gnu(unsigned char*):
        lb      a6, 7(a0)
        lb      a7, 6(a0)
        lb      t0, 5(a0)
...
        addi    t1, a5, -1
        addi    t2, a1, -1
        addi    t3, a2, -1
...
        sb      a2, 7(a0)
        sb      a1, 6(a0)
        sb      a5, 5(a0)
...
        ret

13

मैं बताता हूं कि आपके द्वारा लिखा गया कोड एक बार uint64_t से अधिक के साथ काम शुरू करने के बाद वास्तव में वेक्टर हो जाता है।

https://godbolt.org/z/J9DRzd


1
क्या आप समझा सकते हैं या एक संदर्भ दे सकते हैं कि वहां क्या हो रहा है? यह काफी दिलचस्प लगता है।
n314159

2
मैं SIMD के निर्देशों के बिना ऐसा करने की कोशिश कर रहा था, लेकिन मुझे यह दिलचस्प कुछ भी कम नहीं मिला :)
कैम-व्हाइट

8
दूसरी ओर, वह SIMD कोड भयानक है। कंपाइलर ने पूरी तरह से गलत समझा कि यहां क्या हो रहा है। E: इसका एक उदाहरण है "यह स्पष्ट रूप से एक संकलक द्वारा किया गया था, क्योंकि कोई भी मानव इस मूर्ख नहीं होगा"
हेरोल्ड

1
@PeterCordes: मैं एक __vector_loop(index, start, past, pad)निर्माण की पंक्तियों के साथ अधिक सोच रहा था जो एक कार्यान्वयन के रूप में व्यवहार कर सकता है for(index=start; index<past; index++)[जिसका अर्थ है कि कोई भी कार्यान्वयन किसी मैक्रो को परिभाषित करके कोड का उपयोग कर सकता है], लेकिन जिसमें चीजों को संसाधित करने के लिए संकलक को आमंत्रित करने के लिए शिथिल शब्दार्थ होगा। padअगर वे पहले से ही चंक आकार के गुणक नहीं हैं, तो नीचे की ओर और अंत में ऊपर की ओर विस्तार करते हुए, किसी भी पावर ऑफ़ टू चंक का आकार। प्रत्येक चंक के भीतर साइड-इफ़ेक्ट न के बराबर होगा, और यदि breakलूप के भीतर होता है, तो अन्य रेप्स ...
सुपरकैट :58

1
@PeterCordes: restrictसहायक है (और अधिक उपयोगी होगा यदि मानक "कम से कम संभावित रूप से" पर आधारित अवधारणा को मान्यता देता है, और फिर "आधारित है" और "कम से कम संभावित रूप से नासमझ और नासमझ कोने के मामलों के बिना" पर आधारित है) मेरा प्रस्ताव भी एक संकलक को अनुरोध के मुकाबले लूप के अधिक निष्पादन करने की अनुमति देगा - ऐसा कुछ जो वैश्वीकरण को बहुत सरल करेगा, लेकिन जिसके लिए मानक कोई प्रावधान नहीं करता है।
सुपरकैट

11

आप सुनिश्चित कर सकते हैं कि घटाव अतिप्रवाह नहीं है और फिर उच्च बिट को ठीक करें:

uint64_t sub(uint64_t arg) {
    uint64_t x1 = arg | 0x80808080808080;
    uint64_t x2 = ~arg & 0x80808080808080;
    // or uint64_t x2 = arg ^ x1; to save one instruction if you don't have an andnot instruction
    return (x1 - 0x101010101010101) ^ x2;
}

मुझे लगता है कि यह बाइट के सभी 256 संभावित मूल्यों के लिए काम करता है; मैंने इसे गॉडबोल्ट (RISC-V क्लैंग के साथ) Godbolt.org/z/DGL9aq पर 0x0, 0x7f, 0x80 और 0xff (नंबर के मध्य में स्थानांतरित) जैसे विभिन्न इनपुटों के लिए निरंतर-प्रसार परिणामों को देखने के लिए रखा। अछा लगता है। मुझे लगता है कि शीर्ष उत्तर एक ही चीज़ को उबालता है, लेकिन यह इसे और अधिक जटिल तरीके से समझाता है।
पीटर कॉर्डेस

कंपाइलर यहां रजिस्टरों में एक बेहतर निर्माण कार्य कर सकते हैं। क्लैंग बहुत सारे निर्देशों का निर्माण करता है splat(0x01)और splat(0x80), एक शिफ्ट के साथ दूसरे से प्राप्त करने के बजाय। यहां तक ​​कि इसे इस तरह से लिखने के लिए स्रोत godbolt.org/z/6y9v-u संकलक को बेहतर कोड बनाने में हाथ नहीं रखता है; यह सिर्फ निरंतर प्रचार करता है।
पीटर कॉर्डेस

मुझे आश्चर्य है कि यह सिर्फ मेमोरी से स्थिरांक को लोड क्यों नहीं करता है; यही अल्फा (एक समान वास्तुकला) के लिए संकलक करते हैं।
फॉक हफनर

RISC-V के लिए GCC मेमोरी से स्थिरांक लोड करता है । ऐसा लगता है कि क्लैंग को कुछ ट्यूनिंग की ज़रूरत है, जब तक कि डेटा-कैश मिस की उम्मीद नहीं की जाती है और निर्देश थ्रूपुट की तुलना में महंगे हैं। (वह संतुलन निश्चित रूप से अल्फा के बाद से बदल सकता है, और संभवतः RISC-V के अलग-अलग कार्यान्वयन अलग-अलग हैं। कंपाइलर भी बेहतर कर सकते हैं यदि उन्हें एहसास हुआ कि यह एक दोहराव वाला पैटर्न था जिसे वे एक LUI के साथ शुरू करने के बाद शिफ्ट कर सकते थे / या चौड़ा कर सकते थे। तत्काल डेटा के 20 + 12 = 32 बिट्स के लिए। AArch64 के बिट-पैटर्न भी इन्हें AND / OR / XOR, स्मार्ट डिकोड बनाम घनत्व पसंद के रूप में तुरंत उपयोग कर सकते हैं
पीटर कॉर्डेस

आरआईएससी-वी के लिए जीसीसी के देशी-सदिश स्वार को दिखाने वाला उत्तर जोड़ा गया
पीटर कॉर्डेस

7

सुनिश्चित नहीं हैं कि यह वही है जो आप चाहते हैं लेकिन यह एक दूसरे के समानांतर 8 घटाव करता है:

#include <cstdint>

constexpr uint64_t mask = 0x0101010101010101;

uint64_t sub(uint64_t arg) {
    uint64_t mask_cp = mask;
    for(auto i = 0; i < 8 && mask_cp; ++i) {
        uint64_t new_mask = (arg & mask_cp) ^ mask_cp;
        arg = arg ^ mask_cp;
        mask_cp = new_mask << 1;
    }
    return arg;
}

व्याख्या: बिटमैक्स प्रत्येक 8-बिट संख्या में 1 से शुरू होता है। हम इसे अपने तर्क के साथ हासिल करते हैं। यदि हमारे पास इस स्थान पर 1 था, तो हमने 1 घटाया और रोकना पड़ा। यह new_mask में संबंधित बिट को 0 पर सेट करके किया जाता है। यदि हमारे पास 0 था, तो हम इसे 1 पर सेट करते हैं और कैरी करना होता है, इसलिए बिट 1 रहता है और हम मास्क को बाईं ओर शिफ्ट करते हैं। आप बेहतर तरीके से अपने लिए जांचते हैं कि क्या नया मास्क की पीढ़ी उद्देश्य के अनुसार काम करती है, मुझे ऐसा लगता है, लेकिन एक दूसरी राय खराब नहीं होगी।

पुनश्च: मैं वास्तव में अनिश्चित हूं यदि चेक mask_cpलूप में शून्य नहीं है, तो कार्यक्रम धीमा हो सकता है। इसके बिना, कोड अभी भी सही होगा (चूंकि 0 मुखौटा सिर्फ कुछ नहीं करता है) और कंपाइलर के लिए लूप को अनियंत्रित करना बहुत आसान होगा।


forसमानांतर में नहीं चलेगा, क्या आप भ्रमित हैं for_each?
LTPCGO

3
@LTPCGO नहीं, लूप के लिए इसे समानांतर बनाना मेरा उद्देश्य नहीं है, यह वास्तव में एल्गोरिथ्म को तोड़ देगा। लेकिन यह कोड समानांतर में 64 बिट पूर्णांक में अलग-अलग 8 बिट पूर्णांक पर काम करता है, अर्थात सभी 8 घटाव एक साथ किए जाते हैं, लेकिन उन्हें 8 चरणों तक की आवश्यकता होती है।
n314159

मुझे पता है कि मैं जो पूछ रहा था वह थोड़ा अनुचित हो सकता है लेकिन यह मुझे धन्यवाद की जरूरत के करीब था :)
कैम-व्हाइट

4
int subtractone(int x) 
{
    int f = 1; 

    // Flip all the set bits until we find a 1 at position y
    while (!(x & f)) { 
        x = x^f; 
        f <<= 1; 
    } 

    return x^f; // return answer but remember to flip the 1 at y
} 

आप इसे ऊपर का उपयोग करके बिटवाइज़ ऑपरेशन के साथ कर सकते हैं, और आपको इस फ़ंक्शन में 8 बार भेजने के लिए अपने पूर्णांक को 8 बिट टुकड़ों में विभाजित करना होगा। निम्न भाग से लिया गया था कि 64-बिट संख्या को आठ 8-बिट मानों में कैसे विभाजित किया जाए? मेरे साथ उपरोक्त फ़ंक्शन में जोड़ने के लिए

uint64_t v= _64bitVariable;
uint8_t i=0,parts[8]={0};
do parts[i++] = subtractone(v&0xFF); while (v>>=8);

यह वैध है C या C ++ की परवाह किए बिना कि कोई कैसे इस पार आता है


5
यह हालांकि, जो ओपी का सवाल है, काम को समानांतर नहीं करता है।
निकेलप्रो

हाँ @nickelpro सही है, यह प्रत्येक घटाव को एक के बाद एक करेगा, मैं एक ही समय में सभी 8-बिट पूर्णांक घटाना चाहूंगा। मैं उत्तर की सराहना करता हूं कि धन्यवाद भाई
कैम-व्हाइट

2
@nickelpro जब मैंने उत्तर शुरू किया तो संपादन नहीं किया गया था जिसे प्रश्न का समानांतर भाग कहा गया है और इसलिए मैंने इसे प्रस्तुत करने तक ध्यान नहीं दिया, यदि यह कम से कम उत्तर में दूसरों के लिए उपयोगी है तो इसे छोड़ देंगे। बिटवाइज़ ऑपरेशंस करने का हिस्सा और इसे for_each(std::execution::par_unseq,...
व्हिल्स के

2
यह मेरा बुरा है, मैंने सवाल प्रस्तुत किया तब मुझे एहसास हुआ कि मैंने यह नहीं कहा है कि इसे समानांतर रूप से संपादित करने की आवश्यकता है
कैम-व्हाइट

2

कोड के साथ आने की कोशिश करने के लिए नहीं जा रहा है, लेकिन 1 से एक वेतन वृद्धि के लिए आप 8 1s के समूह द्वारा घटा सकते हैं और फिर यह सुनिश्चित कर सकते हैं कि परिणामों के LSBs "फ़्लिप" हो गए। कोई भी LSB, जिसे भीख नहीं दी जाती है, यह दर्शाता है कि निकटवर्ती 8 बिट्स से एक कैरी हुई है। किसी भी शाखाओं के बिना इसे संभालने के लिए ANDs / ORs / XORs के अनुक्रम को काम करना संभव होना चाहिए।


यह काम कर सकता है, लेकिन उस मामले पर विचार करें जहां एक कैरी 8 बिट्स के एक समूह के माध्यम से और दूसरे में सभी तरह से प्रचार करता है। यह सुनिश्चित करने के लिए अच्छे उत्तरों में कि एमएसबी (या पहले कुछ निर्धारित करना) की रणनीति प्रचारित नहीं करती है, शायद कम से कम उतना ही कुशल हो जितना कि हो सकता है। हरा करने का मौजूदा लक्ष्य (यानी अच्छा नॉन-लूपिंग ब्रांचलेस उत्तर) 5 RISC-V asm ALU निर्देश है जिसमें इंस्ट्रक्शन-लेवल समांतरवाद महत्वपूर्ण पथ को केवल 3 चक्र बनाता है, और दो 64-बिट स्थिरांक का उपयोग करता है।
पीटर कॉर्डेस

0

प्रत्येक बाइट पर पूरी तरह से अकेले काम फोकस करें, फिर उसे वापस वहीं रखें जहां वह था।

uint64_t sub(uint64_t arg) {
   uint64_t res = 0;

   for (int i = 0; i < 64; i+=8) 
     res += ((arg >> i) - 1 & 0xFFU) << i;

    return res;
   }
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.