रीसेट करें C int array to zero: सबसे तेज़ तरीका?


102

यह मानते हुए कि हमारे पास T myarray[100]T = int, अहस्ताक्षरित int, लंबे लंबे int या अहस्ताक्षरित long int के साथ है, इसकी सभी सामग्री को शून्य पर रीसेट करने का सबसे तेज़ तरीका क्या है (न केवल आरंभीकरण के लिए, बल्कि मेरे कार्यक्रम में कई बार सामग्री रीसेट करने के लिए) ? शायद मेमसेट के साथ?

एक गतिशील सरणी के लिए एक ही सवाल की तरह T *myarray = new T[100]


16
@BoPersson: ठीक है, new है सी ++ ...
Matteo इटली

@ मत्ते - अच्छा, हाँ। उत्तर को बहुत प्रभावित नहीं किया (बस अब तक :-))।
बो पर्सन

3
@BoPersson: मुझे बुरा लगा तभी बात करना, memsetजब C ++ किसी तरह शामिल हो गया ... :)
माटेओ इटालिया

2
एक आधुनिक संकलक पर, आप एक साधारण forलूप को हरा नहीं सकते हैं । लेकिन, आश्चर्यजनक रूप से, आप स्मार्ट बनने की कोशिश करके बहुत बुरा कर सकते हैं।
डेविड श्वार्ट्ज

एक संरचना का उपयोग करें और इसके अंदर एक सरणी छड़ी। एक उदाहरण बनाएं जो सभी शून्य हो। उस का उपयोग करें जो आपके द्वारा बनाए गए अन्य लोगों को शून्य करने के लिए है। यह अच्छा काम करता है। कोई शामिल नहीं, कोई कार्य नहीं, बहुत तेजी से।
Xofo

जवाबों:


170

memset(से <string.h>) शायद सबसे तेज़ मानक तरीका है, क्योंकि यह आम तौर पर सीधे विधानसभा में लिखा जाता है और हाथ से अनुकूलित होता है।

memset(myarray, 0, sizeof(myarray)); // for automatically-allocated arrays
memset(myarray, 0, N*sizeof(*myarray)); // for heap-allocated arrays, where N is the number of elements

वैसे, C ++ में मुहावरेदार तरीका होगा उपयोग std::fill(से <algorithm>):

std::fill(myarray, myarray+N, 0);

जो स्वचालित रूप से एक में अनुकूलित किया जा सकता है memset; मुझे पूरा यकीन है कि यह एस के memsetलिए उतनी ही तेजी से काम करेगा int, जबकि यह छोटे प्रकार के लिए थोड़ा खराब प्रदर्शन कर सकता है यदि अनुकूलक पर्याप्त स्मार्ट नहीं है। फिर भी, जब संदेह में, प्रोफ़ाइल।


10
1999 आईएसओ सी मानक के अनुसार, यह वास्तव में गारंटी नहीं थी कि memsetपूर्णांक 0 पर सेट होगा; कोई विशिष्ट कथन नहीं था कि ऑल-बिट्स-जीरो एक प्रतिनिधित्व है 0। एक तकनीकी कोरिगेंडम ने ऐसी गारंटी जोड़ी, जो 2011 आईएसओ सी मानक में शामिल है। मुझे विश्वास है कि सब-बिट शून्य है की एक मान्य प्रतिनिधित्व 0सभी मौजूदा C और C ++ कार्यान्वयन में सभी प्रकार पूर्णांक है, जिसके कारण समिति कि आवश्यकता को जोड़ने में सक्षम था। (फ़्लोटिंग-पॉइंट या पॉइंटर प्रकारों के लिए समान गारंटी नहीं है।)
कीथ थॉम्पसन

3
@ KeithThompson की टिप्पणी में जोड़ना: TC2 (2004) में सादे पाठ में यह गारंटी 6.2.6.2/5 जोड़ा गया; हालाँकि यदि कोई पेडिंग बिट्स नहीं हैं तो 6.2.6.2/1 और / 2 पहले से ही गारंटी देते हैं कि ऑल-बिट्स-शून्य था 0। (पैडिंग बिट्स के साथ संभावना मौजूद है कि ऑल-बिट्स-शून्य एक ट्रैप प्रतिनिधित्व हो सकता है)। लेकिन किसी भी स्थिति में, टीसी को दोषपूर्ण पाठ को स्वीकार करने और बदलने के लिए माना जाता है, इसलिए 2004 तक हमें ऐसा कार्य करना चाहिए जैसे कि C99 हमेशा इस पाठ को समाहित करता है।
एमएम

