C++에서 x86_64의 CPU 사이클 카운트를 얻는 방법
SO에서 최신 CPU 사이클 카운트를 얻기 위한 C 코드가 포함된 다음 게시물을 보았습니다.
C/C++ Linux x86_64에서의 CPU 사이클 수 기반 프로파일링
이 코드를 C++로 사용할 수 있는 방법이 있습니까(Windows 및 Linux 솔루션 환영).C로 쓰여져 있습니다만(C++의 서브셋으로 되어 있습니다), 이 코드가 C++프로젝트에서 기능하는지는 잘 모르겠습니다만, 그렇지 않은 경우는 어떻게 번역하면 좋을까요?
x86-64를 사용하고 있습니다.
편집 2:
이 함수를 찾았지만 VS2010이 어셈블러를 인식할 수 없습니다.넣어야 (될것?uint64_t
로로 합니다.long long
Windows®...?)
static inline uint64_t get_cycles()
{
uint64_t t;
__asm volatile ("rdtsc" : "=A"(t));
return t;
}
편집 3:
위의 코드에서 다음 오류가 발생합니다.
"오류 C2400: 'opcode'에 인라인 어셈블러 구문 오류가 발생했습니다. 'data type'을 찾았습니다."
누가 좀 도와주실래요?
GCC 4.5 이후부터는 MSVC와 GCC 양쪽에 의해 내재가 지원되게 되었습니다.
그러나 필요한 내용은 다릅니다.
#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
이 GNU C 확장 asm은 컴파일러에 다음과 같이 통지합니다.
volatile
: 출력은 입력의 순수한 함수가 아닙니다(따라서 이전 결과를 재사용하지 않고 매번 다시 실행해야 합니다)."=a"(lo)
★★★★★★★★★★★★★★★★★」"=d"(hi)
: 출력 오퍼랜드는 고정 레지스터(EAX 및 EDX)입니다(x86 기계 구속).x86은rdtsc
를 EDX 64비트 EDX로 합니다.즉가 EAX로 할 수 있도록 ."=r"
CPU cpu 、 CPU cpu cpu cpu 。((uint64_t)hi << 32) | lo
- 로 제로 는 -32비트이기 64비트)unsigned
논리적으로 + 또는 이들을 단일 64비트 C 변수로 전환합니다.32비트 코드에서는, 이것은 단지 재해석일 뿐입니다.값은 32비트 레지스터의 페어에 머무릅니다.64비트 코드에서는 일반적으로 하이반이 최적화되지 않는 한 실제 시프트 + OR asm 명령을 받습니다.
하시면 더 일 수 .)unsigned long
unsigned int
가 알 수 lo
는 이미 제로 .에 0으로 있습니다|
★★★★★★★★★★★★★★★★★」+
하다이론상 본질은 최적기가 일을 잘 할 수 있도록 하는 한 두 가지 장점을 모두 제공해야 합니다.)
피할 수 있으면 https://gcc.gnu.org/wiki/DontUseInlineAsm을 참조하십시오.단, 인라인 ASM을 사용하는 오래된 코드를 이해해야 하는 경우 이 섹션이 유용하여 내장 함수를 사용하여 다시 작성할 수 있습니다.https://stackoverflow.com/tags/inline-assembly/info 도 참조해 주세요.
x86-64 의 인라인 이 . "=A"
64비트 모드에서는 컴파일러가 EDX가 아닌 RAX 또는 RDX 중 하나를 선택할 수 있습니다.EAX. 자세한 내용은 이 Q&A를 참조하십시오.
여기에는 인라인 ASM이 필요하지 않습니다.이점은 없습니다.컴파일러는rdtsc
★★★★★★★★★★★★★★★★★」rdtscp
현재 (')를 합니다.__rdtsc
올바른 헤더를 포함할 경우 internal을 선택합니다.그러나 다른 거의 모든 사례(https://gcc.gnu.org/wiki/DontUseInlineAsm),와 달리 @Mystical's와 같은 안전하고 좋은 구현을 사용하는 한 asm에 심각한 단점은 없습니다.
(asm의 작은 장점 중 하나는 확실히 2^32 카운트 미만인 작은 인터벌의 시간을 재는 경우 결과의 상위 절반을 무시할 수 있다는 것입니다.컴파일러는 이 최적화 기능을uint32_t time_low = __rdtsc()
본질적이지만 실제로는 여전히 시프트 / OR을 수행하면서 지시를 낭비하는 경우가 있습니다.)
유감스럽게도 MSVC는 비 SIMD 내장 기능에 사용할 헤더에 대해 다른 모든 사용자와 의견이 다릅니다.
인텔의 interiniscs 가이드에 따르면_rdtsc
(1개의 밑줄 포함)이 들어가 있습니다.<immintrin.h>
단, gcc와 clang에서는 동작하지 않습니다.SIMD 내장 함수를 정의하는 것은<immintrin.h>
그래서 우리는<intrin.h>
(MSVC)와<x86intrin.h>
(최근 ICC를 포함한 기타 모든 것).MSVC 및 인텔 문서와의 호환성을 위해 gcc 및 clang은 함수의 1 언더스코어 버전과 2 언더스코어 버전을 모두 정의합니다.
재미있는 사실: 더블 언더스코어 버전에서는 부호 없는 64비트 정수가 반환되며 인텔 문서_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
}
주요 컴파일러 4개(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는 x86-64의 인라인 asm을 지원하지 않기 때문에 MSVC의 본질은 훨씬 더 오래 존재했습니다.ICC13에는__rdtsc
에immintrin.h
, 단, 에는 없습니다.x86intrin.h
조금도.보다 최근의 ICC는x86intrin.h
적어도 Godbolt가 Linux용으로 설치하는 방법처럼 말이죠.
특히 감산 후 부동으로 변환하려는 경우 부호 있음으로 정의할 수 있습니다. int64_t
-> float/double이 보다 효율적입니다.uint64_t
AVX512 미포함x86 。또한 TSC가 완전히 동기화되지 않은 경우 CPU 마이그레이션으로 인해 작은 부정적인 결과가 발생할 수 있습니다. 이는 서명되지 않은 큰 숫자보다 더 합리적일 수 있습니다.
그나저나, 쨍그랑도 휴대할 수 있는__builtin_readcyclecounter()
(사이클 카운터가 없는 아키텍처에서는 항상 0이 반환됩니다.)Clang/LLVM 언어 확장 문서를 참조하십시오.
(또는 )을 사용하여 순서 외 실행을 차단함으로써 시간 간격에 포함되지 않는 명령의 반복성을 향상시키고 정확하게 제어하는 방법에 대한 자세한 내용은 clflush의 @HadiBrais의 답변과 C 함수를 통한 캐시 행의 비활성화 및 그 차이의 예에 대한 주석을 참조하십시오.
AMD 프로세서에서 LFENCE 직렬화가 이루어집니까?(TL:DR yes, Spectre 경감을 사용하도록 설정하면 커널에서 관련 MSR이 설정되지 않은 상태로 유지되므로 사용해야 합니다.cpuid
시리얼화하다)인텔에서는 항상 부분적인 시리얼화로 정의되어 있습니다.
인텔 ® IA-32 및 IA-64 명령 세트 아키텍처의 코드 실행 시간을 벤치마킹하는 방법 (2010년판 인텔 화이트 페이퍼).
rdtsc
CPU 코어 클럭 사이클이 아닌 참조 사이클 카운트
터보/절전에 관계없이 고정 주파수로 카운트되므로 클럭당 uops 분석을 원할 경우 성능 카운터를 사용하십시오. rdtsc
(시스템 클럭 조정은 카운트되지 않기 때문에) 월클럭 시간과 정확하게 관련되어 있습니다.steady_clock
).
TSC 주파수는 항상 CPU의 정격 주파수(광고된 스티커 주파수)와 같았습니다.i7-6700HQ 2.6GHz Skylake의 경우 2592MHz, 4000MHz i7-6700k의 경우 4008MHz 등 CPU에 따라서는 거의 비슷합니다.i5-1035 Ice Lake와 같은 최신 CPU에서는 TSC = 1.5GHz, 기본 = 1.1GHz이므로 터보를 비활성화해도 해당 CPU의 TSC = 코어 사이클에서는 거의 작동하지 않습니다.
마이크로벤치마킹에 사용하는 경우 타이밍을 시작하기 전에 CPU가 이미 최대 클럭 속도로 되어 있는지 확인하기 위한 워밍업 기간을 먼저 포함합니다.(또한 옵션으로 터보를 비활성화하여 마이크로벤치마크 중에 CPU 주파수의 변화를 피하기 위해 OS에 최대 클럭 속도를 지정하도록 지시합니다).
마이크로벤치마킹은 어렵습니다.퍼포먼스 평가의 이디얼한 방법을 참조해 주세요.다른 함정에 대비해서요
TSC 대신에, 하드웨어 퍼포먼스카운터에 액세스 할 수 있는 라이브러리를 사용할 수 있습니다.복잡하지만 오버헤드가 낮은 방법은 perf 카운터를 프로그래밍하고rdmsr
사용자 공간에서 또는 보다 간단한 방법으로 프로그램 일부에 대한 perf stat과 같은 트릭을 포함할 수 있습니다. 시간 지정 영역이 충분히 길면perf stat -p PID
.
단, 메모리 바인드 시 등에 Skylake가 얼마나 다른 부하로 인해 다운되는지를 확인하는 경우를 제외하고는 CPU 클럭을 마이크로벤치마크용으로 고정하는 것이 좋습니다.(메모리 대역폭/레이텐시는 대부분 코어와 다른 클럭을 사용하여 고정됩니다.아이돌 클럭 속도에서는 L2 또는 L3 캐시 미스에는 코어 클럭사이클이 훨씬 적게 걸립니다).
- 백투백 rdtsc를 사용한 마이너스 클럭 사이클 측정RDTSC의 역사: 원래 CPU는 절전 기능을 하지 않았기 때문에 TSC는 실시간 클럭과 코어 클럭을 모두 갖추고 있었습니다.그런 다음 거의 사용하지 않는 여러 단계를 거쳐 코어 클럭 사이클에서 분리된 현재 형태의 유용한 오버헤드가 낮은 타임소스로 발전했습니다.
constant_tsc
클럭이 정지해도 정지하지 않습니다( ).nonstop_tsc
또한 일부 팁(예: 평균 시간을 사용하지 말고 중앙값을 사용하십시오(특이치가 매우 높음). - std:: chrono:: clock, 하드웨어 클럭 및 사이클 수
- RDTSC를 사용한 CPU 사이클 취득 - RDTSC의 가치가 항상 높아지는 이유는 무엇입니까?
- 인텔(R)의 사이클 손실rdtsc와 CPU_CLK_UNHALTED 간의 불일치.참조_TSC
- RDTSC 명령을 사용하여 C에서 코드 실행 시간을 측정하는 것은 커널 모드에서도 피할 수 없는 SMI(시스템 관리 인터럽트)를 포함한 몇 가지 gotcha를 나열합니다.
cli
) 및 가상화rdtsc
가상 머신 아래에 있습니다.물론 정기적인 인터럽트 등 기본적인 것도 가능하기 때문에 타이밍을 여러 번 반복하고 이상치를 버립니다. - Linux 로 TSC 의 빈도를 확인합니다.TSC 주파수를 프로그램적으로 쿼리하는 것은 어렵고 특히 사용자 공간에서 가능하지 않거나 교정보다 더 나쁜 결과를 초래할 수 있습니다.다른 알려진 시간원을 사용하여 보정하는 데 시간이 걸립니다.TSC를 나노초로 변환하는 것이 얼마나 어려운지에 대해서는, 이 질문을 참조해 주세요(또, OS가 기동시에 이미 변환율을 묻고 있기 때문에, OS에 그 변환율을 문의해 주세요).
튜닝 목적으로 RDTSC를 사용하여 마이크로벤치 마킹을 하는 경우, 가장 좋은 방법은 틱만 사용하고 나노초로 변환하려는 시도조차 건너뛰는 것입니다.그렇지 않으면 다음과 같은 고해상도 라이브러리 시간 함수를 사용합니다.std::chrono
또는clock_gettime
타임스탬프 함수에 대한 설명/비교 또는 메모리에서 공유 타임스탬프를 읽으면 gettimeofday와 동등한 속도를 얻을 수 있습니다.rdtsc
타이머 인터럽트 또는 스레드가 이를 업데이트할 수 있을 정도로 정밀도 요구사항이 낮은 경우.
결정 주파수 및 승수를 찾는 방법은 rdtsc를 사용하여 시스템 시간 계산을 참조하십시오.
특히 멀티코어 멀티패키지 환경에서 CPU TSC 가져오기 조작에 따르면 Nehalem 이상에서는 TSC가 패키지의 모든 코어에 대해 동기화 및 잠겨 있습니다(불변 = 상시 및 논스톱 TSC 기능과 함께).멀티 소켓 동기화에 대한 자세한 내용은 @amdn의 답변을 참조하십시오.
(또한 최신 멀티소켓 시스템에서도 이러한 기능을 갖추고 있는 한 신뢰성이 높은 것으로 생각됩니다.링크된 질문에 대한 @amdn의 답변과 자세한 내용은 아래를 참조하십시오.)
TSC와 관련된 CPUID 기능
Linux가 CPU 기능에 사용하는 이름과 동일한 기능에 대한 다른 별칭을 사용합니다.
tsc
- TSC가 존재하며rdtsc
지원되고 있습니다.x86-64 베이스라인rdtscp
-rdtscp
지원되고 있습니다.tsc_deadline_timer
CPUID.01H:ECX.TSC_Deadline[bit 24] = 1
- TSC가 입력한 값에 도달하면 인터럽트를 발생시키도록 로컬 APIC를 프로그래밍할 수 있습니다.IA32_TSC_DEADLINE
. "티켓 없는" 커널을 사용할 수 있도록 합니다.다음의 일이 일어날 때까지 잠을 잘 수 있도록 합니다.constant_tsc
: 상시 TSC 기능의 지원은 CPU 패밀리와 모델 번호를 체크함으로써 결정됩니다.TSC는 코어 클럭 속도의 변화에 관계없이 일정한 주파수로 체크합니다.이것이 없으면 RDTSC는 코어 클럭사이클을 카운트합니다nonstop_tsc
: 이 기능은 인텔 SDM 매뉴얼에서는 불변 TSC라고 불리며 인텔 SDM을 탑재한 프로세서에서 지원됩니다.CPUID.80000007H:EDX[8]
TSC는 깊은 수면 C 상태에서도 계속 똑딱거린다.모든 x86 프로세서에서nonstop_tsc
암시하다constant_tsc
,그렇지만constant_tsc
반드시 을 의미하는 것은 아니다nonstop_tsc
개별 CPUID 피처 비트는 없습니다.인텔과 AMD에서는 같은 불변 TSC CPUID 비트가 양쪽을 의미합니다.constant_tsc
그리고.nonstop_tsc
특징들.Linux 의 x86/커널/cpu/intel.c 검출 코드를 참조해 주세요.amd.c
비슷했어요.
Saltwell/Silvermont/Airmont를 기반으로 하는 일부 프로세서(전부는 아님)는 ACPI S3 풀 시스템 sleep 상태에서 TSC가 계속 켜져 있습니다.nonstop_tsc_s3
이것은 Always-on TSC라고 불립니다(Airmont 기반의 것은 발매되지 않은 것 같습니다).
상수 및 불변 TSC에 대한 자세한 내용은 "불변하지 않는 상수 TSC가 CPU 상태 간에 주파수를 변경할 수 있습니까?"를 참조하십시오.
tsc_adjust
:CPUID.(EAX=07H, ECX=0H):EBX.TSC_ADJUST (bit 1)
그IA32_TSC_ADJUST
MSR을 사용할 수 있기 때문에 OS는 TSC에 추가되는 오프셋을 설정할 수 있습니다.rdtsc
또는rdtscp
읽습니다.이것에 의해, 논리 코어간에 TSC를 동기 해제하지 않고, 일부 또는 모든 코어의 TSC를 효과적으로 변경할 수 있습니다.(소프트웨어가 TSC를 각 코어의 새로운 절대값으로 설정했을 경우 발생합니다.모든 코어의 같은 사이클로 관련 WRMSR 명령을 실행하는 것은 매우 어렵습니다.)
constant_tsc
그리고.nonstop_tsc
TSC를 타임소스로서 사용할 수 있도록 합니다.clock_gettime
(단, Linux 등의 OS에서는 RDTSC를 사용하여 NTP로 유지되는 느린 클럭의 틱 간에 보간하여 타이머 인터럽트의 스케일/오프셋 계수를 갱신합니다.자세한 내용은 constant_tsc 및 nonstop_tsc를 사용하는 CPU에서 시간이 표류하는 이유를 참조하십시오.)딥 슬립 상태나 주파수 확장을 지원하지 않는 오래된 CPU에서도 TSC를 타임 소스로 사용할 수 있는 경우가 있습니다.
Linux 소스 코드의 코멘트는, 다음과 같은 것도 나타내고 있습니다.constant_tsc
/nonstop_tsc
(인텔의 경우) 기능은 다음과 같습니다.코어 및 소켓 전체에서도 신뢰성이 있습니다(캐비닛 전체는 신뢰성이 없습니다).이 경우는, 명시적으로 오프합니다.)"
"across sockets" 부분은 정확하지 않습니다.일반적으로 불변 TSC는 TSC가 같은 소켓 내의 코어 간에 동기화되는 것만 보증합니다.인텔 포럼 스레드에서 Martin Dixon(인텔)은 TSC의 불변성이 크로스 소켓 동기화를 의미하는 것은 아니라고 지적합니다. 이를 위해서는 플랫폼벤더가 모든 소켓에 동기식으로 RESET을 배포해야 합니다.위의 Linux 커널 코멘트를 보면 플랫폼 벤더가 실제로 그렇게 하고 있는 것 같습니다.특히 멀티코어 멀티프로세서 환경에서 CPU TSC 가져오기 작업에 대한 응답도 단일 메인보드 상의 모든 소켓이 동기화되어 시작되어야 한다는 데 동의합니다.
멀티 소켓 공유 메모리 시스템에서는 모든 코어의 TSC가 동기화되어 있는지 여부를 직접 확인할 수 없습니다.Linux 커널은 기본적으로 부팅 시 및 런타임 체크를 수행하여 TSC를 클럭소스로 사용할 수 있는지 확인합니다.이러한 체크에는 TSC가 동기화되어 있는지 여부가 포함됩니다.명령어 출력dmesg | grep 'clocksource'
는 커널이 TSC를 클럭소스로 사용하고 있는지 여부를 나타냅니다.이것은 체크가 통과했을 경우에만 발생합니다.그러나 그렇다고 해서 TSC가 시스템의 모든 소켓에서 동기화된다는 확실한 증거는 아닙니다.커널 파라미터tsc=reliable
는 커널에 TSC를 클럭소스로 맹목적으로 사용할 수 있음을 알릴 때 사용할 수 있습니다.
크로스 소켓 TSC가 동기화되지 않는 경우가 있습니다. (1) CPU의 핫 플러그, (2) 확장 노드 컨트롤러에 의해 연결된 여러 보드에 소켓이 분산되어 있는 경우, (3) TSC가 일부 프로세서에서 전원이 꺼진 상태에서 복귀한 후 TSC가 재동기되지 않는 경우가 있습니다(4).설치된.
TSC_ADJUST 오프셋을 사용하지 않고 TSC를 직접 변경하는 OS 또는 하이퍼바이저는 TSC를 비동기화할 수 있기 때문에 사용자 공간에서 CPU 이행으로 다른 클럭을 읽을 수 있다고 가정하는 것은 안전하지 않을 수 있습니다.(이래서)rdtscp
는 추가 출력으로 core-ID를 생성하므로 시작/종료 시각이 다른 클럭에서 언제 오는지 검출할 수 있습니다.불변 TSC 기능 이전에 도입되었을 수도 있고, 모든 가능성을 고려했을 수도 있습니다).
사용하시는 경우rdtsc
αtaskset -c 0 ./myprogram
Linux에서.TSC에 필요한지에 관계없이 CPU 이행은 보통 캐시 누락이 많아지고 테스트도 엉망이 되어 시간이 더 걸립니다.(단, 인터럽트가 발생합니다).
내장의 사용으로 인한 ASM의 효율성은 어느 정도입니까?
이것은 @Mystical의 GNU C 인라인 asm에서 얻을 수 있는 것과 거의 비슷하거나 RAX의 상위 비트가 제로인 것을 알고 있기 때문에 더 좋습니다.인라인 asm을 유지하고 싶은 주된 이유는 딱딱하고 오래된 컴파일러와의 호환성을 위해서입니다.
의 비인라인 버전readTSC
함수 자체는 다음과 같이 x86-64용 MSVC와 컴파일됩니다.
unsigned __int64 readTSC(void) PROC ; readTSC
rdtsc
shl rdx, 32 ; 00000020H
or rax, rdx
ret 0
; return in RAX
에서 64비트 정수를 반환하는 32비트 호출 규약의 경우edx:eax
,이건 그저.rdtsc
/ret
중요한 게 아니라 항상 인라인으로 하고 싶잖아요.
테스트 발신자가 그것을 2회 사용하고 간격을 뺄셈하여 시간을 재는 경우:
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 출력입니다(name-demangling 적용).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개의 컴파일러 모두or
+mov
대신lea
낮은 반과 높은 반을 다른 레지스터로 결합합니다.그들이 최적화하지 못한 것은 일종의 통조림 같은 것 같아요.
하지만 직접 인라인 ASM으로 Shift/lea를 쓰는 것은 거의 더 낫지 않습니다.32비트 결과만 유지할 정도로 짧은 간격을 두고 계시다면 EDX에서 상위 32비트의 결과를 무시할 수 있는 컴파일러의 기회를 박탈할 수 있습니다.또는 컴파일러가 시작 시간을 메모리에 저장하기로 결정한 경우 shift//mov 대신 32비트 저장소를 2개만 사용할 수 있습니다.타이밍의 일부로서 1개의 추가 uop이 마음에 걸리는 경우는, 마이크로벤치 마크 전체를 순수한 ASM으로 기입하는 것이 좋습니다.
다만, @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의 경우, 이것은 때때로 더 나은 ASM을 줍니다.__rdtsc()
gcc/clang/ICC의 경우, 그러나 다른 경우 컴파일러는 여분의 레지스터를 사용하여 lo와 hi를 따로 저장하도록 속여 clang이 최적화되도록 합니다.((end_hi-start_hi)<<32) + (end_lo-start_lo)
실제 레지스터 압력이 있으면 컴파일러가 더 빨리 결합할 수 있기를 바랍니다.(gcc와 ICC는 여전히 lo/hi를 별도로 저장하지만 최적화도 하지 않습니다.)
하지만 32비트 gcc8은 실수를 저지릅니다.rdtsc()
실제와 함께 기능하다add/adc
clang처럼 edx:eax로 결과를 반환하는 대신 0을 사용합니다.(gcc6 이전 버전에서는 문제가 없습니다.|
대신+
단, 확실히는__rdtsc()
gcc에서 32비트 코드 gen을 고려하는 경우 internal을 선택합니다.
VC++는 인라인어셈블리에 대해 전혀 다른 구문을 사용합니다.단, 32비트 버전에서만 사용할 수 있습니다.64비트 컴파일러는 인라인 어셈블리를 전혀 지원하지 않습니다.
이 경우엔 그게 더 나을 것 같아요.rdtsc
에는 타이밍 코드시퀀스에 관한 (적어도)2가지 큰 문제가 있습니다.첫 번째 (대부분의 명령과 마찬가지로) 순서대로 실행되지 않을 수 있습니다.따라서 짧은 코드 시퀀스의 시간을 재려고 하면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가 제공하는 모든 수단을 사용하여 이 모든 것을 1개의 프로세스/코어로 실행할 수 있도록 해야 합니다.대부분의 경우 코드 정렬을 강제로 수행할 수도 있습니다. 정렬을 변경하면 실행 속도가 상당히 달라질 수 있습니다.
마지막으로 여러 번 실행해야 합니다.또한 작업 전환 등 중간에 중단될 가능성이 있기 때문에 실행이 나머지 작업보다 상당히 오래 걸릴 가능성에 대비해야 합니다.예를 들어 각각 최대 40~43클럭 사이클이 걸리는 5회 실행과 10000+클럭 사이클이 걸리는 6회 실행 등입니다.확실히 후자의 경우 특이치를 버리면 됩니다.코드에 의한 것이 아닙니다.
요약: rdtsc 명령 실행 자체는 (거의) 걱정할 필요가 없습니다.결과를 얻으려면 더 많은 작업을 수행해야 합니다.rdtsc
어떤 의미도 있을 거야
Windows 의 경우, Visual Studio 는 편리한 「컴파일러 본연의」(컴파일러가 이해하는 특수한 함수)를 제공해, RDTSC 명령을 실행해 결과를 반환합니다.
unsigned __int64 __rdtsc(void);
Linux 시스템콜config = PERF_COUNT_HW_CPU_CYCLES
이 Linux 시스템콜은 퍼포먼스이벤트용 크로스아키텍처 래퍼로 보입니다.
이 답변은 비슷합니다.C 프로그램에서 실행되는 명령의 수를 빠르게 카운트하는 방법.PERF_COUNT_HW_CPU_CYCLES
대신PERF_COUNT_HW_INSTRUCTIONS
이 답변은 다음과 같습니다.PERF_COUNT_HW_CPU_CYCLES
자세한 내용은 다른 답변을 참조하십시오.
다음은 man 페이지 끝에 제공된 예를 기반으로 한 예입니다.
perf_event_open.c
#define _GNU_SOURCE
#include <asm/unistd.h>
#include <linux/perf_event.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <inttypes.h>
#include <sys/types.h>
static long
perf_event_open(struct perf_event_attr *hw_event, pid_t pid,
int cpu, int group_fd, unsigned long flags)
{
int ret;
ret = syscall(__NR_perf_event_open, hw_event, pid, cpu,
group_fd, flags);
return ret;
}
int
main(int argc, char **argv)
{
struct perf_event_attr pe;
long long count;
int fd;
uint64_t n;
if (argc > 1) {
n = strtoll(argv[1], NULL, 0);
} else {
n = 10000;
}
memset(&pe, 0, sizeof(struct perf_event_attr));
pe.type = PERF_TYPE_HARDWARE;
pe.size = sizeof(struct perf_event_attr);
pe.config = PERF_COUNT_HW_CPU_CYCLES;
pe.disabled = 1;
pe.exclude_kernel = 1;
// Don't count hypervisor events.
pe.exclude_hv = 1;
fd = perf_event_open(&pe, 0, -1, -1, 0);
if (fd == -1) {
fprintf(stderr, "Error opening leader %llx\n", pe.config);
exit(EXIT_FAILURE);
}
ioctl(fd, PERF_EVENT_IOC_RESET, 0);
ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);
/* Loop n times, should be good enough for -O0. */
__asm__ (
"1:;\n"
"sub $1, %[n];\n"
"jne 1b;\n"
: [n] "+r" (n)
:
:
);
ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
read(fd, &count, sizeof(long long));
printf("%lld\n", count);
close(fd);
}
결과는 합리적인 것 같습니다. 예를 들어, 인쇄 사이클을 한 후 명령 카운트를 다시 컴파일하면 슈퍼스칼라 실행과 같은 효과로 인해 반복당 약 1사이클(1사이클에 2개의 명령이 수행됨)이 발생하며, 랜덤 메모리 액세스 지연으로 인해 각 실행마다 결과가 약간 다릅니다.
또, 다음과 같은 것에 흥미를 가질지도 모릅니다.PERF_COUNT_HW_REF_CPU_CYCLES
이 문서는 manpage 문서로 다음과 같습니다.
총 사이클. CPU 주파수 스케일링의 영향을 받지 않습니다.
따라서 주파수 스케일링이 켜져 있는 경우 실제 벽 시간에 가까운 값을 얻을 수 있습니다.이것들은, 보다 2/3배 크다.PERF_COUNT_HW_INSTRUCTIONS
아마 지금은 주파수 스케일이 조정되어 있기 때문일 겁니다.
언급URL : https://stackoverflow.com/questions/13772567/how-to-get-the-cpu-cycle-count-in-x86-64-from-c
'IT이야기' 카테고리의 다른 글
Java에서의 Regex 이름 있는 그룹 (0) | 2022.06.14 |
---|---|
vuej에서 데이터 및 방법 단순화 (0) | 2022.06.14 |
Vue는 Vuex의 커밋을 듣습니까? (0) | 2022.06.14 |
beforeRouteEnter를 사용하여 vuej에서 비동기 데이터를 올바르게 로드하는 방법은 무엇입니까? (0) | 2022.06.14 |
Java에서 다른 어레이에서 서브 어레이를 작성하는 방법 (0) | 2022.06.14 |