C++ 동적할당 배열 초기화의 효율 --속도 측면

MyAbby의 이미지

안녕하세요.

어떤 동적할당 한 메모리 영역을 초기화하는 방법은. 제가 알기로는 한 3가지? 정도 됩니다.
일단, calloc()이나 realloc() 같은 함수는 쓰지 않습니다. C++의 new 연산자만 사용하고, 초기화는 0으로. 제가 집중하고 싶은 건 속도입니다. 할당 비용을 제외한, 순수하게 배열을 초기화하는 속도에 중점을 둡시다.

[] 연산:

size_t mSize = 1024;
char *m = new char[mSize];
size_t i;
 
for(i=0; i<mSize; ++i)
   m[i] = 0;

포인터 접근 - 회수 반복:

size_t mSize = 1024;
char *m = new char[mSize];
char *p = m;
size_t i;
 
for(i=0; i<mSize; ++i)
   *(p++) = 0;

포인터 접근 - 경계 반복:

size_t mSize = 1024;
char *m = new char[mSize];
char *p = m, *end = m + mSize;
 
while(p < end)
   *(p++) = 0;

그리고 memset():

size_t mSize = 1024;
char *m = new char[mSize];
 
memset(m, 0, mSize * sizeof(char));

이 스레드에서 보면 한 iteration 에 여러 영역을 초기화 하는 것도 효과가 있다는 걸로 설명합니다. 한 스레드가 실행할 코드인데 저렇게 한다고 뭐가 달라지는 가는 의문입니다. 컴파일러가 예측실행 최적화도 하나?

제가 알고 싶은 건 크기가 커질 수록 더 효과를 발휘하는 방법입니다. 표준 라이브러리 최적화 잘하는 posix 환경(리눅스)에서는 memset() 이 아무래도 괜찮을 것 같기도 한데... 다른 방법이나 아니면 이중에서 제일 괜찮은 방법이 뭘까요?

x

yukariko의 이미지

memset이 저 셋중에 가장 느린걸로 알고있어요.
다른건 어떤게 빠를지는.. 글쎄요

size_t mSize = 1024;
char *m = new char[mSize];
size_t i;
 
for(i=0; i<mSize; i+=2)
{
   m[i] = 0;
   m[i+1] = 0;
}

이런 방법이 1번보다 빠르다고 알고있어요. for문의 비교연산횟수를 반으로 줄여주기 때문이죠.
하지만 사이즈가 홀수이면 1을 미리 해줘서 짝수로 만들어주고나서 해야겠죠.
익명 사용자의 이미지

memset()이 빠릅니다. 아키텍쳐별에 최적화된 어셈블리 코드가 들어가 있기 때문에, C 코드로 아무리 최적화하더라도 이 코드를 따라가기 어렵습니다. 초창기 glibc도 아니고 이미 많은 손을 거쳐온 코드이기도 합니다.

for 루프를 쓰는 방법은 코드 상황에 따라서 성능에 편차가 존재합니다. 또한 실제로 메모리 접근해서 값을 쓰는 정의된 행동을 하는 코드가 만들어졌다면, 그 성능이 memset()에 비해서 느립니다. 빠른 경우가 있어서 살펴보면, 실제로 메모리 접근해서 값을 쓸 필요가 없는 코드라서 컴파일러가 최적화 한 상황이었습니다.

다시 말합니다만, 테스트에서 어떤 C 코드가 memset() 보다 좋은 성능이 나왔다면, 코드 상황에 따라서 캄파일러가 실제로 정의한 행동이 아닌 동작을 하도록 코드를 생성했기 때문입니다. 링크해 주신 kldp 글들에 나오는 많은 코드가 그런 예에 해당합니다. 하지만 일부러 테스트용으로 만든 코드들이기 때문에, 실상황과 매치되는 코드들은 아닙니다.

그래도 테스트해 보고 싶다면, 테스트할 때 memset() 함수와 같은 형태의 함수를 만들어서 memset()과 성능비교를 해 보시기 바랍니다.

void *memset(void *s, int c, size_t n);
익명 사용자의 이미지

속도가 중요하다면 메모리풀을 사용하면 됩니다

