c++ iterator_traits - CPU주기 수를 얻으시겠습니까?




implementation difference_type (5)

Windows의 경우 Visual Studio는 RDTSC 명령어를 실행하고 결과를 다시 제공하는 편리한 "컴파일러 내장 함수"(즉 컴파일러가 이해하는 특수 함수)를 제공합니다.

unsigned __int64 __rdtsc(void);

나는 최신 CPU를 얻기 위해 C 코드가 들어있는이 게시물을 보았다 Cycle count :

C / C ++ Linux x86_64에서의 CPU 사이클 카운팅 기반 프로파일 링

C ++ (Windows 및 Linux 솔루션 환영)에서이 코드를 사용할 수있는 방법이 있습니까? C로 작성되었지만 (C는 C ++의 하위 집합 임)이 코드가 C ++ 프로젝트에서 작동하는지 여부와 그렇지 않은 경우 어떻게 번역 할 것인가에 대해서는 확실치 않습니다.

나는 x86-64를 사용하고있다.

EDIT2 :

이 함수를 찾았지만 VS2010에서 어셈블러를 인식 할 수 없습니다. 아무것도 포함해야합니까? (나는 Windows 용으로 long long uint64_t 를 교환해야한다고 생각합니다 ....)

static inline uint64_t get_cycles()
{
  uint64_t t;
  __asm volatile ("rdtsc" : "=A"(t));
  return t;
}

EDIT3 :

위의 코드에서 오류가 발생합니다.

"오류 C2400 : 'opcode'의 인라인 어셈블러 구문 오류, '데이터 형식'을 발견했습니다"

누군가 제발 도와 줄 수 있니?


VC ++은 인라인 어셈블리에 완전히 다른 구문을 사용합니다. 단 32 비트 버전에서만 가능합니다. 64 비트 컴파일러는 인라인 어셈블리를 전혀 지원하지 않습니다.

이 경우에는 아마 그럴 것입니다. rdtsc 는 (적어도) 타이밍 코드 시퀀스와 관련하여 두 가지 큰 문제가 있습니다. 먼저 (대부분의 명령어와 마찬가지로) 순서가 맞지 않아서 실행될 수 있습니다. 따라서 짧은 코드 시퀀스를 수행하려고하면 그 코드 전후의 rdtsc 가 실행되기 전에 실행되거나 실행 된 후에 실행될 수 있습니다. 당신은 (나는 두 사람이 항상 서로에 관해서 순서대로 행동 할 것이라고 확신합니다. 그래서 적어도 차이는 결코 부정적인 것이 아닙니다.)

둘째, 멀티 코어 (또는 멀티 프로세서) 시스템에서 하나의 rdtsc가 하나의 코어 / 프로세서에서 실행되고 다른 프로세서에서는 다른 코어 / 프로세서에서 실행될 수 있습니다. 이러한 경우에는 부정적인 결과 발생할 수 있습니다.

일반적으로 말하자면, Windows에서 정확한 타이머를 원한다면 QueryPerformanceCounter 사용하는 것이 더 나을 것입니다.

rdtsc 사용을 정말로 주장한다면 어셈블리 언어로 완전히 작성된 (또는 컴파일러 내장 함수를 사용하는) 별도의 모듈에서 수행 한 다음 C 또는 C ++와 링크해야한다고 생각합니다. 필자는이 코드를 64 비트 모드로 작성한 적이 없지만 32 비트 모드에서는 다음과 같이 보입니다.

   xor eax, eax
   cpuid
   xor eax, eax
   cpuid
   xor eax, eax
   cpuid
   rdtsc
   ; save eax, edx

   ; code you're going to time goes here

   xor eax, eax
   cpuid
   rdtsc

이상하게 보입니다.하지만 실제로는 옳습니다. CPUID는 직렬화 명령 (순서대로 실행될 수 없음)과 사용자 모드에서 사용할 수 있으므로 실행합니다. 인텔은 첫 번째 실행이 두 번째 실행 속도와 다른 속도로 실행될 수 있다는 사실을 문서화하기 때문에 (즉, 세 번째는 3 개, 세 번째는 권장 됨), 타이밍을 시작하기 전에 세 번 실행합니다.

그런 다음 테스트중인 코드를 실행하고, 다른 cpuid는 강제로 직렬화하고 마지막 rdtsc는 코드가 완료된 후 시간을 얻습니다.

