A abordagem comum para tirar proveito de múltiplos núcleos é, francamente, simplesmente equivocada. A separação de seus subsistemas em diferentes segmentos de fato dividirá parte do trabalho em vários núcleos, mas há alguns problemas importantes. Primeiro, é muito difícil trabalhar com isso. Quem quer mexer com bloqueios, sincronização, comunicação e outras coisas, quando poderia apenas escrever código de renderização ou física? Segundo, a abordagem realmente não aumenta. Na melhor das hipóteses, isso permitirá que você aproveite talvez três ou quatro núcleos, e isso se você realmente souber o que está fazendo. Existem tantos subsistemas em um jogo e, dentre esses, menos ainda, que consomem grande parte do tempo da CPU. Existem algumas boas alternativas que eu conheço.
Uma é ter um thread principal junto com um thread de trabalho para cada CPU adicional. Independentemente do subsistema, o encadeamento principal delega tarefas isoladas aos encadeamentos de trabalho por meio de algum tipo de fila (s); essas tarefas também podem criar outras tarefas. O único objetivo dos encadeamentos de trabalho é cada tarefa de captura da fila, uma por vez, e executada. O mais importante, porém, é que, assim que um encadeamento precisa do resultado de uma tarefa, se a tarefa for concluída, ele poderá obter o resultado e, se não, poderá remover com segurança a tarefa da fila e prosseguir com a execução. tarefa em si. Ou seja, nem todas as tarefas acabam sendo agendadas em paralelo. Ter mais tarefas do que pode ser executado em paralelo é uma boacoisa neste caso; isso significa que é provável que seja dimensionado à medida que você adiciona mais núcleos. Uma desvantagem disso é que exige muito trabalho antecipadamente para projetar uma fila decente e um loop de trabalho, a menos que você tenha acesso a um tempo de execução de biblioteca ou idioma que já o forneça. A parte mais difícil é garantir que suas tarefas sejam realmente isoladas e seguras, e garantir que suas tarefas estejam em um meio termo feliz entre granulação grossa e granulação fina.
Outra alternativa para os encadeamentos do subsistema é paralelizar cada subsistema isoladamente. Ou seja, em vez de executar renderização e física em seus próprios encadeamentos, escreva o subsistema de física para usar todos os seus núcleos de uma só vez, escreva o subsistema de renderização para usar todos os seus núcleos de uma só vez e faça com que os dois sistemas simplesmente executem sequencialmente (ou intercalados, dependendo de outros aspectos da arquitetura do seu jogo). Por exemplo, no subsistema de física, você pode pegar todas as massas de pontos do jogo, dividi-las entre seus núcleos e fazer com que todos os núcleos os atualizem de uma só vez. Cada núcleo pode trabalhar com seus dados em loop apertado com boa localidade. Esse estilo de paralelismo de etapa de bloqueio é semelhante ao que uma GPU faz. A parte mais difícil aqui é garantir que você esteja dividindo seu trabalho em pedaços finos, de modo que o divida uniformementena verdade, resulta em uma quantidade igual de trabalho em todos os processadores.
No entanto, às vezes é mais fácil, devido à política, código existente ou outras circunstâncias frustrantes, dar a cada subsistema um encadeamento. Nesse caso, é melhor evitar criar mais threads do SO do que núcleos para cargas de trabalho pesadas da CPU (se você tiver um tempo de execução com threads leves que, por acaso, se equilibram entre os núcleos, isso não é tão importante). Além disso, evite comunicação excessiva. Um bom truque é tentar pipelining; cada subsistema principal pode estar trabalhando em um estado de jogo diferente por vez. O pipelining reduz a quantidade de comunicação necessária entre seus subsistemas, pois nem todos eles precisam acessar os mesmos dados ao mesmo tempo, e também pode anular alguns dos danos causados por gargalos. Por exemplo, se o subsistema de física demorar muito para ser concluído e o subsistema de renderização acabar sempre esperando por ele, sua taxa de quadros absoluta poderá ser maior se você executar o subsistema de física para o próximo quadro enquanto o subsistema de renderização ainda estiver trabalhando no anterior quadro, Armação. De fato, se você tiver esses gargalos e não puder removê-los de outra maneira, o pipelining pode ser o motivo mais legítimo para se preocupar com os threads do subsistema.