วิธีที่เร็วที่สุดในการทำให้อาร์เรย์ 2d ใน C เป็นศูนย์


93

ฉันต้องการศูนย์อาร์เรย์ 2d ขนาดใหญ่ซ้ำ ๆ ใน C นี่คือสิ่งที่ฉันทำในขณะนี้:

// 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 แล้ว:

memset(array, 0, sizeof(array))

แต่ใช้ได้กับอาร์เรย์ 1D เท่านั้น เมื่อฉันพิมพ์เนื้อหาของอาร์เรย์ 2 มิติแถวแรกเป็นศูนย์ แต่จากนั้นฉันได้รับตัวเลขจำนวนมากแบบสุ่มและเกิดปัญหา

คำตอบ:


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

ความกว้างและความสูงของอาร์เรย์สองมิติอยู่ที่ไหนmและอยู่ที่ไหนn(ในตัวอย่างของคุณคุณมีอาร์เรย์สองมิติแบบสี่เหลี่ยมจัตุรัสm == n)


1
ดูเหมือนจะไม่ได้ผล ฉันได้รับ 'กระบวนการส่งคืน -1073741819' บน codeblocks ซึ่งเป็นความผิดปกติของ seg ใช่ไหม
Eddy

8
@Eddy: แสดงการประกาศอาร์เรย์
GManNickG

1
ฉันพนันได้เลยว่ามันขัดข้องในบรรทัดอื่น ๆ ไม่ใช่memsetเพราะคุณพูดถึงการหยุดทำงานจากการศูนย์ออกเพียงแถวเดียวด้วย
Blindy

3
ฮะ. เพิ่งลองทดสอบอาร์เรย์ที่ประกาศเป็นint d0=10, d1=20; int arr[d0][d1]และmemset(arr, 0, sizeof arr);ทำงานตามที่คาดไว้ (gcc 3.4.6 รวบรวมด้วย-std=c99 -Wallแฟล็ก) ฉันตระหนักดีว่า "มันใช้งานได้บนเครื่องของฉัน" หมายถึงการนั่งพับเพียบ แต่น่าmemset(arr, 0, sizeof arr); จะได้ผล sizeof arr ควรส่งคืนจำนวนไบต์ที่ใช้โดยอาร์เรย์ทั้งหมด (d0 * d1 * sizeof (int)) sizeof array[0] * m * nจะไม่ให้ขนาดของอาร์เรย์ที่ถูกต้อง
John Bode

4
@ จอห์นโบด: จริง แต่ขึ้นอยู่กับวิธีการรับอาร์เรย์ ถ้าคุณมีฟังก์ชั่นที่ใช้พารามิเตอร์int array[][10]แล้วsizeof(array) == sizeof(int*)ตั้งแต่ขนาดของมิติครั้งแรกไม่เป็นที่รู้จัก OP ไม่ได้ระบุวิธีการรับอาร์เรย์
James McNellis

80

หากarrayเป็นอาร์เรย์อย่างแท้จริงคุณสามารถ "ศูนย์ออก" ด้วย:

memset(array, 0, sizeof array);

แต่มีสองประเด็นที่คุณควรรู้:

  • นี้ทำงานได้เฉพาะในกรณีที่arrayเป็นจริง "สอง-D อาร์เรย์" คือการประกาศสำหรับบางชนิด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] of int" ไม่ใช่ขนาดของ an 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ถูกต้อง?
Tagc

น่าแปลกที่สิ่งนี้ดูเหมือนจะใช้ไม่ได้กับค่าเช่น 1 และ 2 แทนที่จะเป็น 0
Ashish Ahuja

memsetทำงานที่ระดับไบต์ (ถ่าน) เนื่องจาก1หรือ2ไม่มีไบต์เดียวกันในการแทนค่าต้นแบบคุณจึงไม่สามารถทำเช่นนั้นmemsetได้
Alok Singhal

@AlokSinghal อาจจะชี้ให้เห็นว่า" intในระบบของคุณมีขนาด 4 ไบต์"อยู่ก่อนตัวอย่างการทำงานขั้นต่ำเพื่อให้ผู้อ่านสามารถคำนวณผลรวมได้อย่างง่ายดาย
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;
}

อันที่จริงมันยังคงล้างอาร์เรย์ แต่ก็ต่อเมื่อมีการเขียนบางอย่างลงในอาร์เรย์ นี่ไม่ใช่ข้อได้เปรียบที่ยิ่งใหญ่ที่นี่ อย่างไรก็ตามหากใช้อาร์เรย์ 2 มิติโดยใช้เช่นต้นไม้รูปสี่เหลี่ยม (ไม่ใช่แบบไดนามิกในใจเดียว) หรือการรวบรวมแถวข้อมูลคุณสามารถกำหนดเอฟเฟกต์ของแฟล็กบูลีนได้ แต่คุณต้องการแฟล็กเพิ่มเติม ในโครงสร้างรูปสี่เหลี่ยมเพียงตั้งค่าแฟล็กว่างสำหรับโหนดรูทในอาร์เรย์ของแถวเพียงแค่ตั้งค่าแฟล็กสำหรับแต่ละแถว

ซึ่งนำไปสู่คำถาม "ทำไมคุณถึงต้องการให้อาร์เรย์ 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 

