सी में एक 2d सरणी को शून्य करने का सबसे तेज़ तरीका?


92

मैं सी में एक बड़े 2d सरणी को बार-बार शून्य करना चाहता हूं। फिलहाल मैं यही कर रहा हूं:

// Array of size n * m, where n may not equal m
for(j = 0; j < n; j++)
{
    for(i = 0; i < m; i++)
    {  
        array[i][j] = 0;
    }
}

मैंने मेमसेट का उपयोग करने की कोशिश की है:

memset(array, 0, sizeof(array))

लेकिन यह केवल 1D सरणियों के लिए काम करता है। जब मैं 2D सरणी की सामग्री को प्रिंट करता हूं, तो पहली पंक्ति शून्य होती है, लेकिन फिर मुझे यादृच्छिक बड़ी संख्या का भार मिला और यह क्रैश हो गया।

जवाबों:


177
memset(array, 0, sizeof(array[0][0]) * m * n);

कहाँ mऔर nचौड़ाई और दो आयामी सरणी की ऊंचाई (अपने उदाहरण में, आप एक वर्ग दो आयामी सरणी तो है, कर रहे हैं m == n)।


1
यह काम करने के लिए प्रतीत नहीं होता है। मुझे कोडब्लॉक पर 'प्रोसेस लौटा -1073741819' मिला है, जो एक सेग फॉल्ट है?
एडी

8
@ एडी: हमें ऐरे की घोषणा दिखाओ।
GMANNICKG

1
मुझे यकीन है कि यह अन्य लाइनों पर दुर्घटनाग्रस्त हो रहा है, नहीं memset, क्योंकि आपने केवल एक पंक्ति को शून्य करने से दुर्घटनाग्रस्त होने का उल्लेख किया था।
ब्लाइंडी

3
हुह। बस के रूप में घोषित एक सरणी की कोशिश की int d0=10, d1=20; int arr[d0][d1], और memset(arr, 0, sizeof arr);उम्मीद के मुताबिक काम किया (जीसी 3.4.6, -std=c99 -Wallझंडे के साथ संकलित )। मुझे एहसास है कि "यह मेरी मशीन पर काम करता है" का अर्थ है कि डेड स्क्वाट, लेकिन काम memset(arr, 0, sizeof arr); करना चाहिए था। पूरे सरणी (d0 * d1 * sizeof (int)) द्वारा उपयोग किए जाने वाले बाइट्स की संख्या sizeof arr को वापस करना चाहिएsizeof array[0] * m * nआपको सरणी का सही आकार नहीं देगा।
जॉन बोडे

4
@ जॉन बोद: सच है, लेकिन यह निर्भर करता है कि सरणी कैसे प्राप्त की जाती है। यदि आपके पास एक फ़ंक्शन है जो एक पैरामीटर लेता है int array[][10], तो sizeof(array) == sizeof(int*)चूंकि पहले आयाम का आकार ज्ञात नहीं है। ओपी निर्दिष्ट नहीं किया कि सरणी कैसे प्राप्त की गई थी।
जेम्स मैकनेलिस

78

यदि arrayवास्तव में एक सरणी है, तो आप इसके साथ "शून्य आउट" कर सकते हैं:

memset(array, 0, sizeof array);

लेकिन दो बिंदु हैं जो आपको जानना चाहिए:

  • यह तभी काम करता है जब arrayवास्तव में "टू-डी एरे" हो, अर्थात, T array[M][N];किसी प्रकार के लिए घोषित किया गया हो T
  • यह केवल उस दायरे में काम करता है जहां arrayघोषित किया गया था। यदि आप इसे किसी फ़ंक्शन में पास करते हैं, तो नाम array सूचक को इंगित करता है , और sizeofआपको सरणी का आकार नहीं देगा।

चलिए एक प्रयोग करते हैं:

#include <stdio.h>

void f(int (*arr)[5])
{
    printf("f:    sizeof arr:       %zu\n", sizeof arr);
    printf("f:    sizeof arr[0]:    %zu\n", sizeof arr[0]);
    printf("f:    sizeof arr[0][0]: %zu\n", sizeof arr[0][0]);
}

