[assembly] x86이 못생긴 이유는 무엇입니까? 다른 사람과 비교할 때 왜 열등하다고 간주됩니까? [닫은]

최근에 나는 몇 가지 SO 아카이브를 읽고 x86 아키텍처에 대한 진술을 발견했습니다.

그리고 같은 더 많은 댓글

검색을 시도했지만 이유를 찾지 못했습니다. 내가 익숙한 유일한 아키텍처이기 때문에 x86이 나쁘다고 생각하지 않습니다.

누군가가 x86을 다른 사람에 비해 못생긴 / 나쁜 / 열등하다고 생각하는 이유를 친절하게 줄 수 있습니까?



답변

이에 대한 몇 가지 가능한 이유 :

  1. x86은 비교적 오래된 ISA입니다 (원조는 결국 8086이었습니다).
  2. x86은 여러 번 크게 발전했지만 이전 바이너리와의 하위 호환성을 유지하려면 하드웨어가 필요합니다. 예를 들어 최신 x86 하드웨어에는 기본적으로 16 비트 코드를 실행하기위한 지원이 여전히 포함되어 있습니다. 또한 리얼 모드, 보호 모드, 가상 8086 모드 및 (amd64) 롱 모드와 같은 이전 코드가 동일한 프로세서에서 상호 작용할 수 있도록 여러 메모리 주소 지정 모델이 있습니다. 이것은 일부 사람들에게 혼란 스러울 수 있습니다.
  3. x86은 CISC 시스템입니다. 오랫동안 이것은 MIPS 또는 ARM과 같은 RISC 시스템보다 느리다는 것을 의미했습니다. 명령어에는 데이터 상호 의존성과 플래그가 있어 대부분의 명령어 수준 병렬 처리 형식을 구현하기 어렵 기 때문입니다. 최신 구현에서는 x86 명령어를 ” micro-ops ” 라고하는 RISC와 유사한 명령어로 변환하여 이러한 종류의 최적화를 하드웨어에서 구현할 수 있도록합니다.
  4. 어떤면에서 x86은 열등하지 않고 단지 다릅니다. 예를 들어 입력 / 출력은 대부분의 아키텍처에서 메모리 매핑으로 처리되지만 x86에서는 처리되지 않습니다. (주의 : 현대 x86 컴퓨터는 일반적으로 어떤 형태가 DMA의 ,하지만 지원 및 메모리 매핑을 통해 다른 하드웨어와 통신 ISA는 여전히 같은 I / O 지침을 가지고 INOUT)
  5. x86 ISA 에는 아키텍처 레지스터가 매우 적기 때문에 프로그램이 필요한 것보다 더 자주 메모리를 통해 왕복 할 수 있습니다. 이를 수행하는 데 필요한 추가 지침은 효율적인 저장 전달 이지만 유용한 작업에 사용될 수있는 실행 리소스를 사용합니다.지연 시간을 낮게 유지합니다. 레지스터 이름을 큰 물리적 레지스터 파일로 변경하는 최신 구현은 많은 명령을 계속 사용할 수 있지만 아키텍처 레지스터의 부족은 여전히 ​​32 비트 x86의 중요한 약점이었습니다. x86-64의 8 개에서 16 개 정수 및 벡터 레지스터로의 증가는 64 비트 코드에서 32 비트보다 더 빠른 (더 효율적인 레지스터 호출 ABI와 함께) 가장 큰 요소 중 하나이며 각 레지스터의 너비가 증가한 것이 아닙니다. 16 개에서 32 개 정수 레지스터로 추가 증가하면 일부는 도움이되지만 그다지 많지는 않습니다. (AVX512는 32 개의 벡터 레지스터로 증가합니다. 부동 소수점 코드는 대기 시간이 더 길고 종종 더 많은 상수가 필요하기 때문입니다.) ( 주석 참조 )
  6. x86은 많은 기능을 가진 복잡한 아키텍처이기 때문에 x86 어셈블리 코드는 복잡합니다. 일반적인 MIPS 기계에 대한 지침 목록은 한 장의 Letter 크기의 종이에 맞습니다. x86에 해당하는 목록은 여러 페이지를 채우고 지침은 더 많은 작업을 수행하므로 목록에서 제공 할 수있는 것보다 더 큰 설명이 필요한 경우가 많습니다. 예를 들어, MOVSB명령어 가 수행하는 작업을 설명하려면 비교적 큰 C 코드 블록이 필요합니다.

    if (DF==0)
      *(byte*)DI++ = *(byte*)SI++;
    else
      *(byte*)DI-- = *(byte*)SI--;
    

    이는로드, 저장 및 두 개의 더하기 또는 빼기 (플래그 입력에 의해 제어 됨)를 수행하는 단일 명령어이며, 각각은 RISC 시스템에서 별도의 명령어입니다.

    MIPS (및 유사한 아키텍처)의 단순성이 반드시 이들을 우월하게 만드는 것은 아니지만 어셈블러 클래스에 대한 소개를 가르치려면 더 간단한 ISA 로 시작하는 것이 좋습니다 . 일부 어셈블리 클래스는 y86 이라는 매우 단순화 된 x86 서브 세트를 가르치는데 , 이는 실제 사용에 유용하지 않다는 점 (예 : 시프트 명령 없음)을 넘어 단순화되거나 일부는 기본 x86 명령 만 가르칩니다.

  7. x86은 가변 길이 opcode를 사용하여 명령어 구문 분석과 관련하여 하드웨어 복잡성을 추가합니다. 현대 시대에 CPU가 원시 계산보다 메모리 대역폭에 의해 점점 더 제한됨에 따라이 비용은 점점 작아지고 있지만 많은 “x86 bashing”기사와 태도는이 비용이 비교적 훨씬 더 큰 시대에서 비롯되었습니다.
    2016 업데이트 : Anandtech는 x64 및 AArch64 아래의 opcode 크기 에 대한 토론 을 게시했습니다 .

