2014-01-18 11 views
5

_mm_stream_ps 내장 함수를 가지고 놀고 있는데 성능을 이해하는 데 어려움이 있습니다. 정말 퍼즐 무엇스트림 내장 함수가 성능을 저하시킵니다.

2.3 GHz Core i7 (I7-3615QM) (Laptop): 
    305.176 MB allocated 
    Bandwidth (non-temporal): 24.2242 GB/s 
    Bandwidth: 21.4136 GB/s 

Xeon(R) CPU E5-2650 0 @ 2.00GHz (cluster (exclusive job)): 
    305.176 MB allocated 
    Bandwidth (non-temporal): 8.33133 GB/s 
    Bandwidth: 8.20684 GB/s 

내가 더 나은 성능을 볼 수 있다는 것입니다 - :

#include <stdio.h> 
#include <stdint.h> 
#include <stdlib.h> 
#include <omp.h> 

#include <immintrin.h> 

#define NUM_ELEMENTS 10000000L 

static void copy_temporal(float* restrict x, float* restrict y) 
{ 
    for(uint64_t i = 0; i < NUM_ELEMENTS/2; ++i){ 
     _mm_store_ps(y,_mm_load_ps(x)); 
     _mm_store_ps(y+4,_mm_load_ps(x+4)); 
     x+=8; 
     y+=8; 
    } 
} 
static void copy_nontemporal(float* restrict x, float* restrict y) 
{ 
    for(uint64_t i = 0; i < NUM_ELEMENTS/2; ++i){ 
     _mm_stream_ps(y,_mm_load_ps(x)); 
     _mm_stream_ps(y+4,_mm_load_ps(x+4)); 
     x+=8; 
     y+=8; 
    } 
} 

int main(int argc, char** argv) 
{ 
    uint64_t sizeX = sizeof(float) * 4 * NUM_ELEMENTS; 
    float *x = (float*) _mm_malloc(sizeX,32); 
    float *y = (float*) _mm_malloc(sizeX,32); 

    //initialization 
    for(uint64_t i = 0 ; i < 4 * NUM_ELEMENTS; ++i){ 
     x[i] = (float)rand()/RAND_MAX; 
     y[i] = 0; 
    } 

    printf("%g MB allocated\n",(2 * sizeX)/1024.0/1024.0); 

    double start = omp_get_wtime(); 
    copy_nontemporal(x, y); 
    double time = omp_get_wtime() - start; 
    printf("Bandwidth (non-temporal): %g GB/s\n",((3 * sizeX)/1024.0/1024.0/1024.0)/time); 

    start = omp_get_wtime(); 
    copy_temporal(x, y); 
    time = omp_get_wtime() - start; 
    printf("Bandwidth: %g GB/s\n",((3 * sizeX)/1024.0/1024.0/1024.0)/time); 

    _mm_free(x); 
    _mm_free(y); 

    return 0; 
} 

성능 결과 :

여기

은 ... 스트림 버전 내가 함께 일하고 있어요 코드 조각입니다 - Xeon CPU (랩탑에 있지 않음) - 정렬되지 않은로드 및 저장소 (예 : storeu_ps/loadu_ps)를 사용하는 경우 :

y의 중복로드로 인해 스트림 버전이 비 스트림 버전보다 빠르다고 기대합니다. 그러나 측정 결과 스트림 버전이 실제로 비 스트림 버전보다 2 배 느린 것으로 나타났습니다.

설명이 있으십니까?

사용 된 컴파일러 : Intel 14.0.1; 컴파일러 플래그 : -O3 -restrict -xAVX; CPU 사용 : Intel Xeon E5-2650;

감사합니다.

+0

루프를 풀 필요가 없습니다.루프 언 롤링은 종속성 체인에서만 유용하며 종속성 체인은 없습니다. CPU가이를 처리 할 수 ​​있습니다. 나는 질문이있다. 대역폭 계산에서 3의 요인은 무엇입니까? –

+0

두 번의 읽기 + 한 번의 쓰기. 비 시간적 버전은 하나의 읽기만 수행하지만 비교를 간단하게하기 위해 3의 요소를 유지했습니다. – user1829358

답변

0

