2016-09-21 1 views
1

쿼리 응답 프로그램을 가속화하기 위해 Intel SIMD 내장 함수를 사용하려고합니다. query_cnt이 입력에 의존하지만 SIMD 레지스터 수보다 항상 작다고 가정합니다 (즉, SIMD 레지스터를 보유하기에 충분한 SIMD 레지스터가 있음). 쿼리는 필요할 때마다로드하는 대신 애플리케이션의 핫 데이터이므로 처음에로드하고 레지스터에 항상 보관할 수 있습니까?SIMD 내장 함수를 사용할 때 레지스터에 입력 종속 핫 데이터를 유지하는 방법

쿼리가 float 유형이고 AVX256이 지원된다고 가정합니다. 지금은 같은 것을 사용해야합니다 :

std::vector<__m256> vec_queries(query_cnt/8); 
for (int i = 0; i < query_cnt/8; ++i) { 
    vec_queries[i] = _mm256_loadu_ps((float const *)(curr_query_ptr)); 
    curr_query_ptr += 8; 
} 

나는 잠재적 인로드/저장 오버 헤드가 있기 때문에 그것은 좋은 방법이 아닙니다 알고있다, 그러나 적어도 그들이 할 수 있도록 vec_queries[i] 최적화 할 수있는 약간의 기회가있다 레지스터에 보관,하지만 난 여전히 좋은 방법이 아니라고 생각합니다.

더 좋은 아이디어가 있습니까?

+0

다른 작업을 수행하지 않는 루프에서 여러 쿼리를 처리하고 있습니까? 그렇지 않은 경우 다음 쿼리를 수행 할 때 데이터가 레지스터에 남아 있지 않습니다. 아니면 쿼리에 전역 레지스터 변수를 사용하는 것이 가치 있다고 생각하십니까? GNU C는 이것을 할 수 있습니다.'__m256 vec_query0 asm ("ymm0");은 올바른 구문 IIRC 여야합니다. –

+0

벡터를 레지스터에 보관하지 않는 것을 확인하기 위해 실제 asm을 살펴 보았습니까? 운이 좋으면 컴파일러가 std :: vector 동적 할당 오버 헤드의 대부분을 최적화 할 수 있습니다.그렇지 않은 경우 고정 크기 배열을 사용하십시오 (크기가 상한이기 때문에 사용하십시오). –

+0

@PeterCordes 귀하의 조언에 감사드립니다. 실제 asm을 보지 않았지만 고정 크기 배열을 사용하는 것이 좋은 옵션 일 수 있으므로 모든 배열 요소를 레지스터에 바인딩하기 위해'__m256 vec_query0 asm ("ymm0")'과 같은 것을 사용할 수 있습니다. 그러나 그렇게한다면, 레지스터 중 일부는 항상 고정 요소에 의해 점유되어 성능 저하를 초래할 수 있습니까? – MarZzz

답변

0

게시 한 코드 샘플에서 가변 길이 memcpy를 수행하는 것처럼 보입니다. 컴파일러가 수행하는 작업과 주변 코드에 따라 실제로는 memcpy을 호출하면 더 나은 결과를 얻을 수 있습니다. 예 : 벡터 루프와 rep movsb 사이의 브레이크 짝수 포인트는 Intel Haswell에서 ~ 128 바이트 정도로 낮을 수도 있습니다. memcpy에 대한 구현 노트 및 크기에 대한 그래프 - 몇 가지 전략에 대한 인텔의 최적화 설명서를 확인하십시오. ( 태그 위키의 링크).

당신은 어떤 CPU를 말하지 않았습니까? 그래서 나는 단지 최신 인텔을 추측하고 있습니다.

