Recentemente, li alguns arquivos SO e encontrei declarações contra a arquitetura x86.
Por que precisamos de arquitetura de CPU diferente para servidor e mini / mainframe e núcleo misto? diz que
"a arquitetura do PC é uma bagunça, qualquer desenvolvedor de sistema operacional diria isso. "Vale o esforço aprender a linguagem assembly?( arquivado ) diz
" Perceba que a arquitetura x86 é horrível na melhor das hipóteses "Alguma maneira fácil de aprender assembler x86? diz
" A maioria das faculdades ensinam montagem em algo como MIPS porque é muito mais simples de entender, montagem x86 é realmente feia "
e muitos mais comentários como
"Comparado com a maioria das arquiteturas, o X86 é péssimo."
" É definitivamente o senso comum que o X86 é inferior a MIPS, SPARC e PowerPC "
Tentei pesquisar, mas não encontrei nenhum motivo. Não acho o x86 ruim provavelmente porque essa é a única arquitetura com a qual estou familiarizado.
Alguém pode gentilmente me dar razões para considerar o x86 feio / ruim / inferior em comparação com os outros.
Respostas:
Algumas razões possíveis para isso:
IN
eOUT
)O código assembly x86 é complicado porque x86 é uma arquitetura complicada com muitos recursos. Uma lista de instruções para uma máquina MIPS típica cabe em um pedaço de papel do tamanho de uma carta. A listagem equivalente para x86 preenche várias páginas, e as instruções apenas fazem mais, então você geralmente precisa de uma explicação maior do que uma listagem pode fornecer. Por exemplo, a
MOVSB
instrução precisa de um bloco relativamente grande de código C para descrever o que faz:Essa é uma única instrução fazendo um carregamento, um armazenamento e duas adições ou subtrações (controladas por uma entrada de sinalizador), cada uma das quais seriam instruções separadas em uma máquina RISC.
Embora a simplicidade do MIPS (e arquiteturas semelhantes) não os torne necessariamente superiores, para ensinar uma introdução à classe assembler faz sentido começar com um ISA mais simples . Algumas classes de montagem ensinam um subconjunto ultra-simplificado de x86 chamado y86 , que é simplificado além do ponto de não ser útil para uso real (por exemplo, sem instruções de deslocamento), ou algumas ensinam apenas as instruções básicas de x86.
Atualização de 2016: Anandtech postou uma discussão sobre tamanhos de opcode em x64 e AArch64 .
EDIT: Isso não deveria ser um bash no x86! festa. Eu tive pouca escolha a não ser criticar, dada a forma como a pergunta foi formulada. Mas, com exceção de (1), todas essas coisas foram feitas por bons motivos (ver comentários). Os designers da Intel não são estúpidos - eles queriam conseguir algumas coisas com sua arquitetura, e esses são alguns dos impostos que eles tiveram que pagar para tornar essas coisas uma realidade.
fonte
O principal golpe contra o x86 em minha mente são suas origens CISC - o conjunto de instruções contém muitas interdependências implícitas. Essas interdependências tornam difícil fazer coisas como reordenar instruções no chip, porque os artefatos e a semântica dessas interdependências devem ser preservados para cada instrução.
Por exemplo, a maioria das instruções de adição e subtração de inteiros x86 modificam o registro de sinalizadores. Depois de realizar uma adição ou subtração, a próxima operação geralmente é olhar o registrador de sinalizadores para verificar o estouro, bit de sinal, etc. Se houver outra adição depois disso, é muito difícil dizer se é seguro começar a execução da segunda adição antes que o resultado da primeira adição seja conhecido.
Em uma arquitetura RISC, a instrução add especificaria os operandos de entrada e o (s) registrador (es) de saída, e tudo sobre a operação ocorreria usando apenas esses registradores. Isso torna muito mais fácil desacoplar as operações de adição que estão próximas umas das outras porque não há nenhum registro de flags do bloomin 'forçando tudo a se alinhar e executar um único arquivo.
O chip DEC Alpha AXP, um projeto RISC estilo MIPS, era dolorosamente espartano nas instruções disponíveis, mas o conjunto de instruções foi projetado para evitar dependências implícitas de registro entre instruções. Não havia registro de pilha definido por hardware. Não havia registro de sinalizadores definidos por hardware. Até mesmo o ponteiro de instrução foi definido pelo sistema operacional - se você quisesse retornar para o chamador, tinha que descobrir como o chamador iria informá-lo para qual endereço retornar. Isso geralmente era definido pela convenção de chamada do sistema operacional. No x86, porém, é definido pelo hardware do chip.
De qualquer forma, ao longo de 3 ou 4 gerações de designs de chip Alpha AXP, o hardware deixou de ser uma implementação literal do conjunto de instruções espartano com 32 registros int e 32 registros flutuantes para um mecanismo de execução totalmente fora de ordem com 80 registros internos, renomeação de registro, encaminhamento de resultados (onde o resultado de uma instrução anterior é encaminhado para uma instrução posterior que depende do valor) e todos os tipos de impulsionadores de desempenho selvagens e loucos. E com todos esses sinos e assobios, o chip AXP ainda era consideravelmente menor do que o chip Pentium comparável da época, e o AXP era muito mais rápido.
Você não vê esses tipos de picos de desempenho aumentando as coisas na árvore genealógica do x86, principalmente porque a complexidade do conjunto de instruções do x86 torna muitos tipos de otimizações de execução proibitivamente caras, senão impossíveis. A genialidade da Intel foi desistir mais de implementar o conjunto de instruções x86 no hardware - todos os chips x86 modernos são, na verdade, núcleos RISC que, até certo ponto, interpretam as instruções x86, traduzindo-as em microcódigo interno que preserva toda a semântica do x86 original instrução, mas permite um pouco daquele RISC fora de ordem e outras otimizações sobre o microcódigo.
Eu escrevi muito sobre x86 assembler e posso apreciar totalmente a conveniência de suas raízes CISC. Mas eu não apreciei totalmente o quão complicado o x86 era até passar algum tempo escrevendo o assembler Alpha AXP. Fiquei pasmo com a simplicidade e uniformidade do AXP. As diferenças são enormes e profundas.
fonte
add
após o outroadd
. As regras são claras. Também não há necessidade de você lidar com a reordenação de instruções. Desde o Pentium Pro em meados dos anos 90, a CPU faz isso por você. O que você está mencionando pode ter sido um problema há 20 anos, mas não vejo nenhuma razão para considerá-lo contra a arquitetura x86 hoje em dia.A arquitetura x86 data do design do microprocessador 8008 e parentes. Essas CPUs foram projetadas em uma época em que a memória era lenta e se você pudesse fazer isso no chip da CPU, muitas vezes era um muito mais rápido. No entanto, o espaço da CPU também era caro. Essas duas razões são porque há apenas um pequeno número de registradores que tendem a ter propósitos especiais e um conjunto de instruções complicado com todos os tipos de pegadinhas e limitações.
Outros processadores da mesma época (por exemplo, a família 6502) também têm limitações e peculiaridades semelhantes. Curiosamente, tanto a série 8008 quanto a série 6502 foram concebidas como controladores incorporados. Mesmo naquela época, esperava-se que os controladores incorporados fossem programados em assembler e, de muitas maneiras, fornecidos ao programador de assembly, em vez de ao redator do compilador. (Veja no chip VAX o que acontece quando você atende à escrita do compilador.) Os designers não esperavam que eles se tornassem plataformas de computação de uso geral; era para isso que serviam os predecessores da arquitetura POWER. A revolução do computador doméstico mudou isso, é claro.
fonte
Eu tenho alguns aspectos adicionais aqui:
Considere que a operação "a = b / c" x86 implementaria isso como
Como um bônus adicional da instrução div, edx conterá o restante.
Um processador RISC exigiria primeiro carregar os endereços de bec, carregar bec da memória para os registradores, fazer a divisão e carregar o endereço de a e então armazenar o resultado. Dst, sintaxe src:
Aqui normalmente não haverá resto.
Se alguma variável deve ser carregada através de ponteiros, ambas as sequências podem se tornar mais longas, embora isso seja menos possível para o RISC, porque ele pode ter um ou mais ponteiros já carregados em outro registro. x86 tem menos registros, portanto, a probabilidade de o ponteiro estar em um deles é menor.
Prós e contras:
As instruções RISC podem ser misturadas com o código circundante para melhorar o agendamento da instrução, isso é menos possível com o x86 que, em vez disso, faz este trabalho (mais ou menos bem, dependendo da sequência) dentro da própria CPU. A sequência RISC acima normalmente terá 28 bytes de comprimento (7 instruções de 32 bits / 4 bytes de largura cada) em uma arquitetura de 32 bits. Isso fará com que a memória fora do chip funcione mais ao buscar as instruções (sete buscas). A sequência x86 mais densa contém menos instruções e, embora suas larguras variem, você provavelmente está vendo uma média de 4 bytes / instrução lá também. Mesmo se você tiver caches de instrução para acelerar isso, sete buscas significa que você terá um déficit de três em outro lugar para compensar em comparação com o x86.
A arquitetura x86 com menos registros para salvar / restaurar significa que provavelmente fará mudanças de thread e tratará interrupções mais rápido do que RISC. Mais registros para salvar e restaurar requerem mais espaço de pilha de RAM temporário para fazer interrupções e mais espaço de pilha permanente para armazenar estados de thread. Esses aspectos devem tornar o x86 um candidato melhor para executar RTOS puro.
Em uma nota mais pessoal, acho mais difícil escrever um assembly RISC do que x86. Eu resolvo isso escrevendo a rotina RISC em C, compilando e modificando o código gerado. Isso é mais eficiente do ponto de vista de produção de código e provavelmente menos eficiente do ponto de vista de execução. Todos esses 32 registros para acompanhar. Com x86 é o contrário: 6-8 registros com nomes "reais" tornam o problema mais gerenciável e instila mais confiança de que o código produzido funcionará conforme o esperado.
Feio? Isso está nos olhos de quem vê. Eu prefiro "diferente".
fonte
there typically won't be a reminder
mas o wiki diz que os mips têm: en.wikipedia.org/wiki/MIPS_instruction_set#IntegerAcho que essa pergunta tem uma suposição falsa. São principalmente acadêmicos obcecados por RISC que chamam o x86 de feio. Na realidade, o ISA x86 pode fazer em uma única instrução operações que levariam de 5 a 6 instruções em ISAs RISC. Os fãs do RISC podem contestar que as CPUs x86 modernas quebram essas instruções "complexas" em microops; Contudo:
mov %eax, 0x1c(%esp,%edi,4)
, por exemplo, modos de endereçamento, e eles não são divididos.O x86 realmente absorveu todos os aspectos positivos do RISC cerca de 10-15 anos atrás, e as qualidades restantes do RISC (na verdade, a definição - o conjunto mínimo de instruções) são prejudiciais e indesejáveis.
Além do custo e da complexidade de fabricação de CPUs e seus requisitos de energia, x86 é o melhor ISA . Qualquer um que disser o contrário está deixando a ideologia ou a agenda atrapalharem seu raciocínio.
Por outro lado, se você está direcionando dispositivos embarcados onde o custo da CPU conta, ou dispositivos embarcados / móveis onde o consumo de energia é uma preocupação principal, ARM ou MIPS provavelmente fazem mais sentido. Lembre-se de que você ainda terá que lidar com a ram extra e o tamanho binário necessários para lidar com código que é facilmente 3-4 vezes maior e não será capaz de chegar perto do desempenho. Se isso importa, depende muito do que você estará executando nele.
fonte
A linguagem assembler x86 não é tão ruim. É quando você chega ao código de máquina que ele começa a ficar realmente feio. Codificações de instruções, modos de endereçamento, etc. são muito mais complicados do que para a maioria das CPUs RISC. E há diversão extra incorporada para fins de compatibilidade com versões anteriores - coisas que só surgem quando o processador está em um determinado estado.
Nos modos de 16 bits, por exemplo, o endereçamento pode parecer totalmente bizarro; há um modo de endereçamento para
[BX+SI]
, mas não um para[AX+BX]
. Coisas como essa tendem a complicar o uso do registro, já que você precisa garantir que seu valor esteja em um registro que você possa usar conforme necessário.(Felizmente, o modo de 32 bits é muito mais saudável (embora às vezes ainda seja um pouco estranho - segmentação, por exemplo), e o código x86 de 16 bits é amplamente irrelevante fora dos carregadores de inicialização e alguns ambientes incorporados.)
Também há as sobras dos velhos tempos, quando a Intel estava tentando fazer do x86 o processador definitivo. Instruções com alguns bytes de comprimento que executavam tarefas que ninguém mais faz, porque eram francamente lentas ou complicadas demais. As instruções ENTER e LOOP , para dois exemplos - observe que o código do frame da pilha C é como "push ebp; mov ebp, esp" e não "enter" para a maioria dos compiladores.
fonte
Não sou um especialista, mas parece que muitos dos recursos pelos quais as pessoas não gostam podem ser os motivos pelos quais ele tem um bom desempenho. Vários anos atrás, ter registradores (em vez de uma pilha), quadros de registradores, etc. eram vistos como boas soluções para fazer a arquitetura parecer mais simples para os humanos. Porém, hoje em dia, o que importa é o desempenho do cache, e as palavras de comprimento variável do x86 permitem que ele armazene mais instruções no cache. A "decodificação de instruções", que acredito que os oponentes apontaram uma vez que ocupou metade do chip, não é mais assim tanto.
Acho que o paralelismo é um dos fatores mais importantes hoje em dia - pelo menos para algoritmos que já rodam rápido o suficiente para serem utilizáveis. Expressar alto paralelismo no software permite que o hardware amortize (ou muitas vezes oculte completamente) as latências da memória. Claro, o futuro da arquitetura de maior alcance provavelmente está em algo como a computação quântica.
Ouvi da nVidia que um dos erros da Intel foi manter os formatos binários próximos ao hardware. O PTX do CUDA faz alguns cálculos rápidos de uso de registro (coloração de gráfico), então a nVidia pode usar uma máquina de registro em vez de uma máquina de pilha, mas ainda tem um caminho de atualização que não quebra todos os softwares antigos.
fonte
Além dos motivos que as pessoas já mencionaram:
__cdecl
,__stdcall
,__fastcall
, etc.fonte
Acho que você chegará a parte da resposta se alguma vez tentar escrever um compilador voltado para o x86, ou se escrever um emulador de máquina x86, ou mesmo se tentar implementar o ISA em um design de hardware.
Embora eu entenda o "x86 é feio!" argumentos, ainda acho mais divertido escrever assembly x86 do que MIPS (por exemplo) - o último é simplesmente tedioso. Sempre foi feito para ser bom para os compiladores, e não para os humanos. Não tenho certeza se um chip poderia ser mais hostil aos escritores de compiladores se tentasse ...
A parte mais feia para mim é a maneira como a segmentação (em modo real) funciona - que qualquer endereço físico tem 4096 segmentos: aliases de deslocamento. Quando foi a última vez que você precisou disso? As coisas teriam sido muito mais simples se a parte do segmento fosse estritamente bits de ordem superior de um endereço de 32 bits.
fonte
x86 tem um conjunto muito, muito limitado de registros de uso geral
promove um estilo de desenvolvimento muito ineficiente no nível mais baixo (inferno CISC) em vez de uma metodologia de carregamento / armazenamento eficiente
A Intel tomou a terrível decisão de introduzir o modelo de segmentação / deslocamento de memória totalmente estúpido para se manter compatível com (neste momento já!) Tecnologia desatualizada
Em uma época em que todo mundo estava indo para 32 bits, o x86 atrasou o mundo dos PCs convencionais por ter apenas 16 bits (a maioria deles - o 8088 - mesmo apenas com caminhos de dados externos de 8 bits, o que é ainda mais assustador!) CPU
Para mim (e sou um veterano do DOS que viu cada geração de PCs da perspectiva dos desenvolvedores!), O ponto 3. foi o pior.
Imagine a seguinte situação que tivemos no início dos anos 90 (mainstream!):
a) Um sistema operacional que tinha limitações insanas por motivos de legado (640kB de RAM facilmente acessível) - DOS
b) Uma extensão de sistema operacional (Windows) que poderia fazer mais em termos de RAM, mas era limitada quando se tratava de coisas como jogos, etc ... e não era a coisa mais estável na Terra (felizmente isso mudou depois, mas eu estou falando sobre o início dos anos 90 aqui)
c) A maioria dos softwares ainda era DOS e tínhamos que criar discos de boot frequentemente para softwares especiais, porque havia esse EMM386.exe que alguns programas gostavam, outros odiavam (especialmente gamers - e eu era um jogador AVID nessa época - sabe o que eu estou falando aqui)
d) Estávamos limitados a bits MCGA 320x200x8 (ok, havia um pouco mais com truques especiais, 360x480x8 era possível, mas apenas sem suporte de biblioteca em tempo de execução), todo o resto estava bagunçado e horrível ("VESA" - risos)
e) Mas em termos de hardware, tínhamos máquinas de 32 bits com alguns megabytes de RAM e placas VGA com suporte de até 1024x768
O motivo dessa situação ruim?
Uma decisão de design simples da Intel. Nível de instrução da máquina (NÃO nível binário!) Compatibilidade com algo que já estava morrendo, acho que era o 8085. Os outros problemas aparentemente não relacionados (modos gráficos, etc ...) eram relacionados por razões técnicas e por causa do muito estreito arquitetura orientada que a plataforma x86 trouxe consigo.
Hoje, a situação é diferente, mas pergunte a qualquer desenvolvedor montador ou pessoas que constroem back-ends de compiladores para x86. O número insanamente baixo de registros de uso geral nada mais é do que um terrível assassino de desempenho.
fonte