int main(void)
{
    int arr[10][5];
    printf("main: sizeof arr:       %zu\n", sizeof arr);
    printf("main: sizeof arr[0]:    %zu\n", sizeof arr[0]);
    printf("main: sizeof arr[0][0]: %zu\n\n", sizeof arr[0][0]);
    f(arr);
    return 0;
}

मेरी मशीन पर, ऊपर के निशान:

main: sizeof arr:       200
main: sizeof arr[0]:    20
main: sizeof arr[0][0]: 4

f:    sizeof arr:       8
f:    sizeof arr[0]:    20
f:    sizeof arr[0][0]: 4

arrएक सरणी होने के बावजूद , यह एक पॉइंटर को उसके पहले तत्व के पास जाता है जब इसे पास किया जाता है f(), और इसलिए मुद्रित आकार f()"गलत" हैं। इसके अलावा, f()के आकार में arr[0]सरणी का आकार है arr[0], जो कि "सरणी [5] int" है। यह एक का आकार नहीं है int *, क्योंकि "क्षय" केवल पहले स्तर पर होता है, और इसीलिए हमें f()सूचक को सही आकार के एक सरणी में ले जाने की घोषणा करने की आवश्यकता होती है।

इसलिए, जैसा कि मैंने कहा, आप मूल रूप से जो कर रहे थे वह तभी काम करेगा जब ऊपर की दो स्थितियाँ संतुष्ट हों। यदि नहीं, तो आपको वह करने की आवश्यकता होगी जो दूसरों ने कहा है:

memset(array, 0, m*n*sizeof array[0][0]);

अंत में, memset()और forआपके द्वारा पोस्ट किया गया लूप सख्त अर्थों में समतुल्य नहीं है। वहाँ हो सकता है (और किया गया है) संकलक जहां "सभी बिट्स शून्य" कुछ प्रकारों के लिए शून्य के बराबर नहीं है, जैसे कि संकेत और फ्लोटिंग-पॉइंट मान। मुझे संदेह है कि आपको इसके बारे में चिंता करने की ज़रूरत है।


memset(array, 0, n*n*sizeof array[0][0]);मुझे लगता है कि आपका मतलब सही m*nनहीं n*nहै?
टैग

अजीब तरह से, यह 1 के बजाय 1 और 2 जैसे मानों के साथ काम नहीं करता है, इसके बजाय 0.
आशीष आहूजा

memsetबाइट (चार) स्तर पर काम करता है। चूंकि 1या 2अंतर्निहित प्रतिनिधित्व में समान बाइट्स नहीं हैं, आप ऐसा नहीं कर सकते memset
आलोक सिंघल

@AlokSinghal शायद इंगित करें कि " intआपके सिस्टम पर" न्यूनतम कामकाजी उदाहरण से पहले कहीं और 4 बाइट्स है , ताकि पाठक आसानी से रकम की गणना कर सकें।
18GA पर 71GA

9

वैसे, ऐसा करने का सबसे तेज़ तरीका यह नहीं है।

अजीब लगता है मुझे पता है, यहाँ कुछ छद्म कोड है:

int array [][];
bool array_is_empty;


void ClearArray ()
{
   array_is_empty = true;
}

int ReadValue (int x, int y)
{
   return array_is_empty ? 0 : array [x][y];
}

void SetValue (int x, int y, int value)
{
   if (array_is_empty)
   {
      memset (array, 0, number of byte the array uses);
      array_is_empty = false;
   }
   array [x][y] = value;
}

दरअसल, यह अभी भी एरे को क्लियर कर रहा है, लेकिन केवल तब जब एरे को कुछ लिखा जा रहा हो। यह यहाँ एक बड़ा फायदा नहीं है। हालाँकि, यदि 2D सरणी का उपयोग करके कहा गया था, तो कहिए, एक क्वाड ट्री (एक गतिशील एक दिमाग नहीं), या डेटा की पंक्तियों का एक संग्रह है, तो आप बूलियन ध्वज के प्रभाव को स्थानीय कर सकते हैं, लेकिन आपको अधिक झंडे की आवश्यकता होगी। क्वाड ट्री में, रूट नोड के लिए खाली ध्वज को सेट करें, पंक्तियों की सरणी में बस प्रत्येक पंक्ति के लिए ध्वज सेट करें।

