1 pontos por GN⁺ 2024-08-28 | 1 comentários | Compartilhar no WhatsApp
  • O RV64 DynaRec do Box64 evoluiu, em um ano, de um estágio em que rodava apenas jogos nativos simples de Linux para um ponto em que consegue executar The Witcher 3 em um PC RISC-V
  • O avanço foi impulsionado pela possibilidade de usar placas de vídeo AMD, o que reduziu as limitações de OpenGL e permitiu testar mais programas x86 e corrigir bugs
  • O backend RV64 implementa menos instruções x86 do que o backend ARM64, e as instruções AVX ainda seguem como um grande desafio no lado RISC-V
  • O RISC-V não tem extração/inserção de intervalos de bits nem instruções atômicas de 16 bytes, o que aumenta o custo de tradução na emulação de x86 em comparação com AArch64 e LoongArch64
  • The Witcher 3 realmente roda com o box64, chegando a 15 fps no jogo e a velocidade total no menu principal

Até The Witcher 3 rodar no RISC-V

  • Há 1 ano, o RV64 DynaRec só conseguia rodar jogos nativos de Linux relativamente fáceis de executar, como Stardew Valley e World of Goo
  • Na época, havia dois grandes gargalos
    • Muitos bugs no DynaRec ainda restavam no processo de adicionar rapidamente instruções x86_64 ao backend RISC-V
    • As GPUs integradas IMG do VisionFive 2 e do LicheePi 4A suportavam apenas OpenGL ES, e não OpenGL
  • Com o gl4es, foi possível obter algum suporte a OpenGL e rodar jogos como Stardew Valley, mas isso não bastava para jogos Linux mais pesados nem para jogos Windows em geral
  • O Milk-V Pioneer da Sophgo é um PC RISC-V de 64 núcleos com slot PCIe para instalar placa de vídeo
  • Outro colaborador, xctan, encontrou uma forma de “conectar” uma placa de vídeo AMD ao VisionFive 2 por meio da interface M.2
  • Com o uso de placas AMD, a variedade de programas x86 que podiam ser testados aumentou, e houve uma grande leva de correções de bugs no RV64 DynaRec e adição de instruções x86
  • Como resultado, The Witcher 3 funcionou logo na primeira vez em que foi executado

Estado atual do RV64 DynaRec

  • O conjunto de instruções x86 é muito grande, e o volume de implementação varia conforme o backend
    • O backend ARM64 implementa, no total, mais de 1.600 instruções x86
    • O backend RV64 implementa cerca de 1.000 instruções x86
    • Dessas, mais de 300 são instruções AVX recém-suportadas, que ainda não foram implementadas no RISC-V
  • Mesmo na implementação de instruções SSE, o RISC-V fica em desvantagem de desempenho
    • O backend RV64 implementa instruções SSE com instruções escalares
    • O AArch64 usa a extensão Neon, e o LoongArch64 usa a extensão LSX
    • Por causa dessa diferença, o desempenho é consideravelmente inferior ao dos outros dois backends
  • O RISC-V tem a extensão vetorial RVV
    • O Milk-V Pioneer suporta a extensão xtheadvector, uma variante do RVV 0.7.1
    • O SoC SpacemiT K1/M1 suporta o RVV 1.0 ratificado
    • Banana Pi F3 e Milk-V Jupiter, com esse SoC, já podem ser comprados
  • O box64 recebeu recentemente suporte básico a RVV e implementação de algumas instruções SSE comuns
  • O trabalho com RVV ainda está em estágio muito inicial, então por enquanto não ajuda no desempenho

Instruções RISC-V especialmente ausentes para emulação x86

  • Do ponto de vista da emulação x86, o RISC-V é a arquitetura de menor expressividade entre as três suportadas
  • Em comparação com AArch64 e LoongArch64, faltam instruções convenientes, o que exige mais instruções para emular a mesma operação
  • Faltam principalmente duas capacidades importantes
    • Selecionar um determinado intervalo de bits de um registrador e levá-lo para outro registrador
    • Inserir parte dos bits de um registrador em uma faixa específica de outro registrador
  • LoongArch64 e AArch64 têm instruções equivalentes para isso
    • O LoongArch64 usa BSTRPICK.D e BSTRINS.D
    • O ARM64 usa os opcodes UBFX e BFI
  • O RISC-V não tem instruções correspondentes nem em extensões oficiais nem em extensões de fabricantes

