आरआईएससी-वी के लिए आप शायद जीसीसी / क्लैंग का उपयोग कर रहे हैं।
मजेदार तथ्य: 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