Como é possível que dando trabalho extra a um GPU saturado, para processar usando o GPGPU, se possa melhorar as performances de um sistema?

A pergunta é pertinente: Se temos um GPU que já está saturado, quais as vantagens que podem existir em se lhe dar mais trabalho? Na realidade muitas, e tal pode melhorar mesmo performances. Mas para isso é preciso perceber como as coisas funcionam!

Certos programadores referem em entrevistas que pretendem passar mais trabalho para o GPGPU de forma a libertar mais CPU e/ou obter melhores performances.

Mas se em jogos anteriores destes produtores o GPU já se via à rasca para conseguir uma resolução e fotogramas decentes, como é que saturar ainda mais um GPU pode ajudar?

Na realidade pode e muito. Mas há que se perceber o que acontece e como acontece, de forma a se perceber que a sobrecarga adicional de trabalho na realidade não afecta o que o GPU já fazia antes, e na prática pode ajudar a libertar CPU e mesmo GPU, permitindo melhores performances.

É certo que pode parecer um contra-senso entregar mais trabalho a uma peça que está já limitada, mas como irão perceber, na realidade a lógica é muita!



Comecemos por compreender uns conceitos básicos ligados aos GPUs.

O que é um GPU?

O GPU é basicamente um conjunto de processadores que funcionam em paralelo. Estes processadores são basicamente pipelines, ou condutas, por onde os dados seguem, e dotados de várias etapas de processamento dedicadas a grafismo. Basicamente os dados entram no pipeline e saem processados do outro lado. É como se fosse uma fábrica, onde a matéria prima entra, passa pelas várias etapas de produção, e sai do outro lado.

A título de exemplo, para localizarmos o que falamos, a PS4 possui 1152 destes pipelines, a Xbox One 768, sendo que por vezes eles são chamados de Shader Pipelines ou de Stream Processors (um nome específico do hardware AMD).

E antigamente um GPU era assim, e apenas assim, que funcionava. As suas etapas estavam completamente dedicadas ao processamento de pixels, e o GPU apenas fazia isso. Processava os pixels para o ecrã!

O GPGPU

Mas actualmente essas linhas de produção dos pipelines, devido ao avanço do 3D, contam com etapas de processamento que são extremamente versáteis e potentes. E apesar de muitas das etapas do pipeline serem específicas para grafismo, podemos passar as mesmas à frente e usar apenas aquelas que podem, graças às novas tecnologias, processar dados de forma genérica, para fazer processamento não gráfico.

Isso quer dizer que actualmente um pipeline gráfico tornou-se versátil ao ponto de processar não apenas gráficos, mas igualmente dados genéricos. A esta capacidade de processamento genérico chama-se GPGPU, com as letras GP a significarem Generic Processing, ou processamento genérico.

Esta capacidade aparece com a definição de unidades de computação (Compute Units ou CUs), que no caso das placas AMD são constituídas por 64 dos já referidos pipelines.

Um pequeno historial do GPGPU

A Nvidia foi pioneira na criação do GPGPU! Os seus núcleos Cuda eram versáteis ao ponto de as suas placas terem sido das primeiras a poderem fazer os dois tipos de processamento.

E a Nvidia rapidamente colocou esta capacidade a uso com o PhysiX, que lhes permitia libertar o CPU calculando física complexa, e acrescentando animações (como física de tecidos e poeiras), que não se fariam sem esta capacidade.

E durante muito tempo a Nvidia foi o único fabricante com GPGPU nos seus GPUs, algo que lhe valeu muita popularidade.

No entanto a AMD, apesar de entrar posteriormente nesta corrida, rapidamente superou a Nvidia neste campo, com o seu GPGPU a mostrar-se bastante superior.

A computação síncrona

Com os GPUs a processarem os dois tipos de dados, o primeiro tipo de processamento que apareceu, e o implementado pela Nvidia, foi a computação síncrona, ou em série!

Basicamente o que acontece aqui é que os dados entram por ordem sequencial e vão sendo processados por essa ordem. Naturalmente, sem pensarmos muito, percebemos logo que este tipo de processamento sequencial tem necessariamente perdas de performance pois ao acrescentarmos processamento genérico ao gráfico temos mais trabalho para calcular. Mas curiosamente, como irão ver, as perdas não são na mesma proporção da carga adicional de trabalho.

