2013-08-31 1 views
2

32 비트 CPU 모드부터 x86 아키텍처에서 사용할 수있는 확장 주소 피연산자가 있습니다. 베이스 주소, 변위, 인덱스 레지스터 및 스케일링 인수를 지정할 수 있습니다.주소 피연산자는 기계 코드의 성능과 크기에 어떤 영향을 줍니까?

예를 들어, 32 비트 정수리스트를 보폭 싶은 (32 바이트 길이의 데이터 구조의 어레이로부터 모든 처음 두 %rdi 데이터 인덱스,베이스 포인터 %rbx 등).

addl $8, %rdi    # skip eight values: advance index by 8 
movl (%rbx, %rdi, 4), %eax # load data: pointer + scaled index 
movl 4(%rbx, %rdi, 4), %edx # load data: pointer + scaled index + displacement 

아시다시피 이러한 복잡한 주소 지정은 단일 기계 코드 명령어에 적합합니다. 그러나 이러한 작업의 비용은 어떻게 독립 포인터 계산을 해결 간단한 비교 않습니다 후자의 예에서

addl $32, %rbx  # skip eight values: move pointer forward by 32 bytes 
movl (%rbx), %eax # load data: pointer 
addl $4, %rbx  # point next value: move pointer forward by 4 bytes 
movl (%rbx), %edx # load data: pointer 

, 나는 하나의 추가 명령 및 종속성을 도입했습니다. 그러나 정수 추가는 매우 빠르며, 더 간단한 주소 피연산자를 얻었으며 더 이상 곱셈이 없습니다. 반면에, 허용 된 스케일링 인자는 2의 거듭 제곱이기 때문에, 곱셈은 비트 쉬프트로 내려 가고, 이것은 또한 매우 빠른 연산입니다. 여전히, 두 개의 덧셈과 비트 쉬프트는 하나의 덧셈으로 대체 될 수 있습니다.

이 두 가지 방법의 성능 및 코드 크기 차이점은 무엇입니까? 확장 된 주소 피연산자를 사용하는 모범 사례가 있습니까?

또는 C 프로그래머의 관점에서 묻는 질문 : 배열 인덱싱 또는 포인터 산술이란 무엇입니까?


는 어셈블리 편집기 크기/성능 튜닝 거기에 의미인가? 각 어셈블리 명령어의 기계 코드 크기, 클럭 사이클에서의 실행 시간 또는 종속 그래프를 볼 수 있었으면 좋겠습니다. 그러한 응용 프로그램의 혜택을받을 수있는 어셈블리 괴물 수천명이 있습니다, 그래서 나는 이미 이와 같은 것이 존재한다고 확신합니다!

+1

일반적인 대답 # 0 : 최적화는 부두입니다. 지침을 추가하거나 긴 지침을 사용하는 등의 작업은 속도가 빨라질 수 있습니다. 이러한 동작은 CPU마다 다를 수 있습니다. 새로운 모델에서 한 모델에 맞는 것이 사실이 아닐 수도 있습니다. 귀하의 경우 상황은 어느 방향 으로든 갈 수 있으며 단순히 측정하지 않고 예측할 수있는 좋은 방법이 없습니다. – Nayuki

+1

일반 답변 # 1 : http://www.agner.org/optimize/; http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-optimization-manual.html – Nayuki

+0

@NayukiMinase, 일부 유용한 링크. 매우 가치있는 탐색. 감사. – TerryE

답변

1

주소 계산은 매우 빠르며 가능하면 항상 사용해야합니다.

하지만 여기에는 질문이 누락되었습니다.

처음에 주소 산술을 사용하여 32를 곱할 수 없습니다. 가능한 최대 상수는 8입니다.

rbx을 증가시키는 두 번째 명령이 필요하므로 코드의 첫 번째 버전이 완료되지 않았습니다. 그래서, 우리는 두 가지 변종을 다음 있습니다 :

inc rbx   
mov eax, [8*rbx+rdi] 

이 방법

add rbx, 8 
mov eax, [rbx] 

대, 두 변종의 속도는 동일합니다. 크기는 6 바이트입니다.

그럼, 코드가 더 낫다는 프로그램의 상황에 따라 달라집니다 - 우리는 이미 필요한 배열 셀의 주소가 들어있는 레지스터가있는 경우 - 사용 mov 인 EAX를, [RBX]을