나는 레지스터에 대해 너무 걱정한다고 생각합니다. L1 캐시에서 적중 한로드는 매우 저렴합니다. Haswell (및 Skylake)은 시계 당 두 개의 __m256로드 (동일한 사이클의 저장소)를 수행 할 수 있습니다. 이전에 Sandybridge/IvyBridge는 클럭 당 두 개의 메모리 작업을 수행 할 수 있었고 그 중 최대 하나는 저장소였습니다. 또는 이상적인 조건 (256b로드/저장)에서는 클럭 당 2x 16B로드 및 1x 16B를 관리 할 수 ​​있습니다. 따라서 256b 벡터를 로딩/저장하는 것은 Haswell보다 비용이 많이 들지만 L1 캐시에서 정렬되고 뜨거우면 여전히 매우 저렴합니다.

나는 코멘트에서 언급했는데, GNU C global register variables이 가능할 수도 있지만 대부분 "이론적으로 이것은 기술적으로 가능하다"는 의미이다. 프로그램의 전체 런타임 (라이브러리 함수 호출을 포함하여 이러한 목적으로 전용 된 여러 벡터 레지스터가 필요하지 않으므로 다시 컴파일해야합니다).

현실적으로 컴파일러가 중요한 루프 내에서 사용하는 모든 함수에 대한 정의를 (또는 적어도 최적화하는 동안) 인라인 할 수 있는지 확인하십시오. 이렇게하면 Windows 및 System V x86-64 ABI에는 호출 보존 YMM (__m256) 레지스터가 없기 때문에 벡터 호출을 함수 호출에 걸쳐 누설하거나 다시로드하지 않아도됩니다.

현대 CPU의 마이크로 아키텍처 세부 사항, 최소한 실험 및 조정을 통해 측정 할 수있는 세부 사항에 대해 자세히 알아 보려면 Agner Fog's microarch pdf을 참조하십시오.

+0

나는'__m256 a'와'float b [8]'사이에'memcpy'를 시도하여'_mm256_loadu_ps'와'_mm256_storeu_ps'를 모방 해 실제로 작동하는 것을 발견했습니다. 처음에는 '__m256' 또는'__m256i'와 같은 SIMD 본질적인 데이터 유형이 레지스터에 보관할 가능성이 더 큰 특수 데이터 유형이라고 생각했습니다. 이제 나는 'float'이나'int'와 같은 카운터 파트와 비교할 때 특별한 것은없는 것처럼 보이지만 정렬 제한이 더 많다는 생각이 바뀌 었습니다. 맞습니까? 만약 그렇다면, 모든 SIMD'load/stores'를'memcpy'로 대체하면 잠재적 인 성능상의 차이는 무엇입니까? – MarZzz

+0

@MarZzz : 네, 스칼라 float 형 또는 int 형과 매우 비슷합니다. 더 많은 계산을 위해'__m256' (또는'__m256i')을 사용하려고한다면, load intrinsic을 사용하십시오. (또는 다른'__m256'의 간단한 할당을 사용하십시오). 많은 데이터를 이동하려면 memcpy를 사용하십시오. memcpy를 사용하여'array [i]'를'float tmp'에 할당하지 않으므로 SIMD 유형에는 사용하지 마십시오. 아마도 * 최적화 될 것입니다. 그러나 읽기 쉽지 않으며 확실히 컴파일러에 도움이되지 않습니다. –

+0

이와 같은 것에 대해 궁금하다면 컴파일러 출력을 살펴보십시오. 컴파일러가 처음 생성 한 어셈블리를 작성하는 것보다 생성자가 생성 한 코드를 따르는 것이 훨씬 쉽습니다. 따라서 이것을 시도하기 위해 실제로 asm을 알 필요는 없습니다. http://gcc.godbolt.org/에 코드를 올려 놓으면 [멋지게 형식화 된보기]를 얻을 수 있습니다. (http://stackoverflow.com/questions/38552116/how-to-remove-noise-from-gcc-clang- 어셈블리 출력) (asm 행을 생성하는 소스 행을 표시하는 선택적 색상 강조 표시가 있음). –

관련 문제