Esta é a situação usada pelas placas Nvidia pré Pascal, onde a computação funciona desta forma. A activação do CUDA para física ou outros, melhora a quantidade de animações realisticamente presentes no ecrã, mas possui impactos na performance.

Temos então como vantagem desta computação o facto de a mesma permitir libertar o CPU, passando o cálculo para o GPU. Mas como desvantagem temos algum impacto nas performances gráficas que varia com o nível de processamento genérico efectuado.

Na computação sincrona, quando atribuída uma tarefa só podemos passar à seguinte quando esta está terminada.

A computação assíncrona

A diferença desta computação para a anterior é que esta requer um GPU que possa instantaneamente mudar o modo de cálculo. E se no caso das Nvidia, apenas as placas gráficas com a arquitectura Pascal são capazes de tal (apesar de algumas outras limitações), o hardware AMD é capaz desta realidade desde à vários anos (apesar que estava desaproveitado pela ausência de APIs que tirassem partido da capacidade).

E é esta pequena diferença que faz toda a diferença do mundo, permitindo à AMD superar e bastante a Nvidia neste campo.

Basicamente esta situação, que é resultado da presença das unidades ACE nos GPUs AMD, permite que o processamento não seja sequencial, e como tal o acréscimo não se coloca forçosamente na fila existente em local fixo para ser processado. Há a possibilidade de alteração da ordem de forma a optimizar o cálculo e o rendimento do GPU.

Basicamente, graças à análise do sistema efectuada pelas unidades ACE que permitem que as unidades de processamento do GPU que estão IDLE (paradas), possam manter o pedido em stand-by, mudando nesse ciclo em que estaria parado, o tipo de tarefa, o rendimento do GPU sube para muito perto dos 100%.

Mas esta é uma situação que se percebe melhor explicada. Tentem acompanhar.

Como é habitual a PCManias tenta explicar como estas coisas funcionam de uma forma generalista e pálpável que permita mesmo aos mais leigos nesta matéria perceberem os conceitos por detrás das coisas. Nesse sentido o nosso exemplo não irá respeitar a realidade das placas gráficas, mas sim criar uma situação ficticia mais corriqueira que permita que percebam, não o funcionamento do GPU, mas sim as diferenças entre as diversas formas de computação.

Aqui vai então esse exemplo:

Um caso de exemplo para se perceber as diferenças.

Imaginemos então o seguinte cenário:

Um conjunto de 4 pessoas vão simular os diversos tipos de processamento. Uma delas será o capataz (CPU), e as outras três farão o papel de unidades de cálculo de um GPU.

Todas estão munidas de uma calculadora que lhes permite executar cálculo matemático. E vamos por simplificação ignorar o tempo que essas pessoas demoram a calcular a conta pedida, considerando apenas que ocorre um ciclo quando essa conta está terminada.

Eis a lista de ordens relativas a cálculos que vamos atribuir a estas pessoas. A vermelho temos contas que correspondem a cálculo gráfico, e a verde contas de cálculo genérico! Como compreendem as contas em si não interessam dizer quais são, apenas temos de aceitar que são relativas a cada um dos tipos de cálculo.

1- Calculo gráfico 1

2- Calculo genérico 1 

3 – Integração de dados do mundo com o grafismo dependente dos dois resultados anteriores

4- Calculo genérico 2

5 – Calculo gráfico dependente dos valores do passo 3 e 2

6- Calculo genérico dependente dos resultados do passo 2 e passo 4

7 – Calculo gráfico dependente do passo 5

8 – Integração de dados do mundo com o grafismo dependente dos passos 5 e 6

9 – Cálculo genérico 3
10 – Calculo genérico dependente do passo 9 e passo 6.
11 – Cálculo gráfico dependente do passo 3 e 5

12 – Cálculo genérico 4

Vamos então processar estes dados segundo vários métodos.

Computação Standard, CPU no cálculo genérico + GPU no cálculo gráfico

Com este tipo de cálculo, a pessoa nomeada capataz fará os cálculos do CPU. As restantes três simularão então as unidades de cálculo de um GPU, fazendo o seu trabalho.

Para executarmos a lista de comandos de cima, dado que CPU e GPU correm em paralelo teríamos o seguinte:

A cada ciclo o capataz executa uma das contas a verde. E a cada ciclo, cada uma das pessoas que simulam as unidades de processamento, executa uma das contas a vermelho.

