programing

C x86의 64비트 루프 성능

yoursource 2023. 8. 15. 15:07
반응형

C x86의 64비트 루프 성능

원시 소켓을 사용하는 일부 IPv4 ICMP 처리 코드에 대한 인터넷 체크섬 기능(보완 체크섬)이 필요했는데 64비트 Intel 프로세서(gcc 4.8.2 사용)에서는 설명할 수 없는 동작을 발견했습니다.누군가 그것을 밝혀줄 수 있는지 궁금했습니다.

32비트 어큐뮬레이터를 사용하여 16비트 합계를 수행하는 첫 번째 체크섬 기능을 구현했습니다.그런 다음 64비트 어큐뮬레이터와 32비트 합계를 사용하여 동일하게 구현했습니다. 합계가 적으면 실행 속도가 빨라질 것이라고 생각했습니다.그 결과 첫 번째 구현이 두 번째 구현보다 두 배 더 빠르게 실행됩니다(O3 최적화 사용).왜 그런지 모르겠어요...

아래 코드는 실제로 정확한 체크섬을 수행하지 않지만(간소화했습니다) 문제를 설명합니다.둘 다 64비트 네이티브 플랫폼(LP64: 짧은 16비트, int 32비트, 긴 64비트, 포인터 64비트)에서 실행되는 64비트로 컴파일되었습니다.

  1. 32비트 어큐뮬레이터 및 16비트 합계

    unsigned short
    cksum_16_le(unsigned char* data, size_t size)
    {
        unsigned short word;
        unsigned int sum = 0;
        unsigned int i;
    
        for(i = 0; i < size - 1; i += 2)
            sum += *((unsigned short*) (data + i));
    
        sum = (sum & 0xffff) + (sum >> 16);
        sum = (sum & 0xffff) + (sum >> 16);
    
        return ~sum;
    }
    

동일한 10k 데이터에 대한 50,000개의 함수 호출: ~1.1초

  1. 64비트 어큐뮬레이터 및 32비트 합계

    unsigned short
    cksum_32_le(unsigned char* data, size_t size)
    {
        unsigned long word;
        unsigned long sum = 0;
        unsigned int i;
    
        for(i = 0; i < size - 3; i += 4)
            sum += *((unsigned int*) (data + i));
    
        sum = (sum & 0xffffffff) + (sum >> 32);
        sum = (sum & 0xffffffff) + (sum >> 32);
        sum = (sum & 0xffff) + (sum >> 16);
        sum = (sum & 0xffff) + (sum >> 16);
    
        return ~sum;
    }
    

동일한 10k 데이터에 대한 50,000개의 함수 호출: ~2.2초

업데이트:

이 문제는 하드웨어 때문인 것 같습니다.실행 중인 메모리 진단에서 때때로 버스 패리티 오류가 발견되었습니다(이 오류가 32비트 버전에 16비트 버전보다 더 많은 영향을 미치는 이유는 확실하지 않지만 여기에 있습니다).코드는 다른 서버에서 예상대로 실행됩니다.몇 시간 내에 질문을 삭제합니다(하드웨어 관련 문제이므로 더 이상 특별히 유용하지 않습니다).

최종 업데이트:

흥미롭게도, 그것을 대체하는 것.for로 루프합니다.while루프 및 O3 최적화를 사용한 컴파일(64비트 어큐뮬레이터 사례의 경우 아래에 표시됨)을 통해 32비트 및 64비트 어큐뮬레이터 사례가 동일한 속도로 실행됩니다.는 컴파일러가 하게도, , 하파상일▁un이▁not▁thisroll▁does그▁un▁performs▁the은▁it▁some것)forloop을 사용하여 합니다.mmx레지스터...

uint64_t sum = 0;
const uint32_t* dptr = (const uint32_t*) data;

while (size > 3)
{
    sum += (uint32_t) *dptr++;
    size -= 4;
}