जो इस सवाल की ओर जाता है "आप बार-बार एक बड़े 2d सरणी को शून्य क्यों करना चाहते हैं"? सरणी किसके लिए उपयोग की जाती है? क्या कोड को बदलने का कोई तरीका है ताकि सरणी को शून्य करने की आवश्यकता न हो?

उदाहरण के लिए, यदि आपके पास था:

clear array
for each set of data
  for each element in data set
    array += element 

अर्थात्, इसे एक संचय बफर के लिए उपयोग करें, फिर इसे इस तरह बदलने से प्रदर्शन में कोई सुधार नहीं होगा:

 for set 0 and set 1
   for each element in each set
     array = element1 + element2

 for remaining data sets
   for each element in data set
     array += element 

यह सरणी को साफ़ करने की आवश्यकता नहीं है, लेकिन फिर भी काम करता है। और यह सरणी को साफ़ करने की तुलना में बहुत तेज़ होगा। जैसा मैंने कहा, सबसे तेज़ तरीका यह है कि मैं इसे पहले स्थान पर न करूँ।


मुद्दे को देखने का दिलचस्प वैकल्पिक तरीका।
बेस्का

1
मुझे यकीन नहीं है कि हर एक रीड के लिए एक अतिरिक्त तुलना / शाखा जोड़ना इस मामले में सरणी के आरंभीकरण को रोकने के लायक है (हालांकि आपका हो सकता है)। यदि सरणी वास्तव में इतनी बड़ी है कि आरंभीकरण समय एक गंभीर चिंता का विषय है, तो वह इसके बजाय हैश का उपयोग कर सकता है।
tixxit 16

8

यदि आप वास्तव में हैं, तो वास्तव में गति के साथ जुनूनी (और पोर्टेबिलिटी के साथ इतना नहीं) मुझे लगता है कि ऐसा करने के लिए सबसे तेज़ तरीका SIMD वेक्टर आंतरिक का उपयोग करना होगा। इंटेल सीपीयू पर, आप इन SSE2 निर्देशों का उपयोग कर सकते हैं:

__m128i _mm_setzero_si128 ();                   // Create a quadword with a value of 0.
void _mm_storeu_si128 (__m128i *p, __m128i a);  // Write a quadword to the specified address.

प्रत्येक स्टोर इंस्ट्रक्शन एक हिट में चार 32-बिट इन्टस को शून्य पर सेट करेगा।

p को 16-बाइट के साथ जोड़ा जाना चाहिए, लेकिन यह प्रतिबंध गति के लिए भी अच्छा है क्योंकि यह कैश में मदद करेगा। अन्य प्रतिबंध यह है कि p को एक आबंटन आकार की ओर इशारा करना चाहिए जो कि 16-बाइट्स का एक से अधिक है, लेकिन यह बहुत अच्छा है क्योंकि यह हमें लूप को आसानी से अनियंत्रित करने की अनुमति देता है।

इसे एक लूप में रखें, और लूप को कुछ बार अनियंत्रित करें, और आपके पास एक तेज़ तेज़ इनिशियलाइज़र होगा:

// Assumes int is 32-bits.
const int mr = roundUpToNearestMultiple(m, 4);      // This isn't the optimal modification of m and n, but done this way here for clarity.    
const int nr = roundUpToNearestMultiple(n, 4);    

int i = 0;
int array[mr][nr] __attribute__ ((aligned (16)));   // GCC directive.
__m128i* px = (__m128i*)array;
const int incr = s >> 2;                            // Unroll it 4 times.
const __m128i zero128 = _mm_setzero_si128();

for(i = 0; i < s; i += incr)
{
    _mm_storeu_si128(px++, zero128);
    _mm_storeu_si128(px++, zero128);
    _mm_storeu_si128(px++, zero128);
    _mm_storeu_si128(px++, zero128);
}

इसका एक प्रकार यह भी है _mm_storeuकि कैश को दरकिनार कर दिया जाता है (यानी शून्य को रोकना कैश को प्रदूषित नहीं करेगा) जो आपको कुछ परिस्थितियों में कुछ माध्यमिक प्रदर्शन लाभ दे सकता है।

