l rendimiento de multiprocesamiento de @Python solo mejora con la raíz cuadrada de la cantidad de núcleos utilizados

Estoy intentando implementar el multiprocesamiento en Python (Windows Server 2012) y tengo problemas para lograr el grado de mejora del rendimiento que espero. En particular, para un conjunto de tareas que son casi completamente independientes, Esperaría una mejora lineal con núcleos adicionales.

Entiendo que, especialmente en Windows, hay gastos generales al abrir nuevos procesos [1], y que muchas peculiaridades del código subyacente pueden interponerse en una tendencia limpia. Pero, en teoría, la tendencia debería ser, en última instancia, casi lineal para una tarea totalmente paralela [2]; o quizás logístico si estuviera tratando con una tarea parcialmente en serie [3].

in embargo, cuando ejecuto multiprocesamiento. Pool en una función de prueba de comprobación principal (código a continuación), obtengo una relación de raíz cuadrada casi perfecta hastaN_cores=36 (el número de núcleos físicos en mi servidor) antes del rendimiento esperado cuando llego a los núcleos lógicos adicionales.

Aqu es una gráfica de los resultados de mi prueba de rendimiento:
(" Rendimiento normalizado" es[ un tiempo de ejecución con1 CPU-core] dividido por[ un tiempo de ejecución conN Núcleos de CPU]).

Es normal tener esta disminución dramática de los retornos con multiprocesamiento? ¿O me falta algo con mi implementación?

import numpy as np
from multiprocessing import Pool, cpu_count, Manager
import math as m
from functools import partial
from time import time

def check_prime(num):

    #Assert positive integer value
    if num!=m.floor(num) or num<1:
        print("Input must be a positive integer")
        return None

    #Check divisibility for all possible factors
    prime = True
    for i in range(2,num):
        if num%i==0: prime=False
    return prime

def cp_worker(num, L):
    prime = check_prime(num)
    L.append((num, prime))


def mp_primes(omag, mp=cpu_count()):
    with Manager() as manager:
        np.random.seed(0)
        numlist = np.random.randint(10**omag, 10**(omag+1), 100)

        L = manager.list()
        cp_worker_ptl = partial(cp_worker, L=L)

        try:
            pool = Pool(processes=mp)   
            list(pool.imap(cp_worker_ptl, numlist))
        except Exception as e:
            print(e)
        finally:
            pool.close() # no more tasks
            pool.join()

        return L


if __name__ == '__main__':
    rt = []
    for i in range(cpu_count()):
        t0 = time()
        mp_result = mp_primes(6, mp=i+1)
        t1 = time()
        rt.append(t1-t0)
        print("Using %i core(s), run time is %.2fs" % (i+1, rt[-1]))

Nota Soy consciente de que para esta tarea probablemente sería más eficiente implementar multi roscado, pero el script real para el que este es un análogo simplificado es incompatible con el subprocesamiento múltiple de Python debido a GIL.

Respuestas a la pregunta(1)

Su respuesta a la pregunta