Dado que nos interessa apenas ver as diferenças entre o que ocorre na gráfica em cada tipo de processamento, aqui não vamos fazer mais referências ao capataz, apenas percebendo que ele executa em tempo útil os seus cálculos.

Vamos mas é ver o que acontece com as pessoas que executam o cálculo gráfico! As três pessoas serão futuramente designadas por P1, P2 e P3.

Ciclo 1

  • A P1 aceita e processa a ordem gráfica 1.
  • A P2 aceita a ordem gráfica 3, mas não a processa ficando IDLE pois ela é dependente de dados que o capataz está a calcular em paralelo neste ciclo.
  • A P3 aceita a ordem gráfica 5, mas mais uma vez não a processa, ficando IDLE por precisar dos dados atribuidos ao capataz e à P2.

Comandos executados acumulados no final deste ciclo- Comando 1.
Pessoas libertadas para novos cálculos no final do ciclo – P1

Ciclo 2

  • A P1 aceita a ordem gráfica 7, mas não pode processar pois precisa de dados da P3 que só agora pode calcular, ficando IDLE.
  • A P2 processa a ordem gráfica 3 que tinha pendente.
  • A P3 continua a aguardar pois não pode ainda processar a ordem gráfica 5 que depende do resultado agora a ser processado pela P2, ficando IDLE. -Comandos executados acumulados no final deste ciclo – Comandos 1 e 3
    Pessoas libertadas para novos cálculos no final do ciclo – P2

Ciclo 3

  • A P1 não pode ainda processar a ordem gráfica 7, ficando mais uma vez IDLE.
  • A P2 recebe a ordem gráfica 8, mas precisa do resultado da ordem gráfica 5 que vai agora ser calculada pela P3, ficando IDLE.
  • A P3 processa a ordem gráfica 5.

Comandos executados acumulados no final deste ciclo – 1, 3, 5
Pessoas libertadas para novos cálculos no final do ciclo – P3

Ciclo 4

  • A P1 processa a ordem gráfica 7.
  • A P2 processa a ordem gráfica 8.
  • A P3 recebe e processa a ordem 11!

Comandos executados acumulados no final deste ciclo – 1, 3, 5, 7, 8, 11
Pessoas libertadas para novos cálculos no final do ciclo – P1, P2, P3

E estão terminados todos os cálculos gráficos, a vermelho, atribuidos ao GPU. Dado que tudo o resto é processado pelo Capataz (CPU) a devido tempo, o GPU calculou o que lhe era atribuído em 4 ciclos!

Mas convém perceber que, devido às dependências dos trabalhos, as componentes de cálculo estiveram paradas sem processar (IDLE) por 6 vezes!

O gráfico de baixo mostra a verde as vezes em que as tarefas foram completas e a vermelho as em que ficaram pendentes.

i1

Computação Sincrona (Grafismo + Computação genérica no GPU, processado sequencialmente)

Vamos passar processamento genérico para o GPU usando computação sincrona, libertando o CPU que pode agora fazer outras coisas. Eis novamente a lista de comandos.



1- Calculo gráfico 1

2- Calculo genérico 1 

3 – Integração de dados do mundo com o grafismo dependente dos dois resultados anteriores

4- Calculo genérico 2

5 – Calculo gráfico dependente dos valores do passo 3 e 2

6- Calculo genérico dependente dos resultados do passo 2 e passo 4

7 – Calculo gráfico dependente do passo 5

8 – Integração de dados do mundo com o grafismo dependente dos passos 5 e 6

9 – Cálculo genérico 3
10 – Calculo genérico dependente do passo 9 e passo 6.
11 – Cálculo gráfico dependente do passo 3 e 5

12 – Cálculo genérico 4

O que acontece aqui?

Na computação síncrona os dados entram sequencialmente e não há qualquer alteração na ordem de processamento. Vamos entregar os mesmos por ordem a cada um das nossas 3 pessoas que simulam o GPU. E aqui, o nosso capataz não vai fazer absolutamente nada!

Mais uma vez temos a vermelho o cálculo gráfico, a verde o genérico! Tentem acompanhar!

Ciclo 1

  • A P1 recebe e executa a ordem 1 gráfica.
  • A P2 recebe e executa a ordem 2 genérica.
  • A P3 recebe e tenta executar a ordem 3 gráfica, mas tem de ficar IDLE neste ciclo pois não tem os dados para processar.