O custo de tradução mostrado pelo exemplo ADD AH, BL

  • A ISA x86 tende a preservar os bits que não são alterados, por isso a manipulação de registradores parciais é importante
  • No caso de ADD AH, BL, o box64 precisa fazer o seguinte
    • Extrair o byte menos significativo de RBX
    • Somá-lo ao segundo byte menos significativo de RAX
    • Reinserir o resultado no segundo byte menos significativo de RAX
    • Manter os demais bytes de RAX inalterados
  • No LoongArch64, isso pode ser implementado de forma simples e direta com BSTRPICK.D, ADD e BSTRINS.D
  • No RISC-V, a mesma operação exige combinar shifts, máscaras, AND, OR etc., chegando a 10 instruções
  • Esse tipo de caso não é isolado; o x86 tem muitas instruções semelhantes, o que torna a implementação em RISC-V mais trabalhosa

Limitações das instruções atômicas de 16 bytes

  • O x86 tem instruções com prefixo LOCK para operações atômicas lock-free
  • O box64 as emula principalmente com sequências LR/SC
    • LR/SC significa Load-Reserved / Store-Conditionally
    • Por exemplo, LOCK ADD [RAX], RCX é gerado na forma LR.D, ADD, SC.D e desvio condicional
  • Se o endereço em RAX não estiver alinhado, a situação fica mais complexa, mas em geral essa abordagem funciona bem
  • O problema é o LOCK CMPXCHG16B
    • Essa instrução compara RDX:RAX com 16 bytes da memória
    • Dependendo da condição, troca RCX:RBX com esse endereço de memória
  • AArch64 e LoongArch64 têm algumas instruções atômicas de 16 bytes que podem ser usadas na implementação
  • O RISC-V não tem instruções equivalentes, então não consegue uma implementação tão completa quanto as outras arquiteturas
  • Muitos programas, incluindo jogos em Unity, usam LOCK CMPXCHG16B

Resultado prático

  • Apesar das limitações restantes, The Witcher 3 roda em RISC-V com o box64
  • O desempenho chega a 15 fps dentro do jogo
  • No menu principal, ele roda em velocidade total
  • Para um resultado obtido em uma máquina que não foi projetada para rodar jogos AAA, o nível não é ruim