सी में, यदि आपने गतिशील सरणी को सही ढंग से आवंटित किया है , तो दोनों मेमों के बीच कोई अंतर नहीं होगा। सही गतिशील आवंटन होगा int (*myarray)[N] = malloc(sizeof(*myarray));
लुंडिन सेप

@ लुंडिन: बेशक - यदि आप संकलन समय पर जानते हैं कि कितना बड़ा Nहै, लेकिन अगर mallocआप केवल रनटाइम में ही जानते थे, तो अधिकांश मामलों में।
मट्टियो इटालिया

@MatteoItalia हम वर्ष 1999 के बाद से वीएलएएस हैं।
लुंडिन

20

यह सवाल, हालांकि पुराना है, कुछ मानदंड की जरूरत है, क्योंकि यह सबसे मुहावरेदार तरीके के लिए नहीं कहता है, या जिस तरह से सबसे कम लाइनों में लिखा जा सकता है, लेकिन सबसे तेज़ तरीका है। और यह कुछ वास्तविक परीक्षण के बिना उस सवाल का जवाब देने के लिए मूर्खतापूर्ण है। इसलिए मैंने चार समाधानों की तुलना की, मेमसेट बनाम एसटीडी :: एईटी के उत्तर के जीरो बनाम जीरो बनाम एक समाधान जो मैंने एवीएक्स इंट्रेंसिक्स का उपयोग करके बनाया है।

ध्यान दें कि यह समाधान सामान्य नहीं है, यह केवल 32 या 64 बिट्स के डेटा पर काम करता है। कृपया टिप्पणी करें कि क्या यह कोड कुछ गलत कर रहा है।

#include<immintrin.h>
#define intrin_ZERO(a,n){\
size_t x = 0;\
const size_t inc = 32 / sizeof(*(a));/*size of 256 bit register over size of variable*/\
for (;x < n-inc;x+=inc)\
    _mm256_storeu_ps((float *)((a)+x),_mm256_setzero_ps());\
if(4 == sizeof(*(a))){\
    switch(n-x){\
    case 3:\
        (a)[x] = 0;x++;\
    case 2:\
        _mm_storeu_ps((float *)((a)+x),_mm_setzero_ps());break;\
    case 1:\
        (a)[x] = 0;\
        break;\
    case 0:\
        break;\
    };\
}\
else if(8 == sizeof(*(a))){\
switch(n-x){\
    case 7:\
        (a)[x] = 0;x++;\
    case 6:\
        (a)[x] = 0;x++;\
    case 5:\
        (a)[x] = 0;x++;\
    case 4:\
        _mm_storeu_ps((float *)((a)+x),_mm_setzero_ps());break;\
    case 3:\
        (a)[x] = 0;x++;\
    case 2:\
        ((long long *)(a))[x] = 0;break;\
    case 1:\
        (a)[x] = 0;\
        break;\
    case 0:\
        break;\
};\
}\
}

मैं यह दावा नहीं करूंगा कि यह सबसे तेज़ तरीका है, क्योंकि मैं निम्न स्तर का अनुकूलन विशेषज्ञ नहीं हूं। बल्कि यह एक सही आर्किटेक्चर पर निर्भर कार्यान्वयन का एक उदाहरण है जो कि शुरुआत की तुलना में तेज है।

अब, परिणामों पर। मैंने आकार 100 इंट और लंबे लंबे सरणियों के लिए प्रदर्शन की गणना की, दोनों स्टेटिक और डायनामिक रूप से आवंटित, लेकिन msvc के अपवाद के साथ, जिसने स्थिर सरणियों पर एक मृत कोड उन्मूलन किया, परिणाम बेहद तुलनीय थे, इसलिए मैं केवल गतिशील सरणी प्रदर्शन दिखाऊंगा। टाइम मार्किंग 1 मिलियन पुनरावृत्तियों के लिए एमएस हैं, time.h की कम परिशुद्धता घड़ी फ़ंक्शन का उपयोग करते हुए।

