programing

C에서 "signed int"가 "unsigned int"보다 빠른 이유는 무엇입니까?

shortcode 2023. 7. 22. 12:48
반응형

C에서 "signed int"가 "unsigned int"보다 빠른 이유는 무엇입니까?

C는 왜 서, 그는유입니까?signed int보다 빠른.unsigned int맞습니다. 이 웹 사이트(아래 링크)에서 여러 번 질문하고 답변한 것으로 알고 있습니다.하지만, 대부분의 사람들은 차이가 없다고 말했습니다.코드를 작성했는데 우연히 상당한 성능 차이를 발견했습니다.

코드의 "서명되지 않은" 버전이 "서명된" 버전보다 느린 이유는 무엇입니까(동일한 번호를 테스트하는 경우에도)?(x86-64 Intel 프로세서를 사용하고 있습니다.)

유사 링크

컴파일 명령: gcc -Wall -Wextra -pedantic -O3 -Wl,-O3 -g0 -ggdb0 -s -fwhole-program -funroll-loops -pthread -pipe -ffunction-sections -fdata-sections -std=c11 -o ./test ./test.c && strip --strip-all --strip-unneeded --remove-section=.note --remove-section=.comment ./test


signed int »

참고: 명시적으로 선언해도 차이가 없습니다.signed int모든 숫자로

int isprime(int num) {
    // Test if a signed int is prime
    int i;
    if (num % 2 == 0 || num % 3 == 0)
        return 0;
    else if (num % 5 == 0 || num % 7 == 0)
        return 0;
    else {
        for (i = 11; i < num; i += 2) {
            if (num % i == 0) {
                if (i != num)
                    return 0;
                else
                    return 1;
            }
        }
    }
    return 1;
}

unsigned int »

int isunsignedprime(unsigned int num) {
    // Test if an unsigned int is prime
    unsigned int i;
    if (num % (unsigned int)2 == (unsigned int)0 || num % (unsigned int)3 == (unsigned int)0)
        return 0;
    else if (num % (unsigned int)5 == (unsigned int)0 || num % (unsigned int)7 == (unsigned int)0)
        return 0;
    else {
        for (i = (unsigned int)11; i < num; i += (unsigned int)2) {
            if (num % i == (unsigned int)0) {
                if (i != num)
                    return 0;
                else
                    return 1;
            }
        }
    }
    return 1;
}

다음 코드로 파일을 테스트합니다.

int main(void) {
    printf("%d\n", isprime(294967291));
    printf("%d\n", isprime(294367293));
    printf("%d\n", isprime(294967293));
    printf("%d\n", isprime(294967241)); // slow
    printf("%d\n", isprime(294967251));
    printf("%d\n", isprime(294965291));
    printf("%d\n", isprime(294966291));
    printf("%d\n", isprime(294963293));
    printf("%d\n", isprime(294927293));
    printf("%d\n", isprime(294961293));
    printf("%d\n", isprime(294917293));
    printf("%d\n", isprime(294167293));
    printf("%d\n", isprime(294267293));
    printf("%d\n", isprime(294367293)); // slow
    printf("%d\n", isprime(294467293));
    return 0;
}

결과)time ./test):

Signed - real 0m0.949s
Unsigned - real 0m1.174s

서명되지 않은 버전은 10~20% 느린 코드를 지속적으로 생성하기 때문에 당신의 질문은 정말 흥미롭습니다.하지만 코드에는 여러 가지 문제가 있습니다.

  • 모두 두기이모두반다니됩환능다▁return니▁both됩을 반환합니다.0위해서2,3,5그리고.7그것은 틀렸습니다.
  • 시험은if (i != num) return 0; else return 1;루프 본체는 오직 다음을 위해 실행되기 때문에 완전히 쓸모가 없습니다.i < num이러한 테스트는 소규모 프라임 테스트에 유용하지만 특수 케이싱은 실제로 유용하지 않습니다.
  • 서명되지 않은 버전의 캐스트는 중복됩니다.
  • 할 수 없습니다. 은 터널에텍출벤코신수없다습니뢰할드는미킹치를 . 당신은 다음을 사용해야 합니다.clock()인터벤션 I/O 없이 CPU 집약적인 기능을 실행할 수 있습니다.
  • 가 실행되기 입니다.num / 2 대신에 시간sqrt(num).