우리가 레지스터 포함이있는 경우 셀의 인덱스와 시작 주소를 포함하는 다른 인덱스를 사용하여 첫 번째 변형을 사용하십시오. 이렇게하면 알고리즘이 끝난 후에도 rdi에 배열의 시작 주소가 생깁니다.

+0

복잡한 주소 피연산자 (및 그 뒤에있는 회로)는 액세서리, 주소 계산 명령의 필요성을 없애기 위해 도입되어 많은 경우 코드를 압축하고 명령 수준 종속성을 줄입니다. 그래서, 이미 준비된 포인터가 없다면, 일반적으로 복잡한 주소 지정이 바람직합니다. –

+0

@KrzysztofAbramowicz 정확히. 투표를하고 원하는 답변을 수락하는 것을 잊지 마십시오. ;) – johnfound

+0

곱셈에 관해서는 실수가 없지만 예제에서는 반복에 대해 8 개의 단위를 '% rdi'가 더해서 스케일링 요소가 4이지만 데이터에 선형으로 액세스하지 않는다는 (불필요한) 가정을 도입합니다. 첫 번째 변형에 루프 카운터 승격 명령을 제공하는 것을 잊지 않았다면 분명했습니다. 나는 당신의 발언을 제 질문에 적용 할 것입니다. –

2

질문에 대한 답변은 주어진 로컬 프로그램 흐름 상황에 따라 달라지며, 프로세서 제조업체와 아키텍처간에 다소 차이가있을 수 있습니다. 단일 지시 또는 2 지시를 미세 분석하는 것은 일반적으로 무의미합니다. 멀티 스테이지 파이프 라인, 하나 이상의 정수 단위, 캐시 및 훨씬 더 많은 기능이 제공되며 분석을 고려해야합니다.

생성 된 어셈블리 코드를보고 작업 할 다른 하드웨어 장치와 관련하여 시퀀스가 ​​보이는 이유를 분석하여 리버스 엔지니어링을 시도 할 수 있습니다.

또 다른 방법은 프로파일러를 사용하고 다른 구조로 어떤 것이 효과적인지, 그렇지 않은지 확인하는 것입니다.

gcc의 소스 코드를 다운로드하여 정말 멋진 프로그래머가 가능한 가장 빠른 코드를 생성하는 방법을 평가할 수 있습니다. 언젠가 당신이 그들 중 하나가 될 수도 있습니다 :-)

어떤 경우 든 최상의 순서는 프로세서, 컴파일러, 최적화 수준 및 주변 지침에 따라 크게 달라질 것으로 예상됩니다. C를 사용하는 경우 소스 코드의 품질이 매우 중요합니다. 쓰레기 값 = 쓰레기 값.

+0

맞습니다 - 제 질문에 대한보다 폭 넓은 예제를 제공 했어야합니다. 필자의 의도는 동일한 레벨에서 실행 된 독립적 인 ALU 기반 계산과의 비교를 통해 명령 수준 주소 산술 성능 (파이프 라인의 다른 단계에서 전용 장치에 의해 수행됨)에 초점을 맞추는 것이 었습니다. 그럼에도 불구하고이 문제는 편집을 통해 밝혀 낼 더 큰 문제에서 나왔습니다. –

+0

비교를위한 컨텍스트는 명령이 실행될 때까지 각 유닛이 바로 수행 한 것입니다. 최적화 된 코드를 생성하는 훌륭한 컴파일러는 파이프 라인의 어떤 유닛이이 명령어 또는 그 명령어로 작업 할 수 있었는지를 추적하고 작업 완료 시점을 정확하게 예측합니다. 가장 잘 작동하는 시나리오를 찾기 위해 여러 가지 지침 시나리오를 시도합니다 (가장 빠른 실행). 제한된 컨텍스트를 선택하면 실용적이지 않습니다. –

+1

다른 프로세서/아키텍처를 비교할 때 ALU 또는 FPU 효율성을 결정하는 것이 제안하는 것과 같은 테스트가 유용한 한 곳에서 유용합니다. 이 문서 (http://gmplib.org/~tege/x86-timing.pdf)에서 특정 x86 아키텍처가 개별 명령어와 그 병렬 처리 수준을 처리 할 수있는 속도를 확인할 수 있습니다. –

관련 문제