क्लैंग 3.8 (क्लैंग-क्ल फ्रंटेंड, ऑप्टिमाइज़ेशन फ्लैग = / ओएक्स / आर्क: एवीएक्स / ओआई / ओट का उपयोग करके)

int:
memset:      99
fill:        97
ZERO:        98
intrin_ZERO: 90

long long:
memset:      285
fill:        286
ZERO:        285
intrin_ZERO: 188

gcc 5.1.0 (अनुकूलन झंडे: -O3 -march = देशी -mtune = देशी-mavx):

int:
memset:      268
fill:        268
ZERO:        268
intrin_ZERO: 91
long long:
memset:      402
fill:        399
ZERO:        400
intrin_ZERO: 185

msvc 2015 (अनुकूलन झंडे: / OX / मेहराब: AVX / Oi / Ot):

int
memset:      196
fill:        613
ZERO:        221
intrin_ZERO: 95
long long:
memset:      273
fill:        559
ZERO:        376
intrin_ZERO: 188

यहाँ बहुत दिलचस्प चल रहा है: हत्या कर रहा हूँ gcc, MSVC के विशिष्ट धब्बेदार अनुकूलन (यह स्थिर सरणियों पर एक प्रभावशाली मृत कोड उन्मूलन करता है और फिर भराव के लिए भयानक प्रदर्शन होता है)। हालांकि मेरा कार्यान्वयन काफी तेज है, यह केवल इसलिए हो सकता है क्योंकि यह मानता है कि बिट क्लियरिंग में किसी भी अन्य सेटिंग ऑपरेशन की तुलना में बहुत कम ओवरहेड है।

क्लैंग का कार्यान्वयन अधिक देखने योग्य है, क्योंकि यह काफी तेज है। कुछ अतिरिक्त परीक्षण से पता चलता है कि इसकी शुरुआत वास्तव में शून्य के लिए विशिष्ट है - 400 बाइट सरणी के लिए गैर शून्य मेमसेट्स बहुत धीमे (~ 220ms) हैं और जीसीसी के लिए तुलनीय हैं। हालांकि, 800 बाइट सरणी के साथ नॉनजरो मेमसेटिंग में कोई गति अंतर नहीं होता है, यही वजह है कि शायद इस मामले में, उनके कार्यान्वयन में मेरे कार्यान्वयन की तुलना में खराब प्रदर्शन है - विशेषज्ञता केवल छोटे सरणियों के लिए है, और कटऑफ 800 सेंटीमीटर के लिए सही है। यह भी ध्यान दें कि gcc 'fill' और 'ZERO' को याद करने के लिए अनुकूलन नहीं है (उत्पन्न कोड को देखते हुए), gcc केवल समान प्रदर्शन विशेषताओं के साथ कोड जनरेट कर रहा है।

निष्कर्ष: मेमसेट वास्तव में इस कार्य के लिए अनुकूलित नहीं है, साथ ही साथ लोग यह दिखावा भी करेंगे (अन्यथा gcc और msvc और llvm के मेमसेट का प्रदर्शन समान होगा)। यदि प्रदर्शन मायने रखता है, तो मेमसेट अंतिम समाधान नहीं होना चाहिए, विशेष रूप से इन अजीब मध्यम आकार के सरणियों के लिए, क्योंकि यह बिट क्लियरिंग के लिए विशेष नहीं है, और यह संकलित नहीं है जो संकलक की तुलना में बेहतर है।


4
कोड के बिना और संकलक संस्करण और उपयोग किए गए विकल्पों के उल्लेख के बिना एक बेंचमार्क? हम्म ...
मार्क ग्लिस

मेरे पास पहले से ही संकलक संस्करण थे (वे सिर्फ एक छोटे से छिपे हुए थे), और बस उपयोग किए गए लागू विकल्पों को जोड़ा।
बेंजामिन

अमान्य प्रकार के तर्क '*' ('size_t {aka unsigned int}') |
पायोत्र वासिल्विक