이와 함께, 여러분은 OS가 제공하는 모든 것을 사용하여이 모든 것을 하나의 프로세스 / 코어에서 실행하도록합니다. 대부분의 경우 코드를 강제로 정렬하려고합니다. 정렬을 변경하면 실행 스피어에 상당한 차이가 발생할 수 있습니다.

마지막으로 여러 번 실행하고 싶을 때가 있습니다. 항상 작업의 중간에 중단 될 가능성이 있으므로 (예 : 작업 전환), 실행의 가능성에 대비해야합니다. 나머지보다 오래 - 예를 들어 5 번 실행하면 40-43 클럭 사이클을, 6 번째 사이클은 10000+ 클럭 사이클이 걸립니다. 분명히 후자의 경우에, 당신은 단지 outlier를 버린다. 그것은 당신의 코드에서 나온 것이 아니다.

요약 : rdtsc 명령어 자체를 관리하는 것은 거의 (거의) 당신의 걱정거리가 아닙니다. rdtsc 에서 결과를 얻을 수 있기 전에 실제로 해야 할 일이 많이 있습니다. 실제로는 의미가 있습니다.


GCC 4.5 이상부터는 __rdtsc() 내장 함수가 MSVC와 GCC에서 모두 지원됩니다.

하지만 필요한 include가 다릅니다.

#ifdef _WIN32
#include <intrin.h>
#else
#include <x86intrin.h>
#endif

다음은 GCC 4.5 이전의 원래 답변입니다.

내 프로젝트 중 하나에서 직접 당겨 :

#include <stdint.h>

//  Windows
#ifdef _WIN32

#include <intrin.h>
uint64_t rdtsc(){
    return __rdtsc();
}

//  Linux/GCC
#else

uint64_t rdtsc(){
    unsigned int lo,hi;
    __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
    return ((uint64_t)hi << 32) | lo;
}

#endif