1 comentários

 
GN⁺ 2024-08-28
Opiniões do Hacker News
  • Como alguém que não trabalha com chips, tenho curiosidade sobre o que um engenheiro de software precisa fazer de diferente ao criar software voltado para RISC-V
    Fico pensando se o tamanho dos executáveis aumenta a ponto de exigir uma otimização agressiva da localidade de cache, e também se existem tipos de software, como jogos ou servidores web, que se encaixam melhor em CISC ou em RISC

    • RISC-V com a extensão de instruções comprimidas tende, em média, a gerar código menor que x86-64 ou ARM
      Não há muita coisa essencial a mudar na abordagem de software; a maior diferença em relação ao x86-64 é que há 32 registradores, então é possível manter mais valores intermediários antes que eles precisem ser empurrados para a pilha, mas ARM também tem 32, então é parecido. Em geral, a menos que você esteja fazendo micro-otimizações, não é algo com que precise se preocupar muito
      Indo mais aos detalhes, a extensão vetorial (V/RVV) não faz parte da ISA rv64gc básica, então, dependendo do alvo, talvez você não consiga se beneficiar de otimizações SIMD; popcount e contagem de zeros à esquerda/à direita também não existem no rv64gc básico e exigem Zbb. Além disso, uma seleção sem desvio como a ? b : c precisa de 4 a 5 instruções no rv64gc básico e de 3 instruções com Zicond, enquanto em x86-64 e aarch64 dá para fazer com 1
      Os perfis RISC-V resolvem em certa medida os dois primeiros problemas. Por exemplo, o Android exige rva23, que requer RVV, Zbb, Zicond etc. Mas, se uma distribuição Linux mirar rva20/rv64gc, essas extensões acabam ficando, na prática, inutilizáveis por muito tempo em código pré-compilado que não inclua despacho dinâmico. O x86-64 tem um problema parecido, mas no ARM há bem menos extensões, então isso pesa menos; a maior exceção é SVE, que ainda não tem suporte amplo
    • Na maior parte dos casos, não é preciso fazer nada diferente. Código escrito corretamente em uma linguagem de alto nível como C deve se comportar da mesma forma
      A maior diferença é o modelo de memória fraco, uma característica também presente na maioria das arquiteturas não x86, como ARM, e, para começo de conversa, o código não deveria depender de um modelo de memória forte
      A densidade do código executável no x86 não é tão boa por motivos históricos, então o tamanho dos executáveis não aumenta tanto quanto se poderia imaginar. RISC-V com a extensão de instruções comprimidas e ARM de 32 bits com a extensão Thumb são bastante densos
      O importante não é CISC versus RISC, mas sim a existência e a qualidade de instruções vetoriais e extensões criptográficas. Codificação/decodificação de vídeo depende bastante de instruções vetoriais para bom desempenho, e criptografia de disco inteiro ou hashing pode se beneficiar de instruções dedicadas que aceleram algoritmos específicos como AES e SHA256
    • Qualquer conjunto de instruções deveria se adequar de forma semelhante a praticamente todo tipo de carga de trabalho. Se você estiver programando em assembly, haverá diferenças, mas, se estiver usando Python ou Unity, isso quase não será um problema
      O ponto central é mais se livrar das patentes da ARM e fazer um recomeço com base nas lições aprendidas
  • Isso me lembra um russo famoso que rodou Atomic Heart no Elbrus 8S
    O Elbrus tem um tradutor nativo e, pelo que sei, ele é bem razoável. Atomic Heart ficava meio jogável, por volta de 15 a 25 fps

  • O texto deixa um pouco a desejar na explicação “básica”. Achei que tivesse sido executado usando algo como um port do Wine, mas na verdade parece que eles implementaram de alguma forma a ISA x86_64 sobre um chip RISC-V
    Seria bom se alguém pudesse explicar melhor como essa parte funciona

    • A explicação básica está aqui: https://box86.org/
      Embora seja um emulador, algumas bibliotecas “de sistema”, como libc, libm, SDL e OpenGL, usam versões nativas, por isso é fácil integrá-lo à maioria das aplicações e, em alguns casos, o desempenho pode ser surpreendentemente alto. O Wine também pode ser compilado e executado de forma nativa
  • É um resultado impressionante. É uma quantidade enorme de trabalho e, em alguns casos, parece estar batendo nos limites do RISC-V
    Instruções de coleta/dispersão de bits parecem algo que deveria entrar como extensão

    • Também gostaria de ver resultados de testes com jogos que dependam mais do núcleo gráfico do que da CPU. Talvez algo como Divinity 2 fosse adequado
  • No contexto da emulação x86, é interessante a parte em que, entre as três arquiteturas suportadas, RISC-V é a menos expressiva
    Nas aulas de história da ciência da computação, aprendi que RISC significa computador com conjunto reduzido de instruções, mas, ao ver propostas de perfis RISC-V e textos recentes, há muitos argumentos do tipo “só precisamos de mais algumas instruções para equivalência funcional”. Entendo que RISC-V seja, para muita gente, uma alternativa conveniente a outras plataformas, mas também fico curioso se isso significa que o sonho do RISC morreu

    • Entendo que o RISC real é menos “um conjunto de instruções absolutamente mínimo” e mais algo como “não colocar recursos espertos para conveniência de programadores assembly e, sempre que possível, deixar para o compilador em vez do silício do front-end”
      Pelo que lembro de ter lido da especificação RISC-V, ela era bem rigorosa em não adicionar instruções “combo”, já que sequências comuns de instruções podem ser fundidas no front-end
      A insuficiência do RISC-V em comparação com x86/ARM parece vir menos de fundamentalismo RISC e mais do fato de a especificação ter começado por chips embarcados bem básicos e, com o tempo, ter adicionado extensões para CPUs de aplicação. O RV32I básico nem sequer tem multiplicação de inteiros. Infelizmente, levou tempo demais para encerrar as discussões sobre manipulação de bits e extensões SIMD/vetoriais, e o resultado é a lacuna de recursos de que se fala agora
    • Se você quer criar um conjunto de instruções que um estudante consiga implementar dentro de uma disciplina de um semestre, precisa de simplificações como fazer todas as instruções terem duas entradas e uma saída. Isso também fica muito mais fácil para pesquisadores que experimentam com design de processadores
      Só que, na mesma medida, algumas instruções convenientes para alto desempenho ficam de fora
      Um pipeline simples também tem a vantagem de consumir menos recursos de engenharia das equipes que fazem projetos de alto desempenho, permitindo gastar mais tempo com otimização
      RISC é, em geral, uma filosofia de simplificação, mas o grau varia. MIPS é tão simplificado quanto RISC-V, mas ARM e POWER são mais pragmáticos, e parecem não ter grandes problemas em enfrentar x86 no segmento de alto desempenho
      O mercado de processadores tem vários nichos além de executar aplicações, como embarcados e aceleradores. No nicho específico de núcleos de aplicação sou um pouco pessimista em relação ao RISC-V, mas, olhando de forma mais ampla, ele tem muito potencial, pode até dominar alguns nichos comerciais, e é excelente como ferramenta de ensino e pesquisa
    • O sonho do RISC era simplificar o projeto da CPU, já que a maioria dos softwares é escrita por compiladores, e não diretamente em assembly
      As características clássicas do RISC são que a maioria das instruções de manipulação de dados opera apenas em registradores, e as instruções de memória são, em geral, apenas load/store para registradores, exigindo muitos registradores. Como é preciso manipular a pilha diretamente para passar parâmetros, a pilha também é construída diretamente, e chamadas são implementadas com instruções básicas de load/store diretamente no registrador de ponteiro de instrução, sem instruções CALL/JSR. A codificação das instruções é previsível e todas as instruções têm o mesmo tamanho. Várias arquiteturas RISC também tinham um registrador que sempre lia 0 e no qual não era possível escrever, usado para definir valores como 0
      Essa abordagem funcionou, mas depois a execução fora de ordem e SIMD reduziram sua importância. O fluxo bruto de instruções é mais uma declaração do caminho para chegar ao resultado desejado; não significa que a CPU vá de fato executá-lo literalmente. Por trás disso há execução especulativa, predição de desvios e renomeação de registradores. SIMD se aproxima de um grande espaço de registradores e de instruções que operam sobre todos os valores dentro dele. No fim, execução fora de ordem e SIMD assumiram o protagonismo
    • Fico em dúvida se existe mesmo algo como o sonho do RISC. Existem o sonho da eficiência, o sonho do desempenho, o sonho do custo, e também o sonho de baixa complexidade em relação a custo, desempenho e eficiência, mas será que alguém considera o RISC em si mais importante do que custo, desempenho, eficiência e simplicidade?
    • Neste contexto, a ideia é executar em RISC-V código que já foi compilado para x86_64. A demanda por “só mais algumas instruções para equivalência funcional” surge porque se está tentando executar código já compilado para uma arquitetura que tem essas instruções adicionais
      Em teoria, se o código-fonte original fosse compilado para RISC, o binário resultante seria completamente diferente e talvez essas instruções específicas não fossem necessárias
      Na prática, não parece provável que alguém vá compilar esses jogos de fato para RISC-V
  • Na captura de tela, a RAM aparece como 31 GB, o que certamente é mais do que a configuração máxima da placa de desenvolvimento mencionada. Estão usando outra coisa aqui?

    • É uma placa mais antiga, a Pioneer
      Hoje em dia, seria melhor usar uma das opções mais recentes, com vários núcleos mais rápidos que implementam RVA22 e RVV 1.0
    • https://milkv.io/pioneer
    • A milk-v pioneer vem com 128 GB de RAM
  • Isso é o 86Box? Foi divertido lembrar da época em que comprei um Amstrad PC1512
    Ele ficou muito mais interessante depois que adicionei duas placas de disco rígido de 500 MB e uma expansão de memória de 128 KB, levando-o a 640 KB. No começo, havia apenas dois disquetes de 360 KB, e alguns anos depois adicionei uma placa de disco rígido de 32 MB. Também tinha Borland TurboPascal e Zortech C. Bons tempos

    • Não, isto é o Box64, um projeto completamente diferente
      Ainda assim, lembro da época de usar um Amstrad PC1512
  • Fico imaginando se algum dia veremos um sistema com algumas CPUs RISC-V grandes e uma “GPU” implementada com um monte de CPUs RISC-V pequenas
    Imagino que seria algo com recursos vetoriais adequados; como pergunta paralela, também fico curioso se uma abordagem vetorial clássica, em vez de SIMD empacotado, poderia ser útil em GPUs

  • Entre os feitos tecnicamente impressionantes de The Witcher 3 também houve o port para Switch, que rodava muito bem
    Ele mostra o quanto é possível fazer com otimização, e quantos recursos são desperdiçados no PC por causa de otimização ruim

    • Também pesou bastante o uso de texturas e modelos 3D de qualidade muito mais baixa, consumindo muito menos RAM para os assets
      Não é uma comparação equivalente; a qualidade do que é exibido na tela é muito diferente, então é difícil concluir que se trata de má otimização no PC
    • Se você reduzir a resolução de renderização para 720p, ou 540p no modo portátil, colocar as configurações abaixo do mínimo e considerar cerca de 30 fps como aceitável, dá para rodar The Witcher 3 de forma semelhante até em um PC com especificações mínimas
  • Seria bom se esse tipo de feedback no nível da ISA chegasse ao pessoal do RVI

    • No SIG de eficiência escalar, já estão discutindo instruções de inserção/extração de campos de bits
      Conferi ontem [1], e o exemplo do texto já pode ser feito com 4 instruções RISC-V. Só que é um pouco complicado de imaginar
      # a0 = rax, a1 = rbx
      slli t0, a1, 64-8
      rori a0, a0, 16
      add a0, a0, t0
      rori a0, a0, 64-16
      [1] https://www.reddit.com/r/RISCV/comments/1f1mnxf/box64_and_ri...
    • Nada disso é novo
      Na verdade, a ausência de extração de campos de bits é um erro tão evidente que é meu exemplo favorito para mostrar como a ISA RISC-V é absurda. O segundo é a falta de modos de endereçamento decentes
      Alguns projetos melhores de RISC-V de fato implementam instruções customizadas para isso. Por exemplo, há a BEXTM do Hazard3: https://github.com/Wren6991/Hazard3/blob/stable/doc/hazard3....