본문 바로가기

Program Language and Algorithm

배열의 초기화 ( memset)

반응형

일반적인 컴퓨팅 디바이스에서 배열의 초기화는 필요 없는 경우가 많습니다. 소프트웨어 엔지니어가 신경 쓰지 않아도 알아서 0으로 초기화해주기도 하고, 알고리즘 구조상 필요 없는 경우도 많습니다. 하지만 만약 Arm과 같은 프로세서를 이용한다면 특별히 초기화에 신경 써 줄 필요가 있기도 하고, 사실 초기화 시간이 오래 걸리는 작업도 아니기에 해주는 게 좋습니다.

먼저 C와 C++에서 모두 쓸수 있는 방법으로 시작하죠. 배열을 초기화하는 가장 간단한 방식은 아래 코드와 같이  반복문으로 초기화하는 거죠. 간단하고 가독성도 높습니다. 무엇보다 아래 코드에서와 같이 원하는 값으로 쉽게 설정할 수 있습니다.(input)

#include <iostream>

using namespace std;

const int N = 1024;

int arr1D[N];
int arr2D[N][N];

int main(int argc, char ** argv){

    int input;

    sscanf(argv[1], "%d", &input);

    for(int i = 0 ; N > i ; i++){
	arr1D[i] = input;
    }
    for(int i = 0 ; N > i ; i++){
	for(int j = 0 ; N > j ; j++){
	    arr2D[i][j] = input;
	}
    }
    return 0;
}

다른 방식은 memset을 이용하는 방식입니다. 링크의 설명과 같이 포인터와 설정할 값, 갯수 세 가지 변수를 입력해 주면 됩니다. 코드는 아래와 같습니다. 여기선 0으로 초기화했죠. 코드는 반복문을 사용하는 것보다 간결합니다. 가독성도 마찬가지로 높고요. 단 한 가지 단점이 있습니다. 함수의 설명에서는 두 번째 변수인 설정할 값이 int 형으로 되어 있습니다. 하지만 실제 적용되는 수치는 1byte의 값입니다.

#include <iostream>
#include <cstring>

using namespace std;

const int N = 1024;

int arr1D[N];
int arr2D[N][N];

int main(int argc, char ** argv){

    int input;

    sscanf(argv[1], "%d", &input);

    memset(arr1D, input, sizeof arr1D);
    memset(arr2D, input, sizeof arr2D);

    cout << arr1D[1] << endl;

    return 0;
}

구동해서 결과를 보면 아래와 같은 결과가 나옵니다. 설정 값을 0으로 했을때는 문제없으나 다른 숫자에 대해서는 다른 값이 설정되는 문제가 발생합니다.

예제를 구동한 컴퓨터에서 int형 변수는 4byte를 사용합니다. 하지만 memset은 1byte단위로 값을 설정합니다. 그래서 위와 같은 문제가 발생하게 되는 거죠. 1byte로 0은 '0x00'이고, 4byte로 0은 '0x00000000' 이죠, 따라서 0으로 설정할 때는 문제가 없습니다. 하지만 1로 설정할 경우 1byte의 1은 '0x01'이고, 4byte의 1은 '0x00000001'입니다. 하지만 memset은 1byte 단위로 값을 설정하기 때문에 위 예제의 결과는 '0x01010101' 이 되어 버립니다. 그래서 int 형 정수로 읽으면 168,430,009이 되어 버리죠. 이 문제로 인해 memset은 설정할 값이 0이나 -1 ( -1은 2의보수로 1byte의 값이 0 xFF이며, 4byte의 값도 0 xFFFFFFFF입니다.) 일 때 사용합니다. (다른 숫자도 몇몇 가능하지만 실효성으로 볼 때 위 두 개의 숫자만 가능하다고 봐도 충분합니다.) -1로 설정할 때에는 -1이나 255를 쓰시면 됩니다.

마지막으로 구동 시간을 분석해 봐야 겠죠. 위 코드를 각각 100,000번씩 구동해서 구동 시간을 구하고, 이를 히스토 그램으로 분석합니다. 분석 결과 그래프는 아래와 같습니다.

왼쪽은 1D 배열 오른쪽은 2D 배열 입니다. 실선은 설정 값이 '0'일 때이고 점선은 '-1'일 때입니다. 사실 지금 이 글을 쓰고 있는 시간에서 일주일 전에 얻은 결과예요. 그걸 지금 와서 올리는 이유는 보고 멘붕에 빠졌거든요. 2D의 결과는 예상대로 인데, 1D는 기존 상식과 다른 결과를 보여주었습니다. 제가 언어를 배울 때 memset을 굳이 쓰는 이유는 빨라서 라고 배웠는데.... 그러나 1D배열에 대해서는 그냥 for loop로 설정하는 게 빠르다고 나와 버렸어요. 이건 컴파일러 발전의 결과일까요. 아니면 기존에 제가 배운 상식이 잘못된 것일까요.

아무튼 위 문제는 "아 그랬구나" 하고 넘어갈 단순한 건이 아닙니다. 아직도 많은 사람들이 다차원 데이터를 1차원으로 변형하여 처리하기도 하고, 최적화 라며 배열 초기화 코드를 memset으로 처리하거든요. 그게 가독성은 떨어지지만 성능이 우수하니까요.

그래서 더 정밀한 분석을 해볼까 합니다. 이 글은 이미 너무 길어 졌으니, 다음 글로 작성할게요. 완성되면 링크 붙인 겠습니다.

 

그럼 감사합니다.

반응형