สิ่งนี้ไม่จำเป็นต้องล้างอาร์เรย์ แต่ยังใช้งานได้ และนั่นจะเร็วกว่าการล้างอาร์เรย์ อย่างที่บอกวิธีที่เร็วที่สุดคืออย่าทำตั้งแต่แรก


วิธีอื่นที่น่าสนใจในการดูปัญหา
Beska

1
ฉันไม่แน่ใจว่าการเพิ่มการเปรียบเทียบ / สาขาพิเศษสำหรับการอ่านทุกครั้งจะคุ้มค่าที่จะเลื่อนการเริ่มต้นอาร์เรย์ในกรณีนี้ (แม้ว่าอาจเป็นของคุณ) หากอาร์เรย์มีขนาดใหญ่มากจนเวลาเริ่มต้นทำให้เกิดความกังวลอย่างมากเขาสามารถใช้แฮชแทนได้
tixxit

8

ถ้าคุณเป็นคนชอบใช้ความเร็วจริงๆ (และไม่ค่อยพกพามากนัก) ฉันคิดว่าวิธีที่เร็วที่สุดในการทำเช่นนี้คือการใช้วัสดุภายในเวกเตอร์ SIMD เช่นใน CPU ของ Intel คุณสามารถใช้คำแนะนำ 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.

คำสั่งของร้านค้าแต่ละรายการจะตั้งค่า int 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แทน; มันจะเป็นศูนย์อาร์เรย์ของคุณฟรี (เห็นได้ชัดว่าสมบูรณ์แบบเช่นเดียวกับ memset มีรหัสน้อยกว่าสำหรับคุณ)


เร็วกว่า memset หรือไม่ถ้าฉันต้องการให้อาร์เรย์เป็นศูนย์ซ้ำ ๆ
Eddy

calloc จะเป็นศูนย์ครั้งเดียวในเวลาเริ่มต้นและไม่น่าจะเร็วไปกว่าการโทรหา malloc ตามด้วย memset คุณเป็นของตัวเองหลังจากนั้นและสามารถใช้ memset ได้หากคุณต้องการเป็นศูนย์อีกครั้ง เว้นแต่อาร์เรย์ของคุณจะมีขนาดใหญ่มากความสมบูรณ์แบบไม่ได้เป็นสิ่งที่ต้องคำนึงถึงในเครื่องที่สมเหตุสมผล
Ben Zotto


2

อาร์เรย์ 2D ของคุณถูกประกาศอย่างไร?

หากเป็นสิ่งที่ต้องการ:

int arr[20][30];

คุณสามารถเป็นศูนย์ได้โดยทำ:

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

ฉันใช้อาร์เรย์ char [10] [10] แต่ฉันพบข้อผิดพลาด: อาร์กิวเมนต์น้อยเกินไปที่จะทำงาน 'memset' และmemset(a, 0, sizeof(char)*10*10);ทำงานได้ดีสำหรับฉัน , มันเกิดขึ้นได้อย่างไร?
noufal

1

ใช้ calloc แทน malloc calloc จะเริ่มต้นฟิลด์ทั้งหมดเป็น 0

int * a = (int *) calloc (n ขนาดของ (int));

// เซลล์ทั้งหมดของ a เริ่มต้นเป็น 0


0

ฉันคิดว่าวิธีที่เร็วที่สุดในการทำด้วยมือคือการทำตามรหัส คุณสามารถเปรียบเทียบความเร็วกับฟังก์ชัน memset ได้ แต่ไม่ควรช้ากว่า

(เปลี่ยนชนิดของตัวชี้ 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ประเภทถ่าน
tofro

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

1
array [n] [n] คือขนาดของ 1 องค์ประกอบของอาร์เรย์ดังนั้นเฉพาะองค์ประกอบแรกของอาร์เรย์เท่านั้นที่จะเริ่มต้น
EvilTeach

อ๊ะ. ใช่คุณเป็น ฉันหมายถึงการใส่ลายเซ็นประเภทใน parens ไม่ใช่การค้นหาอาร์เรย์ ซ่อมมัน.
swestrup


-2

นี้เกิดขึ้นเพราะ sizeof (อาร์เรย์) จะช่วยให้คุณจัดสรรขนาดของวัตถุที่ชี้ไปตามอาร์เรย์ ( อาร์เรย์เป็นเพียงตัวชี้ไปยังแถวแรกของอาร์เรย์หลายมิติของคุณ) อย่างไรก็ตามคุณจัดสรรเจอาร์เรย์ขนาดฉัน ดังนั้นคุณต้องคูณขนาดของหนึ่งแถวซึ่งส่งคืนโดย sizeof (อาร์เรย์) ด้วยจำนวนแถวที่คุณจัดสรรเช่น:

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

โปรดทราบว่า sizeof (อาร์เรย์) จะใช้ได้เฉพาะกับอาร์เรย์ที่จัดสรรแบบคงที่เท่านั้น สำหรับอาร์เรย์ที่จัดสรรแบบไดนามิกคุณจะเขียน

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

ส่วนแรกผิดพลาด สำหรับsizeofตัวดำเนินการarrayไม่ใช่ตัวชี้ (หากมีการประกาศอาร์เรย์) ดูคำตอบของฉันสำหรับตัวอย่าง
Alok Singhal
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.