이전에도 이와 유사한 문제가 있었습니다. 두 코드 모두에서 문제를 찾을 수 없었습니다.하지만 제게 효과가 있었던 것은 컴파일러를 바꾼 것입니다.

제 생각에는 GCC가 더 이상 사용되지 않는 의회를 작성하고 있는 것 같습니다.

응용프로그램을 압축 해제할 수 있다면 문제를 더 자세히 알아볼 수 있지만 여기에 정보가 부족합니다.

코드를 압축 해제해 보니 전체 메서드를 여러 번 다시 쓰고 있었습니다.하지만 그것은 나만을 위한 것일 수도 있습니다.

이것이 도움이 되었기를 바랍니다, 이것에 대한 정보는 어디에도 없습니다.

만약 제가 학습자의 말에 동의할 것이라고 추측해야 한다면, 저는 디컴파일된 코드가 for 루프를 가리킬 것이라고 확신합니다.이 문제에 관심이 많으니 답변 부탁드립니다.

가능한 답변: "i < size - 1" 조건은 "i < size - 3" 조건보다 더 효율적으로 컴파일되고 실행될 수 있습니다.첫 번째는 상수 3을 어딘가에 로드해야 하는 다른 하나 대신 감소 명령만 필요합니다.이 계산은 반복할 때마다 실행됩니다.이 계산 결과는 다른 곳에 저장해야 합니다.

이것은 while 루프와 관련이 없습니다.while 루프를 다시 작성할 때 반복 조건도 변경하여 위의 원인을 제거했습니다.

저는 또한 루프 외부에서 타이프 캐스팅을 하는 것을 선호하지만, 그것은 또한 한 가지 제한을 드러냅니다 - 당신의 데이터는 다음과 같습니다.

당신은 컴파일러의 일을 어렵게 만들고 있습니까?내부 루프에서 인덱스 스트라이드와 캐스트를 선택하여 바이트 오프셋을 계산합니다.이로 인해 루프가 해제되지 않거나 정렬을 가정하는 다른 최적화가 수행되지 않을 수 있습니다.또한 컴파일러가 주소 지정 모드를 사용하고 유효 주소 자체(또는 LEA it)를 계산하기 위해 나가는 것을 허용하지 않을 수 있습니다.

만약 내가 이것을 하고 있다면, 나는 루프의 맨 위에 있는 데이터 포인터를 당신의 스트라이드 유형에 캐스팅하고 당신의 루프 카운터를 1만큼 늘렸을 것입니다.컴파일러가 조금 더 행복할 수도 있습니다.

char*에서 unsigned int *로 캐스팅되었기 때문에 "for" 루프를 풀 수 없다고 생각합니다.이 경우 완벽한 별칭 분석을 수행할 수 없기 때문에 컴파일러가 코드를 최적화하지 못하는 경우가 많습니다.루프 앞에 추가 로컬 포인터를 선언하여 루프에 캐스트가 없도록 하려면 컴파일러는 "for" 루프를 최적화할 수 있어야 합니다.

sum += *((unsigned int*) (data + i)); 

저는 그런 출연진을 좋아하지 않습니다.

64비트 어큐뮬레이터 및 32비트 합계

당신이 쓴 이후로:

둘 다 64비트 네이티브 플랫폼(LP64: 짧은 16비트, int 32비트, 긴 > 64비트, 포인터 64비트)에서 실행되는 64비트로 컴파일되었습니다.

(서명되지 않은 long*)을 사용할 것을 제안합니다.어떤 사람들은 실제로 무슨 일이 일어나고 있는지 분해된 코드를 확인하라고 조언했습니다.긴 축전지를 가진 당신의 int* 캐스트 때문인 것 같습니다.

O2<>O3 플래그가 없는 건 어때요?정상 컴파일 모드에서 속도가 얼마나 되는지 보여주시겠습니까?

언급URL : https://stackoverflow.com/questions/21654207/c-64-bit-loop-performance-on-x86

반응형