이 경우 인라인 asm이 필요하지 않습니다 . 이점은 없습니다. 컴파일러에는 rdtscrdtscp 대한 기본 제공 기능이 있으며 (적어도 요즘에는) 올바른 헤더를 포함하는 경우 모두 __rdtsc 내장 함수를 정의합니다. 그러나 거의 모든 다른 경우 ( https://gcc.gnu.org/wiki/DontUseInlineAsm )와 달리, @ Mysticial 's와 같은 안전하고 좋은 구현을 사용하는 한 , asm에 심각한 단점 없습니다. "=A" 제약 조건 .

불행히도 MSVC는 비 - SIMD 내장 함수에 사용할 헤더에 대해 다른 모든 사람들과 동의하지 않습니다.

Intel의 intriniscs 가이드_rdtsc (밑줄이 하나)가 <immintrin.h> 에 있지만 gcc와 clang에서는 작동하지 않는다고 말합니다. <immintrin.h> 에서 SIMD 내장 함수 만 정의하므로 <intrin.h> (MSVC) 대 <x86intrin.h> (최신 ICC를 포함한 모든 것)가 붙어 있습니다. MSVC 및 Intel의 설명서와의 호환성을 위해 gcc 및 clang은 함수의 밑줄 및 밑줄 버전을 모두 정의합니다.

재미있는 사실 : 이중 밑줄 버전은 부호없는 64 비트 정수를 반환하지만 Intel은 _rdtsc()__int64 를 반환 (서명) 한 것으로 문서화합니다.

// valid C99 and C++

#include <stdint.h>  // <cstdint> is preferred in C++, but stdint.h works.

#ifdef _MSC_VER
# include <intrin.h>
#else
# include <x86intrin.h>
#endif

// optional wrapper if you don't want to just use __rdtsc() everywhere
inline
uint64_t readTSC() {
    // _mm_lfence();  // optionally wait for earlier insns to retire before reading the clock
    uint64_t tsc = __rdtsc();
    // _mm_lfence();  // optionally block later instructions until rdtsc retires
    return tsc;
}

// requires a Nehalem or newer CPU.  Not Core2 or earlier.  IDK when AMD added it.
inline
uint64_t readTSCp() {
    unsigned dummy;
    return __rdtscp(&dummy);  // waits for earlier insns to retire, but allows later to start
}

주요 컴파일러 인 gcc / clang / ICC / MSVC (32 비트 또는 64 비트 모두)로 컴파일합니다. 몇 명의 테스트 호출자를 포함 하여 Godbolt 컴파일러 탐색기의 결과를 확인하십시오 .

이러한 내장 함수는 gcc4.5 (2010에서) 및 clang3.5 (2014에서)에서 새로 추가되었습니다 . Godbolt의 gcc4.4와 clang 3.4는 이것을 컴파일하지 않지만 gcc4.5.3 (2011 년 4 월)은 컴파일합니다. 이전 코드에서는 asm을 볼 수 있지만 __rdtsc() 대체 할 수는 있습니다. 10 년 이상 된 컴파일러는 대개 gcc6, gcc7 또는 gcc8보다 느린 코드를 작성하며 유용한 오류 메시지가 적습니다.

MSVC intrinsic은 MSVC가 x86-64 용 인라인 asm을 지원하지 않았기 때문에 훨씬 오래 존재했습니다. ICC13은 __rdtscimmintrin.h 가 있지만 immintrin.h 가 전혀 없습니다. 최근의 ICC는 적어도 GodBolt가 Linux 용으로 설치 한 방식으로 x86intrin.h를 가지고 있습니다.

signed long long 으로 정의 할 수 있습니다 . 특히 빼기를 수행하고 float으로 변환하려는 경우에 특히 유용합니다. int64_t -> float / double은 uint64_t 가없는 x86의 uint64_t 보다 더 효율적입니다. 또한 TSC가 완벽하게 동기화되지 않은 경우 CPU 마이 그 레이션으로 인해 작은 부정적인 결과가 발생할 수 있으며 이는 대용량의 부호없는 숫자보다 더 의미가 있습니다.

BTW, clang에는 모든 아키텍처에서 작동하는 휴대용 __builtin_readcyclecounter() 도 있습니다. (사이클 카운터가없는 아키텍처에서는 항상 0을 반환합니다.) clang / LLVM 언어 확장 문서

lfence (또는 cpuid )를 사용하여 rdtsc 반복성을 향상시키고 순서가 잘못된 명령을 차단하여 시간 제한 간격에있는 명령을 정확히 제어하는 ​​방법에 대한 자세한 내용은 @HadiBrais의 C에서 캐시 라인 무효화 에 대한 응답 함수 및 주석의 차이점을 보여줍니다.

AMD 프로세서에서 LFENCE가 직렬화됩니까?를 참조하십시오 . (TL : Spectre mitigation이 활성화 된 DR 예, 그렇지 않은 경우 커널은 관련 MSR을 설정 해제하여 cpuid 를 사용하여 직렬화해야합니다.) 항상 Intel에서 부분 직렬화로 정의되었습니다.

인텔 ® IA-32 및 IA-64 명령어 세트 아키텍처 에서 코드 실행 시간을 벤치마킹하는 방법 . 2010 년 인텔 백서.

rdtsc 는 CPU 코어 클럭 사이클이 아닌 레퍼런스 주기를 계산합니다.

터보 / 절전과 상관없이 고정 주파수로 계산되므로 uops-per-clock 분석이 필요하면 성능 카운터를 사용하십시오. rdtsc 는 벽시계 시간과 정확하게 관련이 있습니다 (시스템 클럭 조정을 제외하고는 steady_clock의 완벽한 시간 소스입니다). CPU의 정격 주파수, 즉 광고 된 스티커 주파수를 틱합니다. (또는 거의 그것, 예를 들면 i7-6700HQ 2.6 GHz skylake의 2592 MHz).

마이크로 벤치마킹에 사용하는 경우, 워밍업 기간을 먼저 포함하여 타이밍을 시작하기 전에 CPU가 이미 최대 클럭 속도에 있는지 확인하십시오. (그리고 선택적으로 터보를 끄고 마이크로 벤치 마크 중 CPU 주파수가 바뀌지 않도록 최대 클럭 속도를 선호하도록 OS에 알려주십시오.) 또는 하드웨어 성능 카운터에 액세스 할 수있는 라이브러리를 사용하거나 시간 영역이 충분히 길면 perf stat -p PID 첨부 할 수 있는 프로그램 부분에 대한 perf stat 와 같은 트릭을 사용하십시오.

메모리 뱅크 (memory-bound) 또는 기타 무엇이든지간에 다른로드가 Skylake를 클럭시키는 방법을보고 싶지 않다면 일반적으로 마이크로 벤치 마크를 위해 CPU 클럭을 고정시키고 싶을 것입니다. (메모리 대역폭 / 대기 시간은 코어와는 다른 클럭을 사용하여 대부분 고정되어 있습니다. 유휴 클럭 속도에서는 L2 또는 L3 캐시 미스가 코어 클럭 사이클보다 훨씬 적습니다.)

또한 모든 코어의 TSC가 동기화되어 있다는 보장은 없습니다 . 따라서 스레드가 __rdtsc() 사이의 다른 CPU 코어로 마이그레이션하는 경우 추가 스큐가 발생할 수 있습니다. (대부분의 OS는 모든 코어의 TSC를 동기화하려고 시도하지만 일반적으로 매우 가깝습니다.) rdtsc 직접 사용하는 경우 프로그램이나 스레드를 코어에 고정하려고합니다 (예 : taskset -c 0 ./myprogram 리눅스에서 taskset -c 0 ./myprogram .

특히 다중 코어 다중 프로세서 환경에서의 CPU TSC 가져 오기 작업Nehalem 이상이 패키지의 모든 코어 (즉, 불변 TSC) 에 대해 TSC를 동기화 및 잠근 상태 라고 말합니다. 그러나 다중 소켓 시스템은 여전히 ​​문제가 될 수 있습니다. 이전 시스템 (예 : Core2 이전의 2007)은 코어 클럭이 중지되거나 참조주기 대신 실제 코어 클럭 주파수에 연결될 때 중지되는 TSC를 가질 수 있습니다. (최신 CPU는 항상 constant-TSC와 non-stop-TSC를가집니다.) 자세한 내용은 해당 질문에 대한 @ amdn의 대답을 참조하십시오.

asm이 intrinsic을 사용하는 것이 얼마나 좋은가?

이것은 @ Mysticial의 GNU C 인라인 비주얼 (asline asm)에서 얻은 것만 큼 좋거나 RAX의 상위 비트가 0임을 알고 있기 때문에 더 좋습니다. 인라인 asm을 유지하려는 주된 이유는 피곤한 오래된 컴파일러와의 호환성 때문입니다.

readTSC 함수의 인라인이 아닌 버전은 다음과 같이 x86-64 용 MSVC로 컴파일됩니다.

unsigned __int64 readTSC(void) PROC                             ; readTSC
    rdtsc
    shl     rdx, 32                             ; 00000020H
    or      rax, rdx
    ret     0
  ; return in RAX

edx:eax 에 64 비트 정수를 반환하는 32 비트 호출 규칙의 경우 rdtsc / ret 입니다. 중요한 것은 아니지만, 항상 인라인해야합니다.

테스트 호출자가 두 번 사용하고 간격을 뺍니다.

uint64_t time_something() {
    uint64_t start = readTSC();
    // even when empty, back-to-back __rdtsc() don't optimize away
    return readTSC() - start;
}

4 개의 모든 컴파일러는 꽤 비슷한 코드를 만듭니다. 이것은 GCC의 32 비트 출력입니다.

# gcc8.2 -O3 -m32
time_something():
    push    ebx               # save a call-preserved reg: 32-bit only has 3 scratch regs
    rdtsc
    mov     ecx, eax
    mov     ebx, edx          # start in ebx:ecx
      # timed region (empty)

    rdtsc
    sub     eax, ecx
    sbb     edx, ebx          # edx:eax -= ebx:ecx

    pop     ebx
    ret                       # return value in edx:eax

이것은 MSVC의 x86-64 출력입니다 (이름 축소가 적용됨). gcc / clang / ICC는 모두 동일한 코드를 방출합니다.

# MSVC 19  2017  -Ox
unsigned __int64 time_something(void) PROC                            ; time_something
    rdtsc
    shl     rdx, 32                  ; high <<= 32
    or      rax, rdx
    mov     rcx, rax                 ; missed optimization: lea rcx, [rdx+rax]
                                     ; rcx = start
     ;; timed region (empty)

    rdtsc
    shl     rdx, 32
    or      rax, rdx                 ; rax = end

    sub     rax, rcx                 ; end -= start
    ret     0
unsigned __int64 time_something(void) ENDP                            ; time_something

4 개의 컴파일러 모두 lea 대신에 + or mov 사용하여 하위 및 상위 절반을 다른 레지스터로 결합합니다. 나는 그들이 최적화하지 못했던 일종의 통조림 같다고 생각합니다.

그러나 인라인 asm에서 shift / lea를 쓰는 것은 쉽지 않습니다. 컴파일러가 EDX에서 결과의 상위 32 비트를 무시할 기회를 박탈 할 것입니다. 단시간 내에 32 비트 결과 만 유지하면됩니다. 또는 컴파일러가 시작 시간을 메모리에 저장하기로 결정하면 shift / 또는 / mov 대신 두 개의 32 비트 저장소를 사용할 수 있습니다. 타이밍의 일환으로 1 여분의 추가 비용이 발생하면 순수 마이크로 단위로 전체 마이크로 벤치 마크를 작성하는 것이 좋습니다.

그러나 수정 된 버전의 @ Mysticial 's code를 사용하여 두 세계의 장점을 최대한 활용할 수 있습니다.

// More efficient than __rdtsc() in some case, but maybe worse in others
uint64_t rdtsc(){
    // long and uintptr_t are 32-bit on the x32 ABI (32-bit pointers in 64-bit mode), so #ifdef would be better if we care about this trick there.

    unsigned long lo,hi;  // let the compiler know that zero-extension to 64 bits isn't required
    __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
    return ((uint64_t)hi << 32) + lo;
    // + allows LEA or ADD instead of OR
}

Godbolt 에서 gcc / clang / ICC의 경우 __rdtsc() 보다 더 나은 asm을 제공하지만 컴파일러가 추가 레지스터를 사용하여 lo 및 hi를 별도로 저장하도록하는 경우가 많으므로 clang은 ((end_hi-start_hi)<<32) + (end_lo-start_lo) . 실제 레지스터가 있으면 컴파일러가 더 일찍 결합되기를 바랍니다. (gcc와 ICC는 여전히 lo / hi를 별도로 저장하지만 최적화도하지 않습니다.)

하지만 32 비트 gcc8은 혼란 스럽다. 실제로 rdtsc() 함수 자체를 edx의 결과를 반환하는 대신 실제 add/adc 로 컴파일한다. (gcc6 이전 버전에서는 + 대신 + 를 사용하지만 gcc의 32 비트 코드 gen을 사용하는 경우 __rdtsc() 내장 함수를 선호합니다.


오라클COUNT(1)COUNT(*) 의 별칭 일 뿐이라는 증거 가있는 article 가 있습니다.

나는 몇몇 부분을 인용 할 것이다 :

"Optimizer"라는 데이터베이스 소프트웨어의 일부가 공식 문서에서 "SQL 문을 실행하는 가장 효율적인 방법을 결정하는 내장 데이터베이스 소프트웨어"로 정의되어 있습니다.

옵티 마이저의 구성 요소 중 하나는 "변압기"라고 불리는데,이 역할은 원본 SQL 문을보다 효율적으로 의미가 동일한 SQL 문으로 다시 작성하는 것이 유리한 지 여부를 결정하는 것입니다.

COUNT (1)을 사용하여 쿼리를 작성할 때 옵티마이 저가 수행하는 작업을보고 싶습니까?

ALTER SESSION 권한을 가진 사용자는 tracefile_identifier 넣고 옵티 마이저 추적을 활성화하고 다음과 같이 COUNT(1) select를 실행할 수 있습니다. SELECT /* test-1 */ COUNT(1) FROM employees; .

그런 다음 추적 파일을 현지화해야합니다. SELECT VALUE FROM V$DIAG_INFO WHERE NAME = 'Diag Trace'; . 파일의 뒷부분에서 다음을 찾을 수 있습니다.

SELECT COUNT(*) “COUNT(1)” FROM “COURSE”.”EMPLOYEES” “EMPLOYEES”

보시다시피 COUNT(*) 의 별칭 일뿐입니다.

또 다른 중요한 의견 : COUNT(*)20 년 전 Oracle에 비해 Oracle 7.3보다 훨씬 빠릅니다.

오라클이 신화 성명을 자동 조정하기 때문에 Count (1)은 7.3부터 count (*)로 다시 작성되었습니다. 이전 Oracle7에서는 오라클이 DETERMINISTIC 및 NON-DETERMINISTIC이 존재하기 전에 각 행에 대해 (1)을 함수로 평가해야했습니다.

그래서 20 년 전 count (*)가 빨라졌습니다.

SQL Server와 같은 다른 데이터베이스의 경우 각 데이터베이스에 대해 개별적으로 조사해야합니다.

나는이 질문이 SQL Server에 한정된 것을 알고 있지만, 데이터베이스에 언급되지 않은 채 동일한 주제에 관한 다른 질문은이 답변에서 중복 된 것으로 표시하고 표시했습니다.





c++ c performance x86 rdtsc