ScottD가 지적했듯이 질문에 대한 대답은 생성 된 어셈블리 코드에 있습니다. 분명히 인텔 컴파일러는 액세스 패턴을 감지 할만큼 똑똑하고 임시 버전의 경우에도 비 시간로드를 자동으로 생성합니다. 여기

는 시간적 버전의 컴파일러가 생성 한 어셈블리 코드입니다 :

..___tag_value___Z13copy_temporalPfS_.35:      # 
     xor  edx, edx          #22.4 
     xor  eax, eax          # 
..B2.2:       # Preds ..B2.2 ..B2.1 
     vmovups xmm0, XMMWORD PTR [rax+rdi]     #23.34 
     inc  rdx           #22.4 
     vmovntps XMMWORD PTR [rax+rsi], xmm0     #23.20 
     vmovups xmm1, XMMWORD PTR [16+rax+rdi]    #24.36 
     vmovntps XMMWORD PTR [16+rax+rsi], xmm1    #24.20 
     vmovups xmm2, XMMWORD PTR [32+rax+rdi]    #23.34 
     vmovntps XMMWORD PTR [32+rax+rsi], xmm2    #23.20 
     vmovups xmm3, XMMWORD PTR [48+rax+rdi]    #24.36 
     vmovntps XMMWORD PTR [48+rax+rsi], xmm3    #24.20 
     add  rax, 64          #22.4 
     cmp  rdx, 5000000         #22.4 
     jb  ..B2.2  # Prob 99%      #22.4 

여전히이 다음과 같다 남아있는 질문 :

왜 비 정렬 시간 버전이 수행하는 것보다 더 나은 CPU E5-2650의 비 임시 버전 (위 참조). 이미 생성 된 어셈블리 코드를 살펴 보았고 컴파일러는 실제로 존재하지 않는 정렬로 인해 vmovups 명령어를 생성합니다.

+0

ICC가 당신이 말한 것과 다른 것을한다면 그것은 실망입니다. 나는 그것이 당신이 의도 한대로 본질을 구현한다면 선호 할 것이다. –

3

스트림 변형은 DRAM에 직접 파이프 라인 된 버스트 쓰기를 만듭니다. 속도는 대략 DRAM 속도와 일치해야합니다. 표준 저장소는 캐시에 씁니다 (그러나 데이터가 캐시에없는 경우 먼저 캐시로 읽습니다). 데이터가 이미 캐시에있는 경우 표준 저장소는 캐시 쓰기 속도로 실행됩니다. 일반적으로 마지막 레벨 캐시 크기보다 훨씬 큰 크기의 쓰기는 stream 메소드를 사용하는 것이 훨씬 빠릅니다. 작은 글씨는 종종 표준 상점을 사용하면 더 빠릅니다. 2GB의 버퍼 크기를 사용하여 테스트를 실행 해보십시오. 스트림 메소드가 더 빨라야합니다. 인텔 코어 i7-2600K에서

#define __USE_MINGW_ANSI_STDIO 1 
#include <stdlib.h> 
#include <intrin.h> 
#include <windows.h> 
#include <stdio.h> 
#include <stdint.h> 

//----------------------------------------------------------------------------- 
// 
// queryPerformanceCounter - similar to QueryPerformanceCounter, but returns 
//       count directly. 

uint64_t queryPerformanceCounter (void) 
    { 
    LARGE_INTEGER int64; 
    QueryPerformanceCounter (&int64); 
    return int64.QuadPart; 
    } 

//----------------------------------------------------------------------------- 
// 
// queryPerformanceFrequency - same as QueryPerformanceFrequency, but returns count direcly. 

uint64_t queryPerformanceFrequency (void) 
    { 
    LARGE_INTEGER int64; 

    QueryPerformanceFrequency (&int64); 
    return int64.QuadPart; 
    } 

//--------------------------------------------------------------------------- 

static void testNontemporal (float *x, float *y, uint64_t numberOfVectors) 
    { 
    uint64_t i; 
    for(i = 0; i < numberOfVectors/2; ++i) 
     { 
     _mm_stream_ps(y,_mm_load_ps(x)); 
     _mm_stream_ps(y+4,_mm_load_ps(x+4)); 
     y+=8; x+=8; 
     } 
    } 

//--------------------------------------------------------------------------- 

