2017-11-19 3 views
1

내 프로젝트는 Windows와 Linux 모두에서 32 비트 용으로 컴파일됩니다. 이것은 그러나하지 않습니다x86-32 용 MSVC++로 작은 구조체를 초기화하기위한 효율적인 asm을 얻는 방법은 무엇입니까?

#define NULL_VALUE_LITERAL {0, {0L}}; 
static const Value NULL_VALUE = NULL_VALUE_LITERAL; 

// example of clearing a value 
var = NULL_VALUE; 

: 나는과 같이 수행 구조체를 제로로 할 필요가 많은 장소에서

struct Value { 
    unsigned char type; 
    union { // 4 bytes 
     unsigned long ref; 
     float num; 
    } 
}; 

: 난 그냥 사방에 사용되는 8 바이트 구조체를 모든 최적화가 설정된 상태에서도 Visual Studio 2013에서 가장 효율적인 코드로 컴파일 할 수 있습니다. 어셈블리에서 볼 수있는 것은 NULL_VALUE에 대한 메모리 위치를 읽은 다음 var에 쓴다는 것입니다. 결과적으로 두 개의 메모리 읽기와 두 개의 메모리 쓰기가 수행됩니다. 그러나 이러한 정리 작업은 시간에 민감한 루틴에서도 많이 발생하며 최적화를 위해 찾고 있습니다.

값을 NULL_VALUE_LITERAL로 설정하면 상황이 더 나 빠집니다. 다시 모두 0 인 리터럴 데이터는 임시 스택 값으로 복사 된 다음 변수가 스택에있는 경우에도 변수에 복사됩니다. 그래서 그건 터무니없는 일입니다.

일반적인 상황이 이렇게도있다 :

*pd->v1 = NULL_VALUE; 

그것은 위의 VAR = NULL_VALUE 유사 어셈블리 코드를 가지고 있지만, 그것이 내가 그 길을가는 것을 선택해야 내가 인라인 어셈블리 최적화 할 수있는 일입니다. 내 연구에서

이 같은 것 메모리를 지우려면 매우 빠른 방법 : 구조체의 정렬이 데이터 유형 후 3 바이트 단지 쓰레기가 있다는 뜻하기 때문에, 더 나은 여전히 ​​

xor eax, eax 
mov byte ptr [var], al 
mov dword ptr [var+4], eax 

또는 :

xor eax, eax 
mov dword ptr [var], eax 
mov dword ptr [var+4], eax 

당신은 내가 메모리가 완전히 불필요한 것을 읽어 방지하기 위해 최적화 된 것과 유사한 코드를 얻을 수있는 방법을 생각할 수 있는가?

두 가지 주소에 32 비트 0 리터럴을 쓰는 코드가 과도하게 많이 생성되는 것으로 끝내는 몇 가지 다른 방법을 시도했지만 IIRC는 메모리에 리터럴을 쓰는 것이 여전히 레지스터 작성만큼 빠르지 않습니다. 메모리로. 나는 내가 얻을 수있는 여분의 성과를 내기 위해 찾고있다.

이상적인 결과는 매우 유용 할 것입니다. 귀하의 도움을 주시면 감사하겠습니다.

+2

"메모리에 리터럴을 쓰는 것이 여전히 메모리에 기록하는 것만 큼 빠릅니다 *"- 사실이 아닙니다. 그리고'memset'을 시도해 보셨습니까? – rustyx

+0

'* ((long long *) & var) = 0LL;은 어떻게됩니까? – xs0

+0

@ xs0 - 이것은 플랫폼 및 할당 방법에 따라 정렬에 문제가 발생할 수 있습니다 ... – immortal

답변

1

float으로 노조의 경우 uint32_t 또는 unsigned int을 권하고 싶습니다. Linux x86-64의 long은 64 비트 유형이므로 원하는 것은 아닙니다.


내가 MSVC CL19 x86-32 및 x86-64에 대한 -Oxon the Godbolt compiler explorer로 놓친 최적화를 재현 할 수 있습니다. CL19 작동 해결 방법 :

  • 대신 charunsigned inttype을, 그래서 패딩이 구조체에 없다, 다음 문자 {0, {0L}} 대신 static const Value 객체로부터 할당합니다. (그러면 두 개의 즉석 상점이 있습니다 : mov DWORD PTR [eax], 0/mov DWORD PTR [eax+4], 0).

    gcc도 구조체에서 패딩을 사용하여 구조체 누락 최적화가 있지만 MSVC (Bug 82142)만큼 나쁘지는 않습니다. 더 넓은 상점으로의 합병을 막을 수 있습니다. gcc가 스택에 객체를 만들고 그 객체로부터 복사하는 것은 아닙니다.

  • std::memset : 아마도 가장 좋은 옵션, MSVC는 SSE2를 사용하여 단일 64 비트 저장소로 컴파일합니다. xorps xmm0, xmm0/movq QWORD PTR [mem], xmm0. (gcc -m32 -O3는. 두 mov -immediate 저장이 memset 함수를 컴파일)