코드를 단순화하고 몇 가지 정확한 벤치마크를 실행해 보겠습니다.

#include <stdio.h>
#include <time.h>

int isprime_slow(int num) {
    if (num % 2 == 0)
        return num == 2;
    for (int i = 3; i < num; i += 2) {
        if (num % i == 0)
            return 0;
    }
    return 1;
}

int unsigned_isprime_slow(unsigned int num) {
    if (num % 2 == 0)
        return num == 2;
    for (unsigned int i = 3; i < num; i += 2) {
        if (num % i == 0)
            return 0;
    }
    return 1;
}

int isprime_fast(int num) {
    if (num % 2 == 0)
        return num == 2;
    for (int i = 3; i * i <= num; i += 2) {
        if (num % i == 0)
            return 0;
    }
    return 1;
}

int unsigned_isprime_fast(unsigned int num) {
    if (num % 2 == 0)
        return num == 2;
    for (unsigned int i = 3; i * i <= num; i += 2) {
        if (num % i == 0)
            return 0;
    }
    return 1;
}

int main(void) {
    int a[] = {
        294967291, 0, 294367293, 0, 294967293, 0, 294967241, 1, 294967251, 0,
        294965291, 0, 294966291, 0, 294963293, 0, 294927293, 1, 294961293, 0,
        294917293, 0, 294167293, 0, 294267293, 0, 294367293, 0, 294467293, 0,
    };
    struct testcase { int (*fun)(); const char *name; int t; } test[] = {
        { isprime_slow, "isprime_slow", 0 },
        { unsigned_isprime_slow, "unsigned_isprime_slow", 0 },
        { isprime_fast, "isprime_fast", 0 },
        { unsigned_isprime_fast, "unsigned_isprime_fast", 0 },
    };

    for (int n = 0; n < 4; n++) {
        clock_t t = clock();
        for (int i = 0; i < 30; i += 2) {
            if (test[n].fun(a[i]) != a[i + 1]) {
                printf("%s(%d) != %d\n", test[n].name, a[i], a[i + 1]);
            }
        }
        test[n].t = clock() - t;
    }
    for (int n = 0; n < 4; n++) {
        printf("%21s: %4d.%03dms\n", test[n].name, test[n].t / 1000), test[n].t % 1000);
    }
    return 0;
}

로 입니다.clang -O2OS/X에서 다음 출력을 생성합니다.

         isprime_slow:  788.004ms
unsigned_isprime_slow:  965.381ms
         isprime_fast:    0.065ms
unsigned_isprime_fast:    0.089ms

이러한 타이밍은 다른 시스템에서 OP의 관찰된 동작과 일치하지만, 보다 효율적인 반복 테스트로 인해 발생하는 극적인 개선을 보여줍니다: 10000배 더 빠릅니다!

서명되지 않은 상태에서 기능이 느린 이유는 무엇입니까?라는 질문과 관련하여 생성된 코드(gcc 7.2 - O2)를 살펴보겠습니다.

isprime_slow(int):
        ...
.L5:
        movl    %edi, %eax
        cltd
        idivl   %ecx
        testl   %edx, %edx
        je      .L1
.L4:
        addl    $2, %ecx
        cmpl    %esi, %ecx
        jne     .L5
.L6:
        movl    $1, %edx
.L1:
        movl    %edx, %eax
        ret

unsigned_isprime_slow(unsigned int):
        ...
.L19:
        xorl    %edx, %edx
        movl    %edi, %eax
        divl    %ecx
        testl   %edx, %edx
        je      .L22
.L18:
        addl    $2, %ecx
        cmpl    %esi, %ecx
        jne     .L19
.L20:
        movl    $1, %eax
        ret
       ...
.L22:
        xorl    %eax, %eax
        ret

