programing

memset과 루프가 복수의 배열을 영점화하는 것 중 어느 것이 더 빠르거나 더 좋습니까?

javamemo 2023. 11. 1. 21:55
반응형

memset과 루프가 복수의 배열을 영점화하는 것 중 어느 것이 더 빠르거나 더 좋습니까?

double d[10];
int length = 10;

memset(d, length * sizeof(double), 0);

//or

for (int i = length; i--;)
  d[i] = 0.0;

당신이 정말 신경 쓴다면 당신은 시도하고 측정해야 합니다.그러나 가장 휴대하기 쉬운 방법은 std::fill():

std::fill( array, array + numberOfElements, 0.0 );

memset의 경우, 이전의 C 함수이기 때문에 요소의 개수가 아닌 바이트의 개수를 전달해야 합니다.

memset(d, 0, sizeof(double)*length);

memset은 assembler로 작성되기 때문에 더 빠를 수 있지만, 반면에std::fill는 단순히 내부적으로 루프를 수행하는 템플릿 함수입니다.

하지만 타입 안전과 좀 더 읽을 수 있는 코드를 위해서 추천합니다. std::fill()- 그것은 일을 하는 c++ 방식이고, 고려합니다.memset성능 최적화가 필요한 경우 코드에 포함됩니다.

멋지기 위해서라도 한번 써보세요 xD

{
    double *to = d;
    int n=(length+7)/8;
    switch(length%8){
        case 0: do{ *to++ = 0.0;
        case 7:     *to++ = 0.0;
        case 6:     *to++ = 0.0;
        case 5:     *to++ = 0.0;
        case 4:     *to++ = 0.0;
        case 3:     *to++ = 0.0;
        case 2:     *to++ = 0.0;
        case 1:     *to++ = 0.0;
        }while(--n>0);
    }
}

루프 길이가 적분 상수 표현이라고 가정하면, 가장 가능성 있는 결과는 좋은 최적화자가 for-loop과 memset(0)을 모두 인식할 것입니다.결과적으로 생성된 어셈블리는 본질적으로 동일합니다.레지스터의 선택이 다를 수도 있고 설정이 다를 수도 있습니다.하지만 두 배당 한계비용은 정말로 동일해야 합니다.

코드에 있는 여러 버그와 누락 외에 memset을 사용하는 것은 휴대가 불가능합니다.모든 0비트가 있는 두 배가 0.0이라고 가정할 수는 없습니다.먼저 코드를 올바르게 설정한 다음 최적화에 대해 고민합니다.

memset(d,0,10*sizeof(*d));

더 빠를 것 같습니다.당신도 할 수 있다고 하는 것처럼

std::fill_n(d,10,0.);

루프를 하는게 더 예쁜 방법일거에요

calloc(length, sizeof(double))

IEEE-754에 따르면, 양의 0의 비트 표현은 모두 0 비트이며, IEEE-754의 준수를 요구하는 것은 문제가 없습니다.(다시 사용하기 위해 어레이를 제로화해야 할 경우 위의 솔루션 중 하나를 선택합니다.)

IEEE 754-1975 64비트 부동 소수점에 대한 위키피디아 기사에 따르면 모든 0의 비트 패턴은 실제로 두 배를 0.0으로 적절하게 초기화합니다.안타깝게도 당신의 멤셋 코드는 그렇게 하지 못합니다.

사용해야 할 코드는 다음과 같습니다.

memset(d, 0, length * sizeof(double));

좀 더 완벽한 패키지의 일부로...

{
    double *d;
    int length = 10;
    d = malloc(sizeof(d[0]) * length);
    memset(d, 0, length * sizeof(d[0]));
}

물론, malloc의 반품 값에 대한 오류 검사를 수행해야 합니다.sizeof(d[0])보다 약간 낫습니다.sizeof(double)d형의 변화에 강하기 때문입니다.

그리고 만약 여러분이calloc(length, sizeof(d[0]))그러면 메모리가 지워지고 그 이후의 멤셋은 더 이상 필요 없게 됩니다.예문에 사용하지 않은 것은 당신의 질문에 답이 없을 것 같아서 입니다.

Memset은 디버그 모드 또는 낮은 수준의 최적화를 사용하는 경우 항상 더 빠릅니다.더 높은 최적화 수준에서는 std::fill 또는 std::fill_n과 동일합니다.예를 들어, Google 벤치마크의 다음 코드의 경우: (테스트 설정: xubuntu 18, GCC 7.3, Clang 6.0)

#include <cstring>
#include <algorithm>
#include <benchmark/benchmark.h>

double total = 0;


static void memory_memset(benchmark::State& state)
{
    int ints[50000];

    for (auto _ : state)
    {
        std::memset(ints, 0, sizeof(int) * 50000);
    }

    for (int counter = 0; counter != 50000; ++counter)
    {
        total += ints[counter];
    }
}


static void memory_filln(benchmark::State& state)
{
    int ints[50000];

    for (auto _ : state)
    {
        std::fill_n(ints, 50000, 0);
    }

    for (int counter = 0; counter != 50000; ++counter)
    {
        total += ints[counter];
    }
}


static void memory_fill(benchmark::State& state)
{
    int ints[50000];

    for (auto _ : state)
    {
        std::fill(std::begin(ints), std::end(ints), 0);
    }

    for (int counter = 0; counter != 50000; ++counter)
    {
        total += ints[counter];
    }
}


// Register the function as a benchmark
BENCHMARK(memory_filln);
BENCHMARK(memory_fill);
BENCHMARK(memory_memset);