void arg_memset(Value *vp) { 
    memset(vp, 0, sizeof(gvar)); 
} 

    ;; x86 (32-bit) MSVC -Ox 
    mov  eax, DWORD PTR _vp$[esp-4] 
    xorps xmm0, xmm0 
    movq  QWORD PTR [eax], xmm0 
    ret  0 

이 나는 ​​현대의 CPU (인텔과 AMD)에 대한 선택 줄 것입니다. 캐시 라인을 건너는 것에 대한 벌칙은 항상 발생하지 않으면 명령을 저장할 가치가 충분히 낮습니다. xor-zeroing은 매우 저렴합니다 (특히 Intel SnB 계열). IIRC 메모리에 문자를 작성


는 여전히 명령에 포함 된 상수가 즉시 데이터라고 빨리 ASM에서 메모리

에 레지스터를 쓰기로하지 않습니다. mov - 메모리에 대한 직접적인 접근은 x86에서 대부분 괜찮지 만, 코드 크기에 비해 약간 부풀어 오른다.

(x86-64 전용) : RIP 상대 주소 지정 모드와 즉각적인 기능을 갖춘 저장소는 인텔 CPU에서 마이크로 퓨즈 할 수 없으므로 2 개의 통합 된 도메인 uops입니다. (Agner Fog's microarch pdf 태그 위키의 다른 링크를 참조하십시오.) 이것은 RIP 상대 주소에 둘 이상의 저장소를 사용하는 경우 프런트 엔드 대역폭을 위해 레지스터를 0으로 설정하는 것이 좋습니다. 하지만 다른 어드레싱 모드가 융합되어 코드 크기 문제 일뿐입니다.

관련 항목 : Micro fusion and addressing modes (Sandybridge/Ivybridge에서는 인덱싱 된 주소 지정 모드가 적용되지 않지만 나중에 Haswell에서는 인덱싱 된 저장소를 마이크로 융합 상태로 유지할 수 있습니다.) 이것은 즉각 대 레지스터 소스에 종속되지 않습니다.


나는 이것은 단지 8 바이트 구조체이기 때문에 memset 함수는 매우 가난한 적합 할 것이라고 생각합니다.

현대 컴파일러는 일부 많이 사용되는/중요한 표준 라이브러리 함수 (memset, memcpy 등) 무엇을 알고 있으며, 내장 함수처럼 취급합니다. 동일한 유형의 최적화가 있다면 a = bmemcpy(&a, &b, sizeof(a)) 사이에는 거의 차이가 없습니다.

디버그 모드에서 실제 라이브러리 구현에 대한 함수 호출이 발생할 수 있지만 디버그 모드는 매우 느립니다. 디버그 모드 성능 요구 사항이있는 경우 이는 드문 경우입니다. (그러나 다른 것을 따라 잡아야하는 코드에서 발생합니다 ...)

+0

내부적으로 노동 조합은 4 바이트 값으로 올바르게 정의되므로 걱정할 필요가 없습니다. 유형 값을 4 비트로 변경하는 것은 나에게 일어 났지만 프로젝트에서이 크기와 복잡성으로 인해 무언가를 깨뜨릴 수 있다고 생각합니다. memset 옵션에 대해서는 실제로 8 바이트로 정렬되지 않은 경우 SSE2에서 qword 포인터를 사용하는 것이 실제로 안전합니까? –

+0

@ LummoxJR : 예; 그렇지 않은 경우 컴파일러는이 최적화를 수행하지 않습니다. 16 바이트 이상의 큰로드/저장소 만이 x86에 "movq"와 같은 "일반"로드/저장 명령어를 갖는 정렬 요구 사항을 갖습니다. 그래서 'movqa'와 'movqu' 명령어 ('movdqu'와 같은)가 별도로 없으며 단지'movq'만이 존재합니다.이것이 내가 캐시 라인 경계를 넘는 상점에서 가능한 성능 문제를 언급 한 이유입니다. 이러한 객체 중 하나가 4 바이트로만 정렬되고 캐시 라인을 통해 분할되는 경우입니다. 자연스럽게 정렬 된 액세스는 자신의 크기보다 큰 경계를 넘을 수 없습니다. –

관련 문제