É possível escrever código (ou software completo, em vez de um pedaço de código) que não funcionará corretamente quando executado em uma CPU com menos de N número de núcleos? Sem verificar explicitamente e falhar de propósito:
SE (noOfCores <4) ENTÃO não é executado corretamente de propósito
Estou observando os requisitos mínimos de sistema de um jogo ( Dragon Age: Inquisition ), e ele declara um mínimo de uma CPU de quatro núcleos. Muitos jogadores dizem que ele NÃO roda em CPUs de dois núcleos e até no Intel Core i3s com dois núcleos físicos e dois lógicos. E NÃO é um problema de poder de computação.
Pelo meu entendimento, os threads são completamente isolados da CPU pelo SO, pois isso não pode ser feito.
Apenas para esclarecer as coisas:
NÃO estou perguntando "Posso descobrir o número de núcleos da CPU no código e falhar de propósito?" ... Esse código seria mal intencionado (obriga a comprar uma CPU mais cara para executar um programa - sem a necessidade de energia computacional). Estou pedindo que seu código, digamos, tenha quatro threads e falhe quando dois threads são executados no mesmo núcleo físico (sem verificar explicitamente as informações do sistema e falhar propositadamente) .
Em resumo, pode haver software que exija vários núcleos, sem a necessidade de poder de computação adicional proveniente de vários núcleos? Exigiria apenas N núcleos físicos separados.
fonte
Respostas:
Pode ser possível fazer isso "por acidente" com o uso descuidado da afinidade central. Considere o seguinte pseudocódigo:
Se você iniciar quatro deles em uma CPU de dois núcleos, algo ocorrerá errado com a configuração de afinidade de núcleo ou você terminará com dois threads sobrecarregando os núcleos disponíveis e dois threads que nunca são agendados. Em nenhum momento ele perguntou explicitamente quantos núcleos existem no total.
(Se você possui threads de longa execução, definir a afinidade da CPU geralmente melhora a taxa de transferência)
A idéia de que as empresas de jogos estão "forçando" as pessoas a comprar hardware mais caro sem uma boa razão não é muito plausível. Só pode perder clientes para eles.
Edit: este post agora tem 33 votos positivos, o que é bastante dado que se baseia em suposições educadas!
Parece que as pessoas têm DA: I para executar mal em sistemas de núcleo duplo: http://www.dsogaming.com/pc-performance-analyses/dragon-age-inquisition-pc-performance-analysis/ Essa análise menciona que a situação melhora muito se o hyperthreading estiver ativado. Dado que o HT não adiciona mais unidades ou cache de problema de instrução, ele apenas permite que um thread seja executado enquanto outro está em um travamento de cache, o que sugere fortemente que ele está vinculado apenas ao número de threads.
Outro pôster afirma que a alteração dos drivers gráficos funciona: http://answers.ea.com/t5/Dragon-Age-Inquisition/Working-solution-for-Intel-dual-core-CPUs/td-p/3994141 ; dado que os drivers gráficos tendem a ser uma colméia miserável de escória e vilania, isso não é surpreendente. Um conjunto notório de drivers tinha um modo "correto e lento" versus "rápido e incorreto" que foi selecionado se chamado no QUAKE.EXE. É perfeitamente possível que os drivers se comportem de maneira diferente para diferentes números de CPUs aparentes. Talvez (voltando à especulação) um mecanismo de sincronização diferente seja usado. Uso indevido de spinlocks ?
"O uso indevido de primitivas de bloqueio e sincronização" é uma fonte muito, muito comum de erros. (O bug que eu deveria estar olhando no trabalho enquanto escrevia isso é "falha se alterar as configurações da impressora ao mesmo tempo em que o trabalho de impressão termina").
Editar 2: os comentários mencionam o sistema operacional tentando evitar a inanição do encadeamento. Observe que o jogo pode ter seu próprio quase agendador interno para atribuir trabalho a threads, e haverá um mecanismo semelhante na própria placa de vídeo (que é efetivamente um sistema de multitarefa próprio). As chances de um bug em um desses ou a interação entre eles são bastante altas.
www.ecsl.cs.sunysb.edu/tr/ashok.pdf (2008) é uma tese de pós-graduação sobre melhor programação para placas gráficas que menciona explicitamente que elas normalmente usam programação de primeiro a chegar, primeiro a ser servido, fácil de implementar em sistemas não-preventivos. A situação melhorou? Provavelmente não.
fonte
Pode ser necessário ter 4 núcleos porque o aplicativo executa quatro tarefas em threads paralelos e espera que eles terminem quase simultaneamente.
Quando cada encadeamento é executado por um núcleo separado e todos os encadeamentos possuem exatamente a mesma carga de trabalho computacional, é bem provável que (mas longe de ser garantido) termine aproximadamente ao mesmo tempo. Mas quando dois threads são executados em um núcleo, o tempo será muito menos previsível, pois o núcleo alternará o contexto entre os dois segmentos o tempo todo.
Os erros que ocorrem devido ao tempo inesperado do encadeamento são chamados de " condições de corrida ".
No contexto do desenvolvimento do jogo, uma arquitetura plausível com esse tipo de problema pode ser aquela em que diferentes recursos do jogo são simulados em tempo real por diferentes threads da CPU. Quando cada recurso é executado em um próprio núcleo, todos são simulados com aproximadamente a mesma velocidade. Mas quando dois recursos rodam em um núcleo, ambos são simulados apenas com a metade da velocidade do resto do mundo do jogo, o que pode causar todo tipo de comportamento estranho.
Observe que uma arquitetura de software que depende de threads independentes executados com horários específicos é extremamente frágil e é um sinal de péssimo entendimento da programação simultânea. Existem recursos disponíveis em praticamente todas as APIs de multithreading para sincronizar threads explicitamente para evitar esses tipos de problemas.
fonte
É improvável que esses "requisitos mínimos" representem algo abaixo do qual o jogo não será executado. Muito mais provável é que eles representem algo abaixo do qual o jogo não será executado com desempenho aceitável. Nenhuma empresa de jogos quer lidar com muitos clientes que reclamam de desempenho ruim quando o executam em uma única caixa de 1 Ghz, mesmo que o software possa tecnicamente ser executado. Portanto, eles provavelmente projetam deliberadamente falhas em caixas com menos núcleos do que lhes dariam um desempenho aceitável.
Uma métrica importante no desempenho do jogo é a taxa de quadros. Normalmente, eles são executados em 30 ou 60 quadros por segundo. Isso significa que o mecanismo do jogo precisa renderizar a visualização atual do estado do jogo em um período fixo de tempo. Para atingir 60 qps, há pouco mais de 16 ms para fazer isso. Jogos com gráficos de ponta são extremamente limitados à CPU e, portanto, há uma enorme troca entre tentar melhorar a qualidade (que leva mais tempo) e a necessidade de permanecer nesse orçamento de tempo. Assim, o orçamento de tempo para cada quadro é extremamente apertado.
Como o orçamento é curto, o desenvolvedor idealmente deseja acesso exclusivo a um ou mais núcleos. Eles provavelmente também querem poder fazer suas coisas de renderização em um núcleo, exclusivamente, pois é o que deve ser feito nesse orçamento de tempo, enquanto outras coisas, como o cálculo do estado mundial, acontecem em um processo separado, onde não são necessárias. intrometer.
Você poderia, em teoria, colocar tudo isso em um único núcleo, mas então tudo se torna muito mais difícil. De repente, você precisa garantir que todo o estado do jogo aconteça com rapidez suficiente e permita que a renderização ocorra. Você não pode simplesmente criar dois threads de software, porque não há como fazer com que o sistema operacional entenda "o thread A deve concluir uma quantidade X de trabalho em 16 ms, independentemente do segmento B".
Os desenvolvedores de jogos não têm interesse em fazer você comprar um novo hardware. A razão pela qual eles têm requisitos de sistema é que o custo do suporte a máquinas de gama baixa não vale a pena.
fonte
Três threads em tempo real que nunca dormem e um outro thread. Se houver menos de quatro núcleos, o quarto thread nunca será executado. Se o quarto encadeamento precisar se comunicar com um dos encadeamentos em tempo real para o encadeamento em tempo real, o código não terminará com menos de quatro núcleos.
Obviamente, se os encadeamentos em tempo real estiverem esperando algo que não lhes permita dormir (como um spinlock), o designer do programa estragou tudo.
fonte
Antes de tudo, os threads de software não têm nada a ver com os threads de hardware e geralmente são confusos. Os encadeamentos de software são partes de código que podem ser despachadas e executadas por si próprias no contexto do processo. Os threads de hardware são gerenciados principalmente pelo sistema operacional e são despachados para o núcleo do processador quando se fala de programas regulares. Esses threads de hardware são despachados com base na carga; o despachante de threads de hardware age mais ou menos como um balanceador de carga.
No entanto, quando se trata de jogos, especialmente jogos de ponta, às vezes os threads de hardware são gerenciados pelo próprio jogo ou o jogo instrui o despachante de threads de hardware sobre o que fazer. Isso ocorre porque todas as tarefas ou grupo de tarefas não têm a mesma prioridade, como em um programa normal. Como o dragon age vem de um estúdio de jogos de ponta que usa mecanismos de jogos de ponta, posso imaginar que ele usa expedição "manual" e, em seguida, o número de núcleos se torna um requisito mínimo do sistema. Qualquer programa falharia quando eu envio um pedaço de código para o terceiro núcleo físico em execução em uma máquina com apenas 1 ou 2 núcleos.
fonte
Como é possível usar o virtualize para ter mais núcleos virtuais do que físicos, o software não saberia que está sendo executado em um virtualizador e, em vez disso, achar que ele possui muitos núcleos físicos, eu diria que esse software não é possível.
Ou seja, não é possível escrever um software que sempre pare com menos de N núcleos.
Como outros já apontaram, existem soluções de software que podem verificar potencialmente, especialmente se o sistema operacional e o código em uso tiver pouca proteção contra as condições de corrida quando N processos executam em processadores <N. O verdadeiro truque é o código que falhará quando você tiver menos de N processadores, mas não falhará quando você tiver N processadores, mas tiver um SO que possa atribuir trabalho a menos de N processadores.
fonte
Pode ser que haja três threads fazendo alguma coisa (gerando fundos ou gerando movimento do NPC) e passando eventos para um quarto, que deve agregar / filtrar os eventos e atualizar o modelo de exibição. Se o quarto encadeamento não receber todos os eventos (porque não está agendado em um núcleo), o modelo de exibição não será atualizado corretamente. Isso pode acontecer apenas esporadicamente, mas esses núcleos precisam estar disponíveis a qualquer momento. Isso pode explicar por que você não está vendo alto uso da CPU o tempo todo, mas o jogo não está funcionando adequadamente.
fonte
Acho que Joshua está seguindo o caminho certo, mas não para a conclusão.
Suponha que você tenha uma arquitetura em que há três threads escritos para fazer o máximo possível - quando terminam o que estão fazendo, o fazem novamente. Para manter o desempenho atualizado, esses encadeamentos não liberam o controle de nada - eles não querem arriscar o atraso do agendador de tarefas do Windows. Contanto que haja 4 ou mais núcleos, isso funcione bem, ele falhará se não houver.
Em geral, isso seria uma programação ruim, mas os jogos são outra questão - quando você se depara com uma escolha entre um design inferior em todo hardware ou um design superior em hardware suficientemente bom ou uma falha nos desenvolvedores de jogos de hardware inferior geralmente escolhem para exigir o hardware.
fonte
Is it possible to write code (or complete software, rather than a piece of code) that won't work properly when run on a CPU that has less than N number of cores?
Absolutamente. O uso de encadeamentos em tempo real seria um bom exemplo de uma situação em que essa é, não apenas possível, mas a maneira desejada (e geralmente a única maneira correta) de realizar o trabalho. No entanto, os encadeamentos em tempo real geralmente são limitados ao kernel do SO, geralmente para drivers que precisam garantir que um evento de hardware de algum tipo seja tratado dentro de um período definido. Você não deve ter threads em tempo real em aplicativos de usuário normais e não tenho certeza de que seja possível ter um em um aplicativo de modo de usuário do Windows. Geralmente, os sistemas operacionais tornam intencionalmente impossível fazer isso do local do usuário precisamente porque permite que um determinado aplicativo assuma o controle do sistema.
Em relação aos aplicativos de terra do usuário: Sua suposição de que a verificação de um determinado número de encadeamentos para execução seja necessariamente intencionalmente maliciosa não está correta. Por exemplo, você pode ter duas tarefas de longa execução e com alto desempenho que precisam de um núcleo para si mesmas. Independentemente da velocidade do núcleo da CPU, o compartilhamento de um núcleo com outros encadeamentos pode ser uma degradação séria e inaceitável do desempenho devido à interrupção do cache, além das penalidades normais decorrentes da troca de encadeamentos (que são bastante substanciais.) Nesse caso, seria perfeitamente razoável, especialmente para um jogo, defina cada um desses threads para ter afinidade apenas em um núcleo específico para cada um deles e defina todos os outros threads para não ter afinidade nesses 2 núcleos. Para fazer isso, no entanto, você '
fonte
Qualquer código que use spinlocks com uma quantidade notável de contenção de bloqueio terá um desempenho terrível (até um ponto em que - para um aplicativo como um jogo - você pode dizer "não funciona" ) se o número de threads exceder o número de núcleos.
Imagine, por exemplo, um encadeamento produtor enviando tarefas para uma fila que atenda a 4 encadeamentos do consumidor. Existem apenas dois núcleos:
O produtor tenta obter o spinlock, mas ele é mantido por um consumidor rodando no outro núcleo. Os dois núcleos estão executando o locktep enquanto o produtor está girando, aguardando a liberação do bloqueio. Isso já é ruim, mas não é tão ruim quanto será.
Infelizmente, o encadeamento do consumidor é quântico no final de seu tempo, portanto é antecipado e outro encadeamento do consumidor está agendado. Ele tenta se apossar da trava, mas é claro que a trava foi executada, agora dois núcleos estão girando e aguardando algo que não pode acontecer.
O encadeamento do produtor chega ao final do seu intervalo de tempo e é antecipado, outro consumidor acorda. Novamente, dois consumidores estão aguardando a liberação de um bloqueio, e isso não acontecerá antes que mais dois quantum de tempo tenham passado.
[...] Finalmente, o consumidor que estava segurando o spinlock liberou a trava. É imediatamente capturado por quem está girando no outro núcleo. Há uma chance de 75% (3 a 1) de ser outro segmento do consumidor. Em outras palavras, é 75% provável que o produtor ainda esteja parado. Obviamente, isso significa que os consumidores também param. Sem as tarefas de submissão do produtor, elas não têm nada a fazer.
Observe que isso funciona em princípio com qualquer tipo de bloqueio, não apenas spinlocks - mas o efeito devastador é muito mais proeminente com os spinlocks, porque a CPU mantém ciclos de gravação sem conseguir nada.
Agora imagine que, além do exposto acima, algum programador teve a brilhante idéia de usar um thread dedicado com afinidade definida no primeiro núcleo, para que o RDTSC proporcione resultados confiáveis em todos os processadores (de qualquer forma, mas algumas pessoas pensam assim).
fonte
Se eu entendo o que você está perguntando, é possível, mas é uma coisa muito, muito ruim.
O exemplo canônico do que você está descrevendo seria manter um contador que é incrementado por vários threads. Isso requer quase nada em termos de poder de computação, mas exige uma coordenação cuidadosa entre os threads. Desde que apenas um encadeamento por vez faça um incremento (que na verdade é uma leitura seguida por uma adição seguida por uma gravação), seu valor sempre estará correto. Isso ocorre porque um thread sempre lê o valor "anterior" correto, adiciona um e escreve o valor "próximo" correto. Coloque dois threads na ação ao mesmo tempo e ambos lerão o mesmo valor "anterior", obterão o mesmo resultado do incremento e gravarão o mesmo valor "próximo". O contador terá sido efetivamente incrementado apenas uma vez, embora dois threads pensem que cada um deles fez isso.
Essa dependência entre tempo e correção é o que a ciência da computação chama de condição de corrida .
Geralmente, as condições de corrida são evitadas usando mecanismos de sincronização para garantir que os threads que desejam operar em um pedaço de dados compartilhados entrem na fila para acessar. O contador descrito acima pode usar um bloqueio de leitura e gravação para isso.
Sem acesso ao design interno de Dragon Age: Inquisition , tudo o que qualquer um pode fazer é especular sobre por que ele se comporta dessa maneira. Mas vou tentar com base em algumas coisas que já vi feitas em minha própria experiência:
Pode ser que o programa seja baseado em quatro threads que foram ajustados para que tudo funcione quando os threads forem executados principalmente - ininterruptamente em seus próprios núcleos físicos. O "ajuste" pode vir na forma de reorganizar o código ou inserir dormentes em locais estratégicos para mitigar os erros induzidos pelas condições de corrida que surgiram durante o desenvolvimento. Novamente, tudo isso é conjectura, mas vi as condições da corrida "resolvidas" dessa maneira mais vezes do que gostaria de contar.
A execução de um programa como esse em algo menos capaz do que o ambiente para o qual foi ajustado introduz alterações de tempo resultantes do código não ser executado tão rapidamente ou, mais provavelmente, de alternâncias de contexto. As alternâncias de contexto ocorrem nas formas física (ou seja, os núcleos físicos da CPU estão alternando entre o trabalho que seus núcleos lógicos estão mantendo) e lógica (ou seja, o sistema operacional na CPU está atribuindo trabalho aos núcleos), mas também é uma divergência significativa do que seria o tempo de execução "esperado". Isso pode trazer à tona o mau comportamento.
Se Dragon Age: Inquisition não der o simples passo de garantir que haja núcleos físicos suficientes disponíveis antes de continuar, a culpa é da EA. Eles provavelmente estão gastando uma pequena fortuna atendendo chamadas e e-mails de suporte de pessoas que tentaram rodar o jogo com pouco hardware.
fonte
O Windows possui uma funcionalidade interna para isso: a função GetLogicalProcessorInformation está na API do Windows . Você pode chamá-lo do seu programa para obter informações sobre núcleos, núcleos virtuais e hyperthreading.
Portanto, a resposta para sua pergunta seria: Sim.
fonte
/proc/cpuinfo
esysconf(_SC_NPROCESSORS_ONLN)
(o último sendo mencionado no POSIX). Usar as informações para impor um limite mínimo de desempenho ainda é uma péssima forma.