Você provavelmente está atingindo pontos de contenção no código, como o bloqueio (por meio de futexes ou similares) em que ocorre a serialização do código e, portanto, interrompe o dimensionamento de desempenho.
Além disso, uma CPU x86 pode ter N núcleos, cada um com 2 segmentos cada, mas isso não lhe dá um desempenho de 2 x N, já que um hyperthread é executado quando determinados blocos de execução estão disponíveis. Acredito que para um único processador x86 com soquete pode-se obter até 30% de desempenho extra com um hyperthread.
Além disso, você pode estar recebendo contenção na memória, seja no cache (L1, L2 ou L3) ou até mesmo na própria memória. Então você pode estar atingindo limitações no throughput, no cache stall ou no TLB.
Com o processo N > N CPUs, você vai acabar com mais processos do que pode ser executado, por isso, o agendador tem que realizar mais trabalho em antecipar processos executáveis e isso é outra penalidade que entra em desempenho.
Você pode obter métricas de desempenho de baixo nível usando ferramentas como perf. Instale-o com:
sudo apt-get install linux-tools
E execute seu aplicativo com perf para obter algumas medidas de desempenho:
perf stat your-program
Você pode fazer uma análise mais detalhada usando o registro de desempenho e o relatório de desempenho, por exemplo
sudo perf record your-program
sudo perf report
Como alternativa, execute seu programa e, enquanto ele estiver em execução, use perf top para obter uma visualização interativa em tempo real da atividade do sistema:
sudo perf top
Espero que isso lhe dê uma ideia de onde o gargalo está ocorrendo.