편집 : 이것은 x86을 bash 로되 어서 는 안됩니다 ! 파티. 나는 선택의 여지가 거의 없었지만 질문이 표현 된 방식을 감안할 때 약간의 강타를 할 수밖에 없었다. 그러나 (1)을 제외하고 이러한 모든 작업은 정당한 이유로 수행되었습니다 (댓글 참조). 인텔 디자이너는 어리석지 않습니다. 아키텍쳐로 몇 가지를 달성하고 싶었고, 이는 이러한 것들을 실현하기 위해 지불해야하는 세금 중 일부입니다.


답변

내 마음 속에서 x86에 대한 주요 노크는 CISC 기원입니다. 명령어 세트에는 많은 암시 적 상호 의존성이 포함되어 있습니다. 이러한 상호 종속성은 칩에서 명령 재정렬과 같은 작업을 수행하기 어렵게합니다. 이러한 상호 종속성의 아티팩트와 의미는 각 명령에 대해 보존되어야하기 때문입니다.

예를 들어, 대부분의 x86 정수 더하기 및 빼기 명령어는 플래그 레지스터를 수정합니다. 더하기 또는 빼기를 수행 한 후 다음 작업은 종종 플래그 레지스터를 확인하여 오버플로, 부호 비트 등을 확인하는 것입니다. 그 후에 다른 더하기가 있으면 두 번째 더하기 실행을 시작하는 것이 안전한지 여부를 알기가 매우 어렵습니다. 첫 번째 추가의 결과가 알려지기 전에.

RISC 아키텍처에서 추가 명령어는 입력 피연산자와 출력 레지스터를 지정하며 연산에 대한 모든 것은 해당 레지스터 만 사용하여 발생합니다. 이렇게하면 모든 항목을 정렬하고 단일 파일을 실행하도록하는 블루밍 플래그 레지스터가 없기 때문에 서로 가까운 추가 작업을 훨씬 쉽게 분리 할 수 ​​있습니다.

MIPS 스타일의 RISC 설계 인 DEC Alpha AXP 칩은 사용 가능한 명령어에서 매우 스파르타 적이었지만 명령어 세트는 명령어 간 암시 적 레지스터 종속성을 피하기 위해 설계되었습니다. 하드웨어 정의 스택 레지스터가 없습니다. 하드웨어 정의 플래그 레지스터가 없습니다. 명령 포인터조차도 OS에 정의되어 있습니다. 호출자에게 돌아가려면 호출자가 반환 할 주소를 알려주는 방법을 알아 내야했습니다. 이것은 일반적으로 OS 호출 규칙에 의해 정의되었습니다. 하지만 x86에서는 칩 하드웨어에 의해 정의됩니다.