중요한건 알고리즘입니다

kukyakya의 이미지

컴파일러 최적화에 의해 memset을 호출하는 코드가 생성되기도 합니다.

gcc의 -ftree-loop-distribute-patterns (-O3부터 활성화됨) 옵션을 이용하면 다음과 같이 생성됩니다.

#include <algorithm>
 
char * f1()
{
        return new char[1024]{0,};
}
 
char * f2()
{
        char *m = new char[1024];
        std::fill_n(m, 1024, 0);
        return m;
}

00000000 <_Z2f1v>:
   0:   e92d4010        push    {r4, lr}
   4:   e3a00b01        mov     r0, #1024       ; 0x400
   8:   ebfffffe        bl      0 <_Znaj>
   c:   e3a01000        mov     r1, #0
  10:   e1a04000        mov     r4, r0
  14:   e59f2010        ldr     r2, [pc, #16]   ; 2c <_Z2f1v+0x2c>
  18:   e4c01001        strb    r1, [r0], #1
  1c:   ebfffffe        bl      0 <memset>      ;;;;;;;;;;;;;;; memset을 호출
  20:   e1a00004        mov     r0, r4
  24:   e8bd4010        pop     {r4, lr}
  28:   e12fff1e        bx      lr
  2c:   000003ff        .word   0x000003ff
 
00000030 <_Z2f2v>:
  30:   e92d4010        push    {r4, lr}
  34:   e3a00b01        mov     r0, #1024       ; 0x400
  38:   ebfffffe        bl      0 <_Znaj>
  3c:   e1a04000        mov     r4, r0
  40:   e3a01000        mov     r1, #0
  44:   e3a02b01        mov     r2, #1024       ; 0x400
  48:   ebfffffe        bl      0 <memset>      ;;;;;;;;;;;;;;; memset을 호출
  4c:   e1a00004        mov     r0, r4
  50:   e8bd4010        pop     {r4, lr}
  54:   e12fff1e        bx      lr

따라서 memset을 호출하거나 하지 않는 것이 성능에 critical한 영향을 준다면 명시적으로 호출하시고, 그렇지 않을 경우에는 가독성을 고려하셔서 짜시는 것이 좋을 것으로 보입니다.

지나가는사람의 이미지

일단 memset은 어느경우라도 가장 빠르게 처리 할겁니다. 이미 오랜시간에 거쳐서 최적화가 되어온 코드이고, 메모리 구조가 아예 뒤집어져서 하나의 메모리가 CPU를 거치는게 아니라 독자적 ALU를 가지고 처리하도록 메모리 구조를 바꾸는 경우가 아니라면야... memset이 아무래도 빠를수 밖에 없을 겁니다.

위의 3개중 2번째것을 제외한 1, 3번은 동일하다고 보셔도 상관 없을 것입니다. 이미 연산량이 동일한 형태로 while이냐 for냐의 차이정도의 루프를 사용하고 있을 뿐이고, 그것을 제외하면 메모리 접근 방식자체는 거의 동일한 형태게 될 것으로 보여지기 때문에요.

2번의 경우는 가장 느릴 것으로 예측이 되는게, 1번의 경우 접근 방식이 for의 경우 배열의 index접근 방법이 될 것이고, 3번은 주소값 증가를 통한 접근 방식을 하기 위해서 1회 이상의 연산을 요구하지 않습니다. 그러나 2번의 경우에는 오직 루프 횟수 계산을 위해서 1회 연산을 소모하고, 주소값 증가연산을 위하여 다시 1회 연산을 추가로 하는 방식으로 되어 있어서, 매 루프당 2회의 연산을 당연히 요구하게 될 겁니다.

그래서 2번이 제 생각에는 가장 느릴 것 같습니다.

이래나 저래나 제가 봤을때, memset을 제외한 전체 속도는 크게 차이나지 않을 것같아요.
저도 지오메트리 정보 관련해서 다루다 보니 메모리 탐색이 중요한지라 빠른 방법을 찾기위해서 이것저것 사용해본 결과 memset보다 빠른 방법은 본적도 없고 제 능력으로는 그런 수준의 코드까지 만들기 어려울것 같더군요.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.