int main (int argc, char ** argv)
{
    benchmark::Initialize (&argc, argv);
    benchmark::RunSpecifiedBenchmarks ();
    printf("Total = %f\n", total);
    getchar();
    return 0;
}

GCC(-O2;-march= native)의 릴리스 모드에서 다음 결과를 제공합니다.

-----------------------------------------------------
Benchmark              Time           CPU Iterations
-----------------------------------------------------
memory_filln       16488 ns      16477 ns      42460
memory_fill        16493 ns      16493 ns      42440
memory_memset       8414 ns       8408 ns      83022

그리고 다음과 같이 디버그 모드(-O0)가 됩니다.

-----------------------------------------------------
Benchmark              Time           CPU Iterations
-----------------------------------------------------
memory_filln       87209 ns      87139 ns       8029
memory_fill        94593 ns      94533 ns       7411
memory_memset       8441 ns       8434 ns      82833

-O3에 있거나 -O2에 클랜이 있는 동안 다음을 얻을 수 있습니다.

-----------------------------------------------------
Benchmark              Time           CPU Iterations
-----------------------------------------------------
memory_filln        8437 ns       8437 ns      82799
memory_fill         8437 ns       8437 ns      82756
memory_memset       8436 ns       8436 ns      82754

TLDR: 적어도 IEEE-754 부동 소수점이 아닌 POD 유형에 대해 std::fill 또는 for-loop을 사용해야 한다고 절대적으로 말하지 않는 한 memset을 사용합니다.하지 않을 강력한 이유는 없습니다.

(참고: 배열 내용을 계산하는 for loops는 클랜이 구글 벤치마크 루프를 완전히 최적화하지 않기 위해 필요합니다(그렇지 않으면 사용되지 않음을 감지합니다).

어레이에 메모리를 할당해야 하므로 예제가 작동하지 않습니다.스택 또는 힙에서 이 작업을 수행할 수 있습니다.

스택에서 이를 수행하는 예는 다음과 같습니다.

double d[50] = {0.0};

그 이후에는 멤셋이 필요 없습니다.

성능에 정말 관심이 있다면 루프에 적절하게 최적화된 제품을 비교하는 것을 잊지 마십시오.

배열이 충분히 길다면 Duff의 장치의 일부 변형, 접두사 --i not supplus i-- (대부분의 컴파일러가 자동으로 수정할 것입니다.)

이것이 최적화하기에 가장 가치 있는 것인지 의문이 들지만 말입니다.이것이 정말 시스템의 병목 현상입니까?

memset(d, 10, 0)은 10바이트만 널링하므로 올바르지 않습니다.prefer std::fill은 의도가 가장 명확하기 때문입니다.

일반적으로 멤셋은 훨씬 더 빨라질 것입니다. 길이를 정확히 맞추십시오. 분명히 예제에서 복식 배열을 할당하거나 정의하지 않았습니다.이제 정말로 몇 개의 2배로 끝나려면 루프가 더 빨라질 수 있습니다.그러나 채우기 루프가 그림자를 드리우는 지점에 도달함에 따라 소수의 설정 명령어 멤셋은 일반적으로 속도를 최대화하기 위해 더 크고 때로는 정렬된 청크를 사용합니다.

평소와 같이 시험하고 측정합니다.(그러나 이 경우에는 캐시에 저장되어 측정값이 거짓으로 판명될 수도 있습니다.)

이 질문에 답하는 한 가지 방법은 컴파일러 탐색기를 통해 코드를 빠르게 실행하는 것입니다.링크를 확인하면 다음 코드에 대한 어셈블리가 표시됩니다.

void do_memset(std::array<char, 1024>& a) {
    memset(&a, 'q', a.size());
}

void do_fill(std::array<char, 1024>& a) {
    std::fill(a.begin(), a.end(), 'q');
}

void do_loop(std::array<char, 1024>& a) {
    for (int i = 0; i < a.size(); ++i) {
        a[i] = 'q';
    }
}

답은 (적어도 에 대해서는)clang)는 최적화 수준을 가진 것입니다.-O0그리고.-O1, 조립은 다르며,std::fill반복기의 사용이 최적화되지 않았기 때문에 속도가 느려질 것입니다.-O2그리고 더 높은 곳에서do_memset그리고.do_fill동일한 조립체를 제작합니다.루프는 결국 전화를 걸게 됩니다.memset 배열의 모든 항목에 대해 다음과 같은 경우에도-O3.

릴리스 빌드가 실행되는 경향이 있다고 가정합니다.-O2성능에 대한 고려 사항은 없으며 다음을 사용할 것을 권장합니다.std::fill가능할 때, 그리고memsetC에 대하여

STL을 사용하지 말아야 한다면...

double aValues [10];
ZeroMemory (aValues, sizeof(aValues));

ZeroMemory는 적어도 의도를 분명히 합니다.

제안된 모든 것에 대한 대안으로 시작 시 배열을 모든 0으로 설정하지 않는 것을 제안할 수 있습니다.대신 특정 셀의 값에 처음 액세스할 때만 값을 0으로 설정합니다.이렇게 하면 질문을 피할 수 있고 더 빠를 수 있습니다.

당신 말은

memset(d, 0, length * sizeof(d[0]))

그리고.

for (int i = length; --i >= 0; ) d[i] = 0;

개인적으로 둘 중 하나는 하지만, 아마std::fill()아마 더 나을 겁니다.

언급URL : https://stackoverflow.com/questions/1373369/which-is-faster-preferred-memset-or-for-loop-to-zero-out-an-array-of-doubles

반응형