% 연산자보다 빠른 분할 성 테스트?


23

컴퓨터에서 궁금한 것을 발견했습니다. * 필기 분할 성 테스트는 %작업자 보다 훨씬 빠릅니다 . 최소한의 예를 고려하십시오.

* AMD Ryzen Threadripper 2990WX, GCC 9.2.0

static int divisible_ui_p(unsigned int m, unsigned int a)
{
    if (m <= a) {
        if (m == a) {
            return 1;
        }

        return 0;
    }

    m += a;

    m >>= __builtin_ctz(m);

    return divisible_ui_p(m, a);
}

이 예는 홀수 a및 로 제한됩니다 m > 0. 그러나, 그것은 쉽게 모든 일반화 될 수 am. 이 코드는 나누기를 일련의 추가로 변환합니다.

이제 다음으로 컴파일 된 테스트 프로그램을 고려하십시오 -std=c99 -march=native -O3.

    for (unsigned int a = 1; a < 100000; a += 2) {
        for (unsigned int m = 1; m < 100000; m += 1) {
#if 1
            volatile int r = divisible_ui_p(m, a);
#else
            volatile int r = (m % a == 0);
#endif
        }
    }

... 내 컴퓨터의 결과 :

| implementation     | time [secs] |
|--------------------|-------------|
| divisible_ui_p     |    8.52user |
| builtin % operator |   17.61user |

따라서 2 배 이상 빠릅니다.

질문 : 코드가 컴퓨터에서 어떻게 동작하는지 알려줄 수 있습니까? GCC에서 최적화 기회를 놓쳤습니까? 이 시험을 더 빨리 할 수 ​​있습니까?


업데이트 : 요청에 따라 최소한의 재현 가능한 예는 다음과 같습니다.

#include <assert.h>

static int divisible_ui_p(unsigned int m, unsigned int a)
{
    if (m <= a) {
        if (m == a) {
            return 1;
        }

        return 0;
    }

    m += a;

    m >>= __builtin_ctz(m);

    return divisible_ui_p(m, a);
}

int main()
{
    for (unsigned int a = 1; a < 100000; a += 2) {
        for (unsigned int m = 1; m < 100000; m += 1) {
            assert(divisible_ui_p(m, a) == (m % a == 0));
#if 1
            volatile int r = divisible_ui_p(m, a);
#else
            volatile int r = (m % a == 0);
#endif
        }
    }

    return 0;
}

gcc -std=c99 -march=native -O3 -DNDEBUGAMD Ryzen Threadripper 2990WX에서 컴파일

gcc --version
gcc (Gentoo 9.2.0-r2 p3) 9.2.0

UPDATE2 : 같이, 어떤 처리 할 수있는 버전 요청 am(테스트 입력 정수로 두 배 긴 정수 유형으로 구현 될 필요가 있습니다 당신은 또한 오버 플로우 정수 피하려면를)

int divisible_ui_p(unsigned int m, unsigned int a)
{
#if 1
    /* handles even a */
    int alpha = __builtin_ctz(a);

    if (alpha) {
        if (__builtin_ctz(m) < alpha) {
            return 0;
        }

        a >>= alpha;
    }
#endif

    while (m > a) {
        m += a;
        m >>= __builtin_ctz(m);
    }

    if (m == a) {
        return 1;
    }

#if 1
    /* ensures that 0 is divisible by anything */
    if (m == 0) {
        return 1;
    }
#endif

    return 0;
}

의견은 긴 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
Samuel Liew

또한 r계산 하는 두 가지 가 실제로 서로 같다고 주장하는 테스트를보고 싶습니다 .
Mike Nakis

@MikeNakis 방금 추가했습니다.
DaBler

2
대부분의 실제 용도는 a % bb보다 훨씬 작다 a. 테스트 케이스에서 대부분의 반복을 통해 비슷한 크기이거나 b더 크며, 이러한 상황에서 많은 CPU에서 버전이 더 빠를 수 있습니다.
Matt Timmermans

답변:


11

고가의 작업을 일련의 저렴한 작업으로 교체하는 것을 힘 감소라고합니다.

많은 CPU의 mod 명령어는 역사적으로 몇 가지 일반적인 벤치 마크에서 테스트되지 않았기 때문에 디자이너가 다른 명령어를 대신 최적화했기 때문에 속도가 느립니다. 이 알고리즘은 많은 반복을 수행해야하는 경우 성능이 저하됩니다.% 수행해야하는 클럭 사이클이 2 개만 필요한 CPU에서는 성능이 향상됩니다.

마지막으로, 특정 상수로 나눈 나머지를 가져 오는 많은 지름길이 있음에 유의하십시오. (컴파일러가 일반적으로이를 처리하지만)


역사적으로 몇 가지 일반적인 벤치 마크에서 테스트되지 않았습니다. 또한 부서는 본질적으로 반복적이고 빠르기 어렵 기 때문입니다! 86 적어도의 일환으로 나머지를 수행 div/ idiv인텔 펜린 (Penryn), 브로드 웰과 IceLake (높은 기수 하드웨어 디바이더)에서 사랑을 좀 찍었
피터 코르

1
"강도 감소"의 나의 이해는 단일 가벼운 동작 루프, 예를 들어, 대신에 무거운 작업을 대체 할 것입니다 x = i * const당신이 매일 반복 x += const마다 반복. 단일 곱셈을 시프트 / 추가 루프로 바꾸는 것을 강도 감소라고 생각하지 않습니다. en.wikipedia.org/wiki/…에 따르면이 용어는 이런 식으로 사용될 수 있지만 "이 자료는 논쟁의 여지가 있습니다.이 부분은 구멍 최적화 및 명령 할당으로 더 잘 설명됩니다."
Peter Cordes

9

나는 내 질문에 스스로 대답 할 것이다. 내가 지점 예측의 희생자가 된 것 같습니다. 피연산자의 상호 크기는 중요하지 않으며 순서 만 중요합니다.

다음 구현을 고려하십시오

int divisible_ui_p(unsigned int m, unsigned int a)
{
    while (m > a) {
        m += a;
        m >>= __builtin_ctz(m);
    }

    if (m == a) {
        return 1;
    }

    return 0;
}

그리고 배열

unsigned int A[100000/2];
unsigned int M[100000-1];

for (unsigned int a = 1; a < 100000; a += 2) {
    A[a/2] = a;
}
for (unsigned int m = 1; m < 100000; m += 1) {
    M[m-1] = m;
}

셔플 기능을 사용하여 셔플 되지 않습니다 .

셔플 링 없이도 결과는 여전히

| implementation     | time [secs] |
|--------------------|-------------|
| divisible_ui_p     |    8.56user |
| builtin % operator |   17.59user |

그러나 일단 이러한 배열을 섞으면 결과가 다릅니다.

| implementation     | time [secs] |
|--------------------|-------------|
| divisible_ui_p     |   31.34user |
| builtin % operator |   17.53user |
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.