어쨌든, 3 ~ 4 세대에 걸친 Alpha AXP 칩 설계에서 하드웨어는 32 개의 int 레지스터와 32 개의 부동 레지스터로 구성된 스파르탄 명령어 세트의 문자 적 ​​구현에서 80 개의 내부 레지스터, 레지스터 이름 변경, 결과 전달 (이전 명령어의 결과가 값에 따라 나중 명령어로 전달되는 경우) 및 모든 종류의 거칠고 미친 성능 부스터. 그리고 이러한 모든 종소리와 휘파람으로 AXP 칩 다이는 그 당시의 비슷한 펜티엄 칩 다이보다 훨씬 작았으며 AXP는 훨씬 더 빨랐습니다.

x86 명령어 세트의 복잡성으로 인해 많은 종류의 실행 최적화가 불가능하지는 않더라도 엄청나게 많은 비용이 들기 때문에 x86 패밀리 트리에서 이러한 종류의 성능 급증이 크게 향상되지는 않습니다. 인텔의 천재성은 더 이상 하드웨어에서 x86 명령 세트를 구현하는 것을 포기하는 데있었습니다. 모든 최신 x86 칩은 실제로 x86 명령을 어느 정도 해석하여 원래 x86의 모든 의미를 보존하는 내부 마이크로 코드로 변환하는 RISC 코어입니다. 그러나 마이크로 코드에 대한 RISC 비 순차 및 기타 최적화를 약간 허용합니다.

저는 x86 어셈블러를 많이 작성했으며 CISC 루트의 편리함을 충분히 이해할 수 있습니다. 그러나 Alpha AXP 어셈블러를 작성하는 데 시간을 할애하기 전까지는 x86이 얼마나 복잡한 지 충분히 이해하지 못했습니다. 나는 AXP의 단순성과 균일성에 매료되었습니다. 그 차이는 엄청나고 심오합니다.


답변

x86 아키텍처는 8008 마이크로 프로세서 및 친척의 설계에서 시작되었습니다. 이 CPU는 메모리가 느린시기에 설계되었으며 CPU 다이에서 할 수 있다면 훨씬 더 빠릅니다. 그러나 CPU 다이 공간도 비쌌습니다. 이 두 가지 이유는 특별한 목적을 갖는 경향이있는 적은 수의 레지스터와 모든 종류의 문제와 제한이있는 복잡한 명령어 세트가있는 이유입니다.

같은 시대의 다른 프로세서 (예 : 6502 제품군)도 비슷한 제한과 단점이 있습니다. 흥미롭게도 8008 시리즈와 6502 시리즈는 모두 임베디드 컨트롤러로 설계되었습니다. 그 당시에도 임베디드 컨트롤러는 어셈블러로 프로그래밍 될 것으로 예상되었으며 여러면에서 컴파일러 작성자가 아닌 어셈블리 프로그래머에게 적합했습니다. (컴파일러 작성을 수용 할 때 어떤 일이 발생하는지 VAX 칩을보십시오.) 설계자는 이들이 범용 컴퓨팅 플랫폼이 될 것이라고 기대하지 않았습니다. 이것이 바로 POWER 아키텍처의 전임자와 같은 것입니다. 물론 가정용 컴퓨터 혁명은 그것을 바꾸어 놓았습니다.


답변

여기에 몇 가지 추가 측면이 있습니다.

작업 “a = b / c”x86이이를 다음과 같이 구현한다고 생각해보십시오.

  mov eax,b
  xor edx,edx
  div dword ptr c
  mov a,eax

div 명령의 추가 보너스로 edx는 나머지를 포함합니다.

RISC 프로세서는 먼저 b와 c의 주소를로드하고, b와 c를 메모리에서 레지스터로로드하고, 분할을 수행하고 a의 주소를로드 한 다음 결과를 저장해야합니다. Dst, src 구문 :

  mov r5,addr b
  mov r5,[r5]
  mov r6,addr c
  mov r6,[r6]
  div r7,r5,r6
  mov r5,addr a
  mov [r5],r7