내부 루프는 매우 유사합니다. 같은 수의 명령어와 비슷한 명령어입니다.그러나 다음은 몇 가지 잠재적인 설명입니다.

  • cltd는 의부를확다니의 합니다.eax에등니다에 합니다.edx 는 명령 지연의 이 될 수.eax에 있는 명령어 바로지의수해정됩니다에 됩니다.movl %edi, %eax하지만 이것은 서명되지 않은 버전보다 서명된 버전을 더 빨리 만들지 않고 더 느리게 만들 것입니다.
  • 루프의 초기 명령어가 서명되지 않은 버전에 대해 잘못 정렬될 수 있지만 소스 코드의 순서를 변경하는 것은 타이밍에 영향을 미치지 않기 때문에 가능성이 낮습니다.
  • 된 division opcode와 되지 않은 division opcode는 하지만, opcode는 opcode, opcode는 opcode는 opcode는 opcode, opcode는 opcode는 opcode는 opcode, opcode는 opcode는 opcode일 가능성이 .idivl명령은 더 적은 주기를 필요로 합니다.divl例명. 되지 않은 가 1% , 이 는 그 가 상당히 커 .실제로 서명된 부서는 서명되지 않은 부서보다 하나의 작은 정밀도로 운영되지만, 이 작은 변화에 대해서는 그 차이가 상당히 커 보입니다.
  • 저는 실리콘 구현에 더 많은 노력을 기울였다고 생각합니다.idivl부호화된 분할이 부호화되지 않은 분할보다 더 흔하기 때문입니다(Intel에서 수년간 코딩 통계로 측정).
  • 인텔 프로세스의 지침 표를 보고 있는 rcgldr이 언급했듯이, 아이비 브리지의 경우 DIV 32비트는 10마이크로ops, 19~27사이클, IDIV 9마이크로ops, 19~26사이클이 걸립니다.벤치마크 시간은 이러한 시간과 일치합니다.추가 마이크로-op은 IDIV(63/31비트)에 비해 DIV(64/32비트)의 피연산자가 길기 때문일 수 있습니다.

이 놀라운 결과는 우리에게 몇 가지 교훈을 줄 것입니다.

  • 최적화하는 것은 어려운 기술입니다. 겸손하고 미루세요.
  • 정확성은 종종 최적화에 의해 깨집니다.
  • 더 나은 알고리즘을 선택하는 것이 최적화보다 훨씬 낫습니다.
  • 항상 벤치마크 코드, 당신의 본능을 믿지 마세요.

부호 있는 정수 오버플로가 정의되지 않았기 때문에 컴파일러는 부호 있는 정수를 포함하는 코드에 대해 많은 가정과 최적화를 할 수 있습니다.부호 없는 정수 오버플로는 감싸도록 정의되므로 컴파일러가 최적화할 수 없습니다.http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html#signed_overflow 및 http://www.airs.com/blog/archives/120 참조하십시오.

AMD/Intel에 대한 지침 사양(K7용):

Instruction Ops Latency Throughput
DIV r32/m32 32  24      23
IDIV r32    81  41      41
IDIV m32    89  41      41 

경우 은 i7의 경우 동일합니다.IDIVL그리고.DIVLµops에 대해 약간의 차이가 있습니다.

이것은 기계에서 -O3 어셈블리 코드가 서명(DIVL 대 IDIVL)에 의해서만 다르기 때문에 차이를 설명할 수 있습니다.

상당한 시간 차이가 나타나지 않을 수도 있는 대체 Wiki 후보 테스트입니다.

#include <stdio.h>
#include <time.h>

#define J 10
#define I 5

int main(void) {
  clock_t c1,c2,c3;
  for (int j=0; j<J; j++) {
    c1 = clock();
    for (int i=0; i<I; i++) {
      isprime(294967241);
      isprime(294367293);
    }
    c2 = clock();
    for (int i=0; i<I; i++) {
      isunsignedprime(294967241);
      isunsignedprime(294367293);
    }
    c3 = clock();
    printf("%d %d %d\n", (int)(c2-c1), (int)(c3-c2), (int)((c3-c2) - (c2-c1)));
    fflush(stdout);
  }
  return 0;
}

샘플 출력

2761 2746 -15
2777 2777 0
2761 2745 -16
2793 2808 15
2792 2730 -62
2746 2730 -16
2746 2730 -16
2776 2793 17
2823 2808 -15
2793 2823 30

실제로 많은 경우 서명되지 않은 것이 서명된 것보다 빠릅니다.

  • 2의 거듭제곱으로 나누면,
    unsigned int x=37;
    cout<<x/4;
  • 짝수인 경우 번호를 확인할 때
    unsigned int x=37;
    cout<<(x%2==0)?"even":"odd";

언급URL : https://stackoverflow.com/questions/34165099/in-c-why-is-signed-int-faster-than-unsigned-int

반응형