As CPUs são projetadas tendo em mente o software que as pessoas escreverão para ela, implícita ou explicitamente.
Parece-me que, se você examinar o design das arquiteturas de conjuntos de instruções, elas são muito "imperativas", no sentido de que cada instrução codifica um comando de estilo imperativo. Parece-me também que as arquiteturas atuais do conjunto de instruções evoluíram parcialmente com base no tipo de código que os programadores produzem.
Se alguém projetasse uma CPU do zero, sabendo que apenas executaria programas escritos em um estilo de programação funcional, como essa CPU seria projetada de maneira diferente das CPUs existentes?
computer-architecture
functional-programming
user56834
fonte
fonte
Respostas:
Na verdade, isso foi feito: https://en.wikipedia.org/wiki/Lisp_machine
Um aspecto no design da CPU para FP é a coleta de lixo. O GC é muito importante para linguagens funcionais. Implementações comuns exigem que o GC possa distinguir entre dados de ponteiros e não-ponteiros. Efetivamente, isso significa armazenar um bit extra ao longo dos seus dados. Esse é o motivo pelo qual, por exemplo, números inteiros OCaml são apenas 31 bits em arquiteturas de 32 bits e 63 bits em arquiteturas de 64 bits. A aritmética de número inteiro envolve operações de deslocamento extra desajeitadas. Outros idiomas (ou outros tipos de dados OCaml) podem desperdiçar palavras inteiras da máquina para esse bit extra, usando 128 bits para números inteiros de 64 bits. Uma CPU projetada nativamente para o GC pode ter um barramento de dados de 65 bits, mas aritmética de 64 bits.
Dito isto, muitas linguagens não funcionais também têm coleta de lixo e, portanto, se beneficiariam das respectivas arquiteturas.
Outra coisa que vem à mente é que o uso de memória do FP normalmente é muito mais disperso do que o de programas imperativos. Principalmente porque é menos natural usar matrizes. Em conseqüência, esses programas lucram menos com o armazenamento em cache de pedaços contíguos de memória. Portanto, uma CPU FP pode usar diferentes estratégias de cache.
fonte
Isso não mudaria nada ou utilizaria uma configuração paralela massiva, como em Reduceron e seu sucessor PilGRIM 1, com uma pilha enorme.
A afirmação de que isso mudaria nada parece ousado no começo, mas como a CPU é seqüencial, existe um processo de conversão (compilação) que usa o hardware disponível para aumentar sua eficiência. Haverá outra arquitetura, algumas operações seriam mais rápidas, outras precisariam de truques de hackers para acelerar.
A arquitetura que faria a diferença exigiria que a operação e as listas do mapa funcionassem mais rapidamente (não a história toda, mas basta mostrar o efeito). Não há possibilidade de criar hardware dinâmico e dinâmico para executar listas de forma nativa; portanto, elas são armazenadas na memória contígua. Aderimos à representação de matriz de alguma forma. Para o mapa, para rodar em um cenário não sequencial - voltamos ao Reduceron. Tão eficazmente um processamento central para instruções consecutivas e suporte para processamento paralelo.
O que pode ser diferente é a possibilidade de carregar várias funções e executá-las sem malabarismo de quadros - mas adicionar várias unidades para funções criaria uma bagunça no acesso à memória.
Somando a resposta de kne, o GC seria benéfico para rodar como coprocessador, seria um recurso muito interessante.
1: O PilGRIM é descrito adequadamente em Boeijink A., Hölzenspies PKF, Kuper J. (2011) Apresentando o PilGRIM: um processador para a execução de linguagens funcionais preguiçosas. In: Hage J., Morazán MT (eds) Implementation and Application of Functional Languages. IFL 2010. Lecture Notes in Computer Science, vol. 6647. Springer, Berlim, Heidelberg .
fonte
Primeiro, uma piada: como a execução de um programa 100% funcional nunca pode fazer algo útil, seria suficiente ter apenas uma instrução NOP. (Eu abro isso para guerras de chamas).
Portanto, será necessário haver algumas instruções imperativas para o IO e o suporte usual para a programação imperativa.
Caso contrário, depende em parte do idioma real usado. Os dois que estão na minha cabeça são Haskell e Erlang.
Eu acreditaria que Haskell poderia se beneficiar do suporte a listas e mapas. Uma lista pode ser suportada por mapeamentos de memória de hardware específicos, transformando a lista vinculada em um conjunto consecutivo de endereços. O primeiro elemento pode estar no endereço n, o segundo no endereço n + 1 e assim por diante. Para remover o primeiro elemento da lista, basta alterar o ponteiro n. Finalmente, quando você exclui o ponteiro n, toda a memória pode ser liberada. Os mapas podem ser suportados como matrizes associativas - digite o valor da pesquisa e o sistema de memória retorna o item. Não há necessidade de pesquisas iterativas.
Erlang, por sua vez, poderia se beneficiar do suporte de mensagens / processos e recursão final com estado completo. Mensagens e processos podem ser suportados de várias maneiras; um exemplo pode ser o de ter uma quantidade extremamente grande de núcleos de processamento. A recursão da cauda poderia ser melhorada por um controlador de memória sabendo que o estado pode ser copiado muito mais rápido, talvez não copiando grandes pedaços de dados, mas modificando os ponteiros do sistema de memória.
fonte