अपने स्वयं के अनुकूलित शून्यिंग विधि को लिखने के लिए इतना उदार होना - क्या आप इसे कैसे काम करते हैं, और यह तेज क्यों है? कोड सभी लेकिन आत्म-व्याख्यात्मक है।
मोतिन शंकर

1
@MottiShneor इसकी तुलना में अधिक जटिल है। एक AVX रजिस्टर का आकार 32bytes है। इसलिए वह गणना करता है कि aएक रजिस्टर में कितने मूल्य फिट हैं। बाद में, वह सभी 32byte ब्लॉकों पर लूप करता है, जो कि सूचक अंकगणित ( (float *)((a)+x)) का उपयोग करके पूरी तरह से ओवरराइट किया जाना चाहिए । दो आंतरिक (शुरुआत के साथ _mm256) बस एक शून्य-प्रारंभिक 32byte रजिस्टर बनाते हैं और इसे वर्तमान पॉइंटर में संग्रहीत करते हैं। यह पहली 3 पंक्तियाँ हैं। बाकी बस सभी विशेष मामलों को संभालता है जहां पिछले 32byte ब्लॉक को पूरी तरह से अधिलेखित नहीं किया जाना चाहिए। यह वैश्वीकरण के कारण तेज है। - मुझे आशा है कि वह मदद करेंगे।
विचमास्टर

11

से memset():

memset(myarray, 0, sizeof(myarray));

sizeof(myarray)यदि myarrayसंकलन-समय पर आकार ज्ञात हो तो आप इसका उपयोग कर सकते हैं । अन्यथा, यदि आप एक डायनामिक-आकार के सरणी का उपयोग कर रहे हैं, जैसे कि के माध्यम से प्राप्त किया गया है mallocया new, आपको लंबाई का ट्रैक रखने की आवश्यकता होगी।


2
आकार-निर्धारण तब भी काम करेगा जब सरणी का आकार संकलन-समय पर ज्ञात न हो। (बेशक, केवल जब यह सरणी है)
asaelr

2
@asaelr: C ++ में, sizeofहमेशा संकलन-समय पर मूल्यांकन किया जाता है (और इसका उपयोग VLAs के साथ नहीं किया जा सकता है)। C99 में, यह VLAs के मामले में एक रनटाइम एक्सप्रेशन हो सकता है।
बेन वोइगट

खैर @BenVoigt, सवाल दोनों के बारे में है cऔर c++। मैंने एलेक्स के उत्तर पर टिप्पणी की, जो कहता है कि "आप आकार-प्रकार (मायार्रे) का उपयोग कर सकते हैं यदि मायार्रे का आकार संकलन-समय पर जाना जाता है"।
असाल्र

2
@asaelr: और C ++ में, वह पूरी तरह से सही है। आपकी टिप्पणी ने C99 या VLAs के बारे में कुछ नहीं कहा, इसलिए मैं इसे स्पष्ट करना चाहता था।
बेन वोइगट

5

आप उपयोग कर सकते हैं memset, लेकिन केवल इसलिए कि हमारे प्रकारों का चयन अभिन्न प्रकारों तक सीमित है।

सी में सामान्य मामले में यह मैक्रो को लागू करने के लिए समझ में आता है

#define ZERO_ANY(T, a, n) do{\
   T *a_ = (a);\
   size_t n_ = (n);\
   for (; n_ > 0; --n_, ++a_)\
     *a_ = (T) { 0 };\
} while (0)

यह आपको C ++ देगा - जैसे कार्यक्षमता जो आपको किसी भी प्रकार की वस्तुओं की एक सरणी को "हैक करने के लिए रिज़ॉर्ट करने के लिए" के रूप में "शून्य पर रीसेट" करने देगा memset। मूल रूप से, यह C ++ फ़ंक्शन टेम्पलेट का एक सी एनालॉग है, सिवाय इसके कि आपको टाइप तर्क को स्पष्ट रूप से निर्दिष्ट करना होगा।

उसके शीर्ष पर आप गैर-क्षय हुए सरणियों के लिए एक "टेम्पलेट" बना सकते हैं

#define ARRAY_SIZE(a) (sizeof (a) / sizeof *(a))
#define ZERO_ANY_A(T, a) ZERO_ANY(T, (a), ARRAY_SIZE(a))

आपके उदाहरण में इसे लागू किया जाएगा

