C int 배열을 0으로 재설정 : 가장 빠른 방법?


102

T myarray[100]with T = int, unsigned int, long long int 또는 unsigned long long int 가 있다고 가정하면 모든 내용을 0으로 재설정하는 가장 빠른 방법은 무엇입니까 (초기화뿐만 아니라 내 프로그램에서 내용을 여러 번 재설정) ? 아마도 memset과 함께?

같은 동적 배열에 대한 동일한 질문입니다 T *myarray = new T[100].


16
@BoPersson : 잘 new 되는 C ++ ...
마테오 이탈리아

@Matteo-음, 그래. 답변에 많은 영향을 미치지 않았습니다 (지금까지 :-).
Bo Persson 2012

3
@BoPersson은 : 난 단지에 대해 이야기 나쁜 느낌 memset:) ... C ++ 어떻게 든 관련된 경우
마테오 이탈리아에게

2
최신 컴파일러에서는 단순한 for루프를 이길 수 없습니다 . 그러나 놀랍게도 당신은 똑똑해 지려고 노력함으로써 훨씬 더 나쁜 일을 할 수 있습니다.
David Schwartz

구조체를 사용하고 그 안에 배열을 붙입니다. 모두 0 인 인스턴스를 만듭니다. 그것을 사용하여 당신이 만든 다른 것을 제로화하십시오. 잘 작동한다. 포함, 기능, 매우 빠릅니다.
Xofo

답변:


170

memset(from <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(from <algorithm>) 을 사용하는 것입니다 .

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

어떤 (A) 내로 자동으로 최적화 memset; 나는 확신이 최대한 빨리 작동 할 것이라고 해요 memset위한 int최적화 스마트 충분이 아닌 경우가 더 작은 유형에 대해 약간 더 수행 할 수있는 반면,의. 여전히 의심 스러울 때 프로필.


10
1999 년 ISO C 표준에서는 memset정수가 0으로 설정 된다는 것이 실제로 보장되지 않았습니다 . all-bits-zero가 0. Technical Corrigendum은 2011 ISO C 표준에 포함 된 이러한 보증을 추가했습니다. 저는 all-bits-zero 0 모든 기존 C 및 C ++ 구현의 모든 정수 유형에 대한 유효한 표현 이라고 믿습니다. 이것이위원회가 해당 요구 사항을 추가 할 수 있었던 이유입니다. (부동 소수점 또는 포인터 유형에 대한 유사한 보장은 없습니다.)
Keith Thompson

3
@KeithThompson의 의견에 추가 :이 보증은 TC2 (2004)의 일반 텍스트로 6.2.6.2/5에 추가되었습니다. 그러나 패딩 비트가 없으면 6.2.6.2/1 및 / 2는 이미 all-bits-zero가 0. (패딩 비트를 사용하면 모든 비트 0이 트랩 표현이 될 가능성이 있습니다). 그러나 어쨌든 TC는 결함있는 텍스트를 인정하고 교체해야하므로 2004 년부터는 C99가 항상이 텍스트를 포함하는 것처럼 행동해야합니다.
MM

C에서 동적 배열을 올바르게 할당 하면 두 memset간에 차이가 없습니다. 올바른 동적 할당은입니다 int (*myarray)[N] = malloc(sizeof(*myarray));.
Lundin

@Lundin : 물론-컴파일 시간에 얼마나 큰지 안다면 N대부분의 경우에 사용 malloc했다면 런타임에서만 알 수 있습니다.
Matteo Italia

@MatteoItalia 1999 년 이후로 VLA를 받았습니다.
Lundin

20

그렇지 않은 가장 관용적 인 방법, 또는 라인의 가장 적은 수에 기록 할 수있는 방법,하지만 요청으로이 질문은 오히려 오래된 있지만, 몇 가지 벤치 마크를 필요로 가장 빠른 방법입니다. 그리고 실제 테스트없이 그 질문에 대답하는 것은 어리석은 일입니다. 그래서 저는 memset 대 std :: fill 대 AnT의 답변의 ZERO 대 AVX 내장 함수를 사용하여 만든 솔루션의 네 가지 솔루션을 비교했습니다.

이 솔루션은 일반적이지 않으며 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;\
};\
}\
}

저수준 최적화 전문가가 아니기 때문에 이것이 가장 빠른 방법이라고 주장하지 않겠습니다. 오히려 그것은 memset보다 빠른 올바른 아키텍처 종속 구현의 예입니다.

이제 결과에. 정적으로 및 동적으로 할당 된 크기 100 int 및 long long 배열의 성능을 계산했지만, 정적 배열에서 데드 코드 제거를 수행 한 msvc를 제외하고는 결과가 매우 비슷했기 때문에 동적 배열 성능 만 보여 드리겠습니다. time.h의 낮은 정밀도 시계 기능을 사용하여 시간 표시는 1 백만 회 반복에 대한 ms입니다.

clang 3.8 (clang-cl 프런트 엔드 사용, 최적화 플래그 = / OX / arch : AVX / Oi / Ot)

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 = native -mtune = native -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 / arch : AVX / Oi / Ot) :

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

여기에는 llvm killing gcc, MSVC의 일반적인 반점 최적화 (정적 배열에서 인상적인 데드 코드 제거를 수행 한 다음 채우기 성능이 끔찍함)가 있습니다. 내 구현이 훨씬 빠르지 만 비트 지우기가 다른 설정 작업보다 훨씬 적은 오버 헤드를 가지고 있음을 인식하기 때문일 수 있습니다.