SSE2 संदर्भ के लिए यहां देखें: http://msdn.microsoft.com/en-us/library/kcwz153a(v=vs.80).aspx


5

यदि आप सरणी को इसके साथ आरंभ करते हैं malloc, तो callocइसके बजाय उपयोग करें ; यह आपके एरे को मुफ़्त में शून्य कर देगा। (समान रूप से स्पष्ट रूप से मेम के रूप में एकदम सही, आपके लिए बस कम कोड।)


अगर मैं बार-बार अपने एरे को शून्य करना चाहता हूं तो क्या यह याद से ज्यादा तेज है?
एडी

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


2

आपका 2D सरणी कैसे घोषित किया गया?

अगर यह कुछ इस तरह है:

int arr[20][30];

आप इसे करके शून्य कर सकते हैं:

memset(arr, sizeof(int)*20*30);

मैंने एक चार [10] [१०] सरणी का उपयोग किया है। लेकिन मुझे त्रुटि मिली: 'मेमसेट' को कार्य करने के लिए बहुत कम तर्क, और memset(a, 0, sizeof(char)*10*10);मेरे लिए ठीक काम करता है। , यह कैसे होता है?
noufal

1

मॉलॉक के बजाय कॉलोक का उपयोग करें। calloc सभी फ़ील्ड को 0 पर आरंभ करेगा।

int * a = (int *) calloc (n, का आकार (int));

// एक की सभी कोशिकाओं को 0 से आरंभीकृत किया गया है


0

मुझे लगता है कि इसे हाथ से करने का सबसे तेज़ तरीका कोड का पालन कर रहा है। आप तुलना कर सकते हैं कि यह फंक्शन को गति देने के लिए है, लेकिन यह धीमा नहीं होना चाहिए।

(ptr और ptr1 पॉइंटर्स का प्रकार बदलें यदि आपका सरणी प्रकार अलग है तो int)


#define SIZE_X 100
#define SIZE_Y 100

int *ptr, *ptr1;
ptr = &array[0][0];
ptr1 = ptr + SIZE_X*SIZE_Y*sizeof(array[0][0]);

while(ptr < ptr1)
{
    *ptr++ = 0;
}


आपका कोड सबसे संभवत: memsetचार प्रकार के लिए की तुलना में धीमा होगा ।
टोफरो

0
memset(array, 0, sizeof(int [n][n]));

1
सरणी [एन] [एन] सरणी के 1 तत्व का आकार है, इसलिए केवल सरणी का पहला तत्व आरंभीकृत किया जाएगा।
एविलटाइच

उफ़। आप सही हैं। मेरा मतलब था कि एक प्रकार का हस्ताक्षर करना, न कि किसी व्यूह में। ठीक कर दिया।
स्वैप्ट करें


-2

ऐसा इसलिए होता है क्योंकि sizeof (सरणी) आपको सरणी द्वारा इंगित ऑब्जेक्ट का आवंटन आकार देता है । ( सरणी आपके बहुआयामी सरणी की पहली पंक्ति के लिए केवल एक संकेतक है)। हालाँकि, आपने आकार की j सरणियाँ आवंटित की हैं i । नतीजतन, आपको एक पंक्ति के आकार को गुणा करना होगा, जो आपके द्वारा आवंटित पंक्तियों की संख्या के साथ आकार (सरणी) द्वारा लौटाया जाता है, जैसे:

bzero(array, sizeof(array) * j);

यह भी ध्यान दें कि आकार (सरणी) केवल सांख्यिकीय रूप से आवंटित सरणियों के लिए काम करेगा। एक गतिशील रूप से आवंटित सरणी के लिए आप लिखेंगे

size_t arrayByteSize = sizeof(int) * i * j; 
int *array = malloc(array2dByteSite);
bzero(array, arrayByteSize);

पहला भाग गलत है। के लिए sizeofऑपरेटर, arrayएक सूचक नहीं है (अगर यह एक सरणी घोषित किया गया था)। एक उदाहरण के लिए मेरा जवाब देखें।
आलोक सिंघल
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.