여기에는 일반적으로 나머지가 없습니다.

포인터를 통해 변수를로드해야하는 경우 두 시퀀스 모두 더 길어질 수 있지만 다른 레지스터에 이미로드 된 포인터가 하나 이상있을 수 있기 때문에 RISC에 대한 가능성은 적습니다. x86은 레지스터가 적기 때문에 포인터가 그중 하나에있을 가능성이 더 적습니다.

장점과 단점:

RISC 명령어는 명령어 스케줄링을 개선하기 위해 주변 코드와 혼합 될 수 있습니다. x86에서는 대신 CPU 자체 내에서이 작업을 수행하는 (순서에 따라 다소 잘) 가능성이 적습니다. 위의 RISC 시퀀스는 일반적으로 32 비트 아키텍처에서 28 바이트 길이 (각각 32 비트 / 4 바이트 너비의 명령어 7 개)입니다. 이렇게하면 명령어를 가져올 때 (7 회 가져 오기) 오프 칩 메모리가 더 많이 작동합니다. 밀도가 높은 x86 시퀀스에는 더 적은 명령어가 포함되어 있으며 너비는 다양하지만 평균 4 바이트 / 명령어도 여기에서 볼 수 있습니다. 7 번의 가져 오기 속도를 높이기위한 명령어 캐시가 있어도 x86과 비교하여 다른 곳에서 3 개의 부족한 부분이 있음을 의미합니다.

저장 / 복원 할 레지스터가 더 적은 x86 아키텍처는 아마도 스레드 전환을 수행하고 RISC보다 빠르게 인터럽트를 처리 할 것임을 의미합니다. 저장 및 복원 할 레지스터가 많을수록 인터럽트를 수행하는 데 더 많은 임시 RAM 스택 공간이 필요하고 스레드 상태를 저장하기위한 더 영구적 인 스택 공간이 필요합니다. 이러한 측면은 x86을 순수한 RTOS 실행에 더 적합한 후보로 만들 것입니다.

좀 더 개인적인 메모에서 x86보다 RISC 어셈블리를 작성하는 것이 더 어렵다는 것을 알았습니다. RISC 루틴을 C로 작성하고 생성 된 코드를 컴파일하고 수정하여이 문제를 해결합니다. 이것은 코드 생산 관점에서는 더 효율적이고 실행 관점에서는 덜 효율적입니다. 추적해야 할 모든 32 개의 레지스터. x86에서는 그 반대입니다. “실제”이름을 가진 6-8 개의 레지스터를 사용하면 문제를보다 쉽게 ​​관리 할 수 ​​있고 생성 된 코드가 예상대로 작동 할 것이라는 확신을 갖게됩니다.

추한? 그것은 보는 사람의 눈에 있습니다. 나는 “다른”을 선호합니다.


답변

이 질문에는 잘못된 가정이 있다고 생각합니다. x86을 추악하다고 부르는 것은 주로 RISC에 집착하는 학자들입니다. 실제로 x86 ISA는 RISC ISA에서 5-6 개의 명령어를 사용하는 단일 명령어 작업으로 수행 할 수 있습니다. RISC 팬은 최신 x86 CPU가 이러한 “복잡한”명령을 마이크로 옵스로 분해한다고 반박 할 수 있습니다. 하나:

  1. 많은 경우에 그것은 부분적으로 만 사실이거나 전혀 사실이 아닙니다. x86에서 가장 유용한 “복잡한”명령어는 mov %eax, 0x1c(%esp,%edi,4)주소 지정 모드 와 같은 것 입니다.
  2. 현대 기계에서 종종 더 중요한 것은 소비 된주기의 수 (대부분의 작업이 CPU 바인딩이 아니기 때문에)가 아니라 코드의 명령 캐시 영향입니다. 5-6 개의 고정 크기 (일반적으로 32 비트) 명령어는 5 바이트를 넘지 않는 복잡한 명령어 하나 이상에 캐시에 영향을줍니다.