int a[100];

ZERO_ANY(int, a, 100);
// or
ZERO_ANY_A(int, a);

यह भी ध्यान देने योग्य है कि विशेष रूप से अदिश प्रकार की वस्तुओं के लिए एक प्रकार का स्वतंत्र मैक्रो लागू कर सकता है

#define ZERO(a, n) do{\
   size_t i_ = 0, n_ = (n);\
   for (; i_ < n_; ++i_)\
     (a)[i_] = 0;\
} while (0)

तथा

#define ZERO_A(a) ZERO((a), ARRAY_SIZE(a))

उपरोक्त उदाहरण में बदल रहा है

 int a[100];

 ZERO(a, 100);
 // or
 ZERO_A(a);

1
मैं के ;बाद छोड़ देना होगा while(0), तो एक कॉल कर सकते हैं ZERO(a,n);, +1 महान जवाब
0x90

@ 0x90: हाँ, आप बिलकुल सही हैं। do{}while(0)मुहावरे में पूरे मुहावरे की आवश्यकता नहीं ;है। फिक्स्ड।
चींटी

3

स्थिर घोषणा के लिए मुझे लगता है कि आप उपयोग कर सकते हैं:

T myarray[100] = {0};

गतिशील घोषणा के लिए मैं उसी तरह सुझाव देता हूं: memset


2
प्रश्न कहता है: "न केवल आरंभीकरण के लिए"।
बेन वोइगट

2

zero(myarray); आप सभी की जरूरत है C ++ में।

बस इसे हेडर में जोड़ें:

template<typename T, size_t SIZE> inline void zero(T(&arr)[SIZE]){
    memset(arr, 0, SIZE*sizeof(T));
}

1
यह गलत है, यह SIZE बाइट्स को साफ कर देगा। 'मेमसेट (गिरफ्तारी, 0, आकार * आकार (टी));) सही होगा।
कोर्नेल किलीविलेज़

@KornelKisielewicz D'oh! मुझे उम्मीद है कि पिछले 1.5 वर्षों में किसी ने भी इस फ़ंक्शन को कॉपी-पेस्ट नहीं किया :(
नवीन

1
आशा नहीं, मैंने टिप्पणी की क्योंकि Google ने मुझे यहाँ लाया :)
कोर्नेल किसेल्विकेज़

1
ध्यान दें कि यह फ़ंक्शन zeroभी उदाहरण के लिए सही है T=char[10]जैसा कि हो सकता है जब arrतर्क एक बहुआयामी सरणी उदाहरण के लिए हो char arr[5][10]
मंडराके

1
हां, मैंने gcc 4.7.3 के साथ कई मामलों का परीक्षण किया। मुझे लगता है कि इस उत्तर के लिए ध्यान देना अच्छा होगा, क्योंकि आपको अन्यथा प्रत्येक सरणी आयाम गणना के लिए टेम्पलेट विशेषज्ञताओं की आवश्यकता होगी। अन्य उत्तर भी सामान्यीकरण नहीं करते हैं, जैसे कि ARRAY_SIZEमैक्रो, जो गलत आकार देता है यदि इसका उपयोग एक बहुआयामी सरणी पर किया जाता है, तो बेहतर नाम शायद होगा ARRAY_DIM<n>_SIZE
मंड्रे

1

यहाँ मैं उपयोग समारोह है:

template<typename T>
static void setValue(T arr[], size_t length, const T& val)
{
    std::fill(arr, arr + length, val);
}

template<typename T, size_t N>
static void setValue(T (&arr)[N], const T& val)
{
    std::fill(arr, arr + N, val);
}

आप इसे इस तरह कह सकते हैं:

//fixed arrays
int a[10];
setValue(a, 0);

//dynamic arrays
int *d = new int[length];
setValue(d, length, 0);

ऊपर से सी + 11 11 का उपयोग करने की तुलना में अधिक है। यदि आप आकार निर्दिष्ट करने के साथ गतिशील सरणी का उपयोग करते हैं, तो भी आपको संकलन समय त्रुटि मिलती है।


मूल प्रश्न C पर है, C ++ पर नहीं, इसलिए std :: fill एक उचित उत्तर नहीं हो सकता है
Motti Shneor
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.