Comandos executados acumulados no final deste ciclo – 1, 2
Pessoas libertadas para novos cálculos no final do ciclo – P1, P2

Ciclo 2

  • A P1 recebe e executa a ordem genérica 4.
  • A P2 recebe a ordem 5 gráfica, mas não a pode executar pois precisa do resultado da ordem 3 que só agora será executada, ficando IDLE.
  • A P3 executa a ordem 3 gráfica pendente.

Comandos executados acumulados no final deste ciclo – 1, 2, 3, 4
Pessoas libertadas para novos cálculos no final do ciclo – P1, P3

Ciclo 3

  • A P1 recebe e processa a ordem 6 genérica.
  • A P2 executa então a ordem 5 gráfica pendente.
  • A P3 recebe a ordem gráfica 7, mas não a pode executar por falta de dados, ficando IDLE.

Comandos executados acumulados no final deste ciclo – 1, 2, 3, 4, 5 ,6
Pessoas libertadas para novos cálculos no final do ciclo – P1, P2

Ciclo 4

  • A P1 recebe a ordem gráfica 8 e processa-a.
  • A P2 executa a ordem 9 genérica.
  • A P3 processa a ordem gráfica 7 pendente.

Comandos executados acumulados no final deste ciclo – 1, 2, 3, 4, 5 ,6, 7, 8, 9
Pessoas libertadas para novos cálculos no final do ciclo – P1, P2, P3

Ciclo 5

  • A P1 recebe e executa a ordem 10 genérica.
  • A P2 executa a ordem gráfica 11.
  • A P3 executa a ordem genérica 12!

Comandos executados acumulados no final deste ciclo – 1, 2, 3, 4, 5 ,6, 7, 8, 9, 10, 11, 12
Pessoas libertadas para novos cálculos no final do ciclo – P1, P2, P3

O que tiramos daqui?

Basicamente duas situações.

  • O CPU ficou livre de trabalho!
  • Aumentamos o número de comandos no GPU para o dobro, mas tal apenas o prejudicou em mais um ciclo.

Esta é a situação que já tinhamos referido em cima. A percentagem em que o GPU é penalizado não corresponde à percentagem de trabalho adicional. Neste exemplo demos-lhe mais 100% de trabalho, mas ele só levou mais um ciclo, ou seja, como antes tinha usado 4, foi penalizado em 25%.

 

Como se explica isto? Porque os momentos em que o GPU fica IDLE (sem trabalhar) alteram-se, com o trabalho diferente a eliminar muitos dos tempos mortos por esperas devidas a dependências de cálculo. No nosso exemplo elas descem de 6 para apenas 3!

E por norma, devido a esta alteração, acabamos por ter um aumento de carga real que normalmente se revela inferior à proporção do aumento do trabalho.

Vendo num gráfico como o anterior, temos a verde as alturas em que se completaram tarefas (Verde escuro gráficas, claro as genéricas), e a vermelho as alturas em que elas ficaram pendentes.

i2

Computação Assincrona (Grafismo + Computação genérica no GPU, processado paralelamente)

Na computação assincrona há. como referido, quase apenas uma pequena diferença face ao que temos na síncrona. Uma pequena diferença que, como já foi também referido,  faz toda a diferença do mundo. É que quando o processamento fica IDLE  a placa tem a capacidade de mudar o tipo de processamento procurando uma instrução de outro tipo. Basicamente o que acontece aqui é que diminuimos drasticamente os tempos em que o GPU está parado, aproximando a eficiência dos 100%.

Eis novamente a lista de comandos que vamos agora analisar assincronamente.

1- Calculo gráfico 1

2- Calculo genérico 1 

3 – Integração de dados do mundo com o grafismo dependente dos dois resultados anteriores

4- Calculo genérico 2

5 – Calculo gráfico dependente dos valores do passo 3 e 2

6- Calculo genérico dependente dos resultados do passo 2 e passo 4

7 – Calculo gráfico dependente do passo 5

8 – Integração de dados do mundo com o grafismo dependente dos passos 5 e 6

9 – Cálculo genérico 3
10 – Calculo genérico dependente do passo 9 e passo 6.
11 – Cálculo gráfico dependente do passo 3 e 5

12 – Cálculo genérico 4