x86은 약 10 ~ 15 년 전에 RISC의 모든 좋은 측면을 실제로 흡수했으며, RISC의 나머지 특성 (실제로 정의하는 것-최소 명령 집합)은 해롭고 바람직하지 않습니다.

CPU 제조의 비용과 복잡성과 에너지 요구 사항 외에도 x86은 최고의 ISA 입니다. 다르게 말하는 사람은 이데올로기 나 의제가 그들의 추론을 방해하는 것입니다.

반면에 CPU 비용이 중요한 임베디드 장치 또는 에너지 소비가 가장 중요한 임베디드 / 모바일 장치를 대상으로하는 경우 ARM 또는 MIPS가 더 적합 할 것입니다. 쉽게 3 ~ 4 배 더 큰 코드를 처리하는 데 필요한 추가 램과 바이너리 크기를 처리해야하며 성능에 근접 할 수는 없습니다. 이것이 중요한지는 당신이 무엇을 실행할 것인지에 달려 있습니다.


답변

x86 어셈블러 언어는 그렇게 나쁘지 않습니다. 기계 코드에 도달했을 때 정말 추악 해지기 시작합니다. 명령어 인코딩, 주소 지정 모드 등은 대부분의 RISC CPU보다 훨씬 더 복잡합니다. 그리고 이전 버전과의 호환성을 위해 내장 된 추가 재미가 있습니다. 프로세서가 특정 상태에있을 때만 시작되는 기능입니다.

예를 들어, 16 비트 모드에서 주소 지정은 완전히 이상하게 보일 수 있습니다. 에 대한 주소 지정 모드가 [BX+SI]있지만 [AX+BX]. 필요에 따라 사용할 수있는 레지스터에 값이 있는지 확인해야하기 때문에 이와 같은 것은 레지스터 사용을 복잡하게 만드는 경향이 있습니다.

(다행히도 32 비트 모드는 훨씬 더 정상적이며 (예를 들어 세분화와 같이 때때로 다소 이상하지만) 16 비트 x86 코드는 더 이상 부트 로더 및 일부 임베디드 환경과 관련이 없습니다.)

인텔이 x86을 최고의 프로세서로 만들려고했던 옛날부터 남은 것들도 있습니다. 아무도 실제로 더 이상 수행하지 않는 작업을 수행하는 몇 바이트 길이의 명령은 솔직히 너무 느리거나 복잡하기 때문입니다. 두 가지 예에 대한 ENTER 및 LOOP 명령어 -C 스택 프레임 코드는 대부분의 컴파일러에서 “enter”가 아니라 “push ebp; mov ebp, esp”와 같습니다.


답변

나는 전문가는 아니지만 사람들이 좋아하지 않는 많은 기능이 성능이 좋은 이유 일 수 있습니다. 몇 년 전에는 레지스터 (스택 대신), 레지스터 프레임 등이 아키텍처를 인간에게 더 단순하게 보이게하는 좋은 솔루션으로 간주되었습니다. 그러나 요즘 중요한 것은 캐시 성능이며 x86의 가변 길이 단어를 사용하면 캐시에 더 많은 명령을 저장할 수 있습니다. 반대자들이 한때 칩의 절반을 차지했다고 지적했던 “명령 디코딩”은 더 이상 그렇게 많이되지 않습니다.

저는 병렬 처리가 오늘날 가장 중요한 요소 중 하나라고 생각합니다. 적어도 이미 사용할 수있을만큼 충분히 빠르게 실행되는 알고리즘의 경우입니다. 소프트웨어에서 높은 병렬성을 표현하면 하드웨어가 메모리 대기 시간을 분할 (또는 완전히 숨길 수 있음) 할 수 있습니다. 물론 더 먼 미래의 아키텍처는 아마도 양자 컴퓨팅과 같은 것입니다.

nVidia로부터 Intel의 실수 중 하나는 바이너리 형식을 하드웨어에 가깝게 유지했다는 것입니다. CUDA의 PTX는 빠른 레지스터 사용 계산 (그래프 색상)을 수행하므로 nVidia는 스택 머신 대신 레지스터 머신을 사용할 수 있지만 여전히 모든 오래된 소프트웨어를 손상시키지 않는 업그레이드 경로를 가지고 있습니다.