Clang의 구현은 훨씬 더 빠르기 때문에 더 많이 볼 가치가 있습니다. 일부 추가 테스트에서는 memset이 실제로 0에 특화되어 있음을 보여줍니다 .400 바이트 배열의 경우 0이 아닌 memset은 훨씬 느리고 (~ 220ms) gcc와 비슷합니다. 그러나 800 바이트 배열을 사용하는 0이 아닌 memsetting은 속도 차이가 없습니다. 이것이 아마도이 경우 memset이 내 구현보다 성능이 떨어지는 이유 일 것입니다. 전문화는 작은 배열에만 해당되며 컷오프는 800 바이트 정도입니다. 또한 gcc 'fill'및 'ZERO'는 memset (생성 된 코드보기)에 최적화되지 않으며 gcc는 단순히 동일한 성능 특성을 가진 코드를 생성합니다.

결론 : memset은 실제로이 작업에 최적화되어 있지 않으며 사람들이 그렇게 생각할 것입니다 (그렇지 않으면 gcc와 msvc 및 llvm의 memset의 성능이 동일합니다). 성능이 중요하다면 memset은 특히 이러한 어색한 중간 크기의 배열에 대한 최종 솔루션이되어서는 안됩니다. 이는 비트 지우기에 특화되지 않았고 컴파일러가 자체적으로 수행 할 수있는 것보다 더 잘 최적화되지 않았기 때문입니다.


4
코드가없고 컴파일러 버전과 사용 된 옵션에 대한 언급이없는 벤치 마크? 흠 ...
Marc Glisse 2015 년

나는 이미 컴파일러 버전을 가지고 있었고 (조금 숨겨져 있었음) 사용 가능한 옵션을 추가했습니다.
Benjamin

단항 '*'의 잘못된 형식 인수 ( 'size_t {aka unsigned int}'포함) |
Piotr Wasilewicz

최적화 된 제로화 방법을 작성하는 데 너무 관대합니다. 작동 원리와 더 빠른 이유에 대해 몇 마디 만 아껴 주시겠습니까? 코드는 모두 자명합니다.
Motti Shneor

1
@MottiShneor 그것은 그것보다 더 복잡해 보입니다. AVX 레지스터의 크기는 32 바이트입니다. 그래서 그는 a레지스터 에 맞는 값의 수를 계산합니다 . 그 후, 그는 모든 32 바이트 블록을 반복하며 포인터 산술 ( (float *)((a)+x))을 사용하여 완전히 덮어 써야합니다 . 두 가지 내장 함수 (로 시작 _mm256)는 0으로 초기화 된 32 바이트 레지스터를 만들고 현재 포인터에 저장합니다. 이것은 처음 3 줄입니다. 나머지는 마지막 32 바이트 블록을 완전히 덮어 쓰지 않아야하는 모든 특수한 경우를 처리합니다. 벡터화로 인해 더 빠릅니다. -도움이 되었기를 바랍니다.
wychmaster

11

에서 memset():

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

컴파일 타임에 sizeof(myarray)의 크기를 myarray알고 있으면 사용할 수 있습니다 . 당신은을 통해 얻은 같은 동적 크기의 배열을 사용하는 경우 그렇지 않은 경우, malloc또는 new, 당신은 길이를 추적해야합니다.


2
sizeof는 컴파일 타임에 배열의 크기를 알 수없는 경우에도 작동합니다. (물론 배열 일 때만)
asaelr

2
@asaelr : C ++에서는 sizeof항상 컴파일 타임에 평가됩니다 (VLA와 함께 사용할 수 없음). C99에서는 VLA의 경우 런타임 표현식이 될 수 있습니다.
Ben Voigt

잘 @BenVoigt, 질문 모두에 관한 것입니다 cc++. 나는 Alex의 답변에 대해 "컴파일시에 myarray의 크기를 알면 sizeof (myarray)를 사용할 수 있습니다"라고 언급했습니다.
asaelr

2
@asaelr : 그리고 C ++에서는 완전히 맞습니다. 귀하의 의견은 C99 또는 VLA에 대해 아무 말도하지 않았기 때문에 명확히하고 싶었습니다.
Ben Voigt

5

를 사용할 수 memset있지만 유형 선택이 정수 유형으로 제한되기 때문입니다.

일반적으로 C의 경우 매크로를 구현하는 것이 합리적입니다.

#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 ++ 함수 템플릿의 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)관용구의 요점은 ;매크로 정의에서 필요하지 않습니다 . 결정된.
AnT 2013-06-16

3

정적 선언의 경우 다음을 사용할 수 있다고 생각합니다.

T myarray[100] = {0};

동적 선언의 경우 동일한 방법을 제안합니다. memset


2
질문은 "초기화뿐만 아니라"라고 말합니다.
Ben Voigt

2

zero(myarray); C ++에서 필요한 모든 것입니다.

헤더에 다음을 추가하십시오.

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

1
잘못된 정보입니다. SIZE 바이트가 삭제됩니다. 'memset (arr, 0, SIZE * sizeof (T));' 정확할 것입니다.
Kornel Kisielewicz

트윗 담아 가기 나는 지난 1.5 년 동안 아무도이 함수를 복사-붙여 넣기하지 않기를 바랍니다. :(
Navin

1
구글 :) 여기에 나를 데리고 있기 때문에하지 희망, 나는 주석
코넬 Kisielewicz

1
이 함수 zero는 인수가 다차원 배열 (예 :) 인 T=char[10]경우 에도 정확합니다 . arrchar arr[5][10]
mandrake

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);

위는 memset을 사용하는 것보다 더 많은 C ++ 11 방식입니다. 또한 크기를 지정하여 동적 배열을 사용하면 컴파일 시간 오류가 발생합니다.


원래 질문은 C가 아니라 C에 ++, 따라서 표준 : 채우기 적절한 해답이 될 수 없다
은 Motti Shneor
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.