Ciclo 1

  • A P1 executa a ordem 1 gráfica.
  • A P2 executa a ordem 2 genérica.
  • A P3 recebe a ordem 3 gráfica mas não pode executar pois ela depende de resultados ainda a serem executados neste ciclo. Assim, muda de processamento gráfico para genérico e executa a ordem 4 genérica.

Comandos executados acumulados no final deste ciclo – 1, 2, 4
Pessoas libertadas para novos cálculos no final do ciclo – P1, P2, P3

Ciclo 2

  • A P1 executa a ordem 5 gráfica, mas como não a pode ainda calcular muda para processamento genérico e executa a ordem 6 genérica.
  • A P2 recebe a ordem 7 gráfica e como não a pode executar passa a processamento genérico, executando a ordem 9 genérica.
  • A P3 pega agora na ordem 3 gráfica que estava pendente, calculando-a pois já tem os dados.

Comandos executados acumulados no final deste ciclo – 1, 2, 3, 4, 6, 9
Pessoas libertadas para novos cálculos no final do ciclo – P1, P2, P3

Ciclo 3

  • A P1 já pode agora executar a ordem 5 gráfica que tinha ficado pendente.
  • A P2 executa a ordem 7 gráfica que tinha ficado pendente.
  • O CU 3 executa a ordem 8 gráfica, mas como não a pode calcular pois ela depende da ordem 5 que está agora em cálculo, muda para processamento genérico e calcula a ordem genérica 10 (a 9 já estava calculada).

Comandos executados acumulados no final deste ciclo – 1, 2, 3, 4, 5, 6, 7, 9, 10
Pessoas libertadas para novos cálculos no final do ciclo – P1, P2, P3

Ciclo 4 – A P1 executa a ordem 8 gráfica.  A P2 executa a ordem 11 gráfica. A P3 executa a ordem 12 genérica.

Comandos executados acumulados no final deste ciclo – 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12
Pessoas libertadas para novos cálculos no final do ciclo – P1, P2, P3

O que concluímos?

  • A placa gastou o mesmo número de ciclos que gastou a processar apenas grafismo, ou seja, sem qualquer penalização.
  • A placa processou o dobro do trabalho!
  • A eficiência de trabalho da placa foi 100%, isto é, sem tempos mortos (IDLE=0).
  • O CPU está livre do trabalho.

Vamos ver isso num gráfico:

i3

Basicamente o acréscimo de trabalho colocado, graças à computação assíncrona não criou peso no GPU pois ela foi feita nas alturas onde o processamento gráfico estava parado.

E assim podemos agora responder a parte da vossa questão: Como é que se se pode atribuir mais trabalho a um GPU já saturado?

Colocando o mesmo a processar dados genéricos nas pausas do processamento gráfico que o GPU tem! E isto permite que o mesmo efectivamente debite mais, sem que prejudique o rendimento do que já debitaria!



Mas como é que isto ajuda o sistema?

De duas maneiras possíveis:

  • Se o gargalo está no CPU o mesmo é libertado e pode ser usado para outras situações.
  • Se o gargalo está no GPU esta computação genérica pode ser usada como auxiliar ao processamento gráfico. Jogos como Forza e Forza Horizon já o fazem usando uma metodologia denominada Forward Rendering + que utiliza a computação assincrona para processamento gráfico adicional, melhorando fps e a qualidade gráfica.

Espero que tenham compreendido. Agora não nos critiquem por o exemplo fugir um pouco à realidade dos GPUs, mas aqui a intenção era que percebessem as diferenças entre as computações, o que cremos que aqui se percebe plenamente desde que acompanhem e percebam o descrito nos vários ciclos para cada um dos tipos de cálculo.

Agora estarão em condições de perceber a imagem que está no topo deste artigo com as diversas carruagens. Elas estão em várias pistas (as PistasdP1, P2 e P3) com espaços entre elas (as alturas em que não calculavam nada), mas na computação assincrona, os processamentos encaixam todas nos espaços vazios uns dos outros. Aumenta-se a eficiencia do GPU para perto dos 100% (no nosso exemplo foi mesmo 100%, mas é um exemplo teórico) e processa-se mais em paralelo, sem se prejudicar o cálculo que já havia.

Note-se que este tipo de cálculo melhora a eficiência, mas aumenta a produção de calor no GPU, sendo que, por esse motivo nunca podemos ter todo o GPU a executar este tipo de processamento.



Posts Relacionados