static void testTemporal (float *x, float *y, uint64_t numberOfVectors) 
    { 
    uint64_t i; 
    for(i = 0; i < numberOfVectors/2; ++i) 
     { 
     _mm_store_ps(y,_mm_load_ps(x)); 
     _mm_store_ps(y+4,_mm_load_ps(x+4)); 
     y+=8; x+=8; 
     } 
    } 

//--------------------------------------------------------------------------- 

static void runtests (int nonTemporal) 
    { 
    uint64_t startCount, elapsed, index; 
    float *x, *y; 
    uint64_t numberOfBytes = 400 * 0x100000ull; 
    uint64_t numberOfFloats = numberOfBytes/sizeof *x; 
    uint64_t numberOfVectors = numberOfFloats/4; 
    double gbPerSecond; 

    x = _mm_malloc (numberOfBytes, 32); 
    y = _mm_malloc (numberOfBytes, 32); 
    if (x == NULL || y == NULL) exit (1); 

    // put valid floating point data into the source buffer 
    // to avoid performance penalty 
    for (index = 0; index < numberOfFloats; index++) 
     x [index] = (float) index, y [index] = 0; 

    startCount = queryPerformanceCounter(); 
    if (nonTemporal) 
     testNontemporal (x, y, numberOfVectors); 
    else 
     testTemporal (x, y, numberOfVectors); 
    elapsed = queryPerformanceCounter() - startCount; 
    gbPerSecond = (double) numberOfBytes/0x40000000 * queryPerformanceFrequency()/elapsed; 
    printf ("%.2f GB/s\n", gbPerSecond); 
    _mm_free (x); 
    _mm_free (y); 
    } 

//--------------------------------------------------------------------------- 

int main (void) 
    { 
    // raise our priority to increase measurement accuracy 
    SetPriorityClass (GetCurrentProcess(), REALTIME_PRIORITY_CLASS); 

    printf ("using temporal stores\n"); 
    runtests (0); 
    printf ("using non-temporal stores\n"); 
    runtests (1); 
    return 0; 
    } 

//--------------------------------------------------------------------------- 

출력 : 여기서

입증하는 벤치 마크

using temporal stores 
5.57 GB/s 
using non-temporal stores 
8.35 GB/s 
+0

답장을 보내 주셔서 감사합니다. 이미 400MB 크기의 버퍼 (예 : 시스템의 캐시보다 많은 버퍼)를 사용하고 있습니다. 또한 일부 하드웨어 카운터를 읽으려면 코드를 계측 했으므로 결과가 결정적입니다 (예 : stream_ps를 사용하면 쓰기 미스가 적음). 그러나이 두 버전 간의 큰 성능 차이를 아직 설명 할 수 없습니다. – user1829358

+0

대형 버퍼의 비 시간 (스트리밍) 이점을 보여주는 예제 벤치 마크를 추가하겠습니다. 그것은 조금 빠르고 빠르지 만, 나는 옳다고 생각합니다. 비 휴대용 (Windows) 타이밍 기능이 사용됩니다. – ScottD

+0

원본 게시물을 업데이트했지만 결과를 재현 할 수 없습니다 (코드를 Linux로 포팅해도 결과는 동일하게 유지됩니다). 왜 이것이 사실인지 아십니까? 또한 정렬되지 않은 버전이 더 빠른 이유에 대한 설명이 있습니까? 스트림이 정렬을 기대하기 때문에 실제로 이것은 실제 문제를 지적 할 수 있습니다. – user1829358

1

AFAIK 비 일시적인 저장 모든 캐시로부터 타겟 캐시 라인을 드롭. 라인이 자연적으로 떨어지기 전에 다시 터치되면, 당신은 꽤 열심히 길을 잃었습니다.

+0

그러나이 코드 스 니펫에서는 그렇지 않습니다. – user1829358

+0

나는 그것이 문제의 버퍼의 크기에 달려 있다고 말하려고했으나 ScottD에 대한 귀하의 의견은 그것들이 매우 큽니다. 이 시점에서 나는 무슨 일이 일어나고 있는지 잘 모르겠습니다. '#pragma'를 주석으로 처리하고'-xAVX'없이 컴파일하는 등 다양한 방법으로 시도해 볼 수 있습니다. 정규 및 비 임시 저장소 간의 성능 비율 변경을 찾습니다. – gsg