A cada fila de un marco de datos hay una operación común. Estas operaciones son vergonzosamente paralelas: cada fila se puede procesar de forma independiente. Con una CPU de múltiples núcleos, muchas filas se pueden procesar a la vez.
Hasta hace poco, no era posible explotar esta oportunidad en Python. La aplicación de función multiproceso, al estar unida a la CPU, fue estrangulada por el Lock Global Interpreter (GIL).
Python ahora ofrece una solución: con la “construcción experimental de subproceso libre” de Python 3.13, se elimina el GIL y es posible una verdadera concurrencia múltiple de las operaciones unidas a la CPU.
Los beneficios de rendimiento son extraordinarios. Aprovechando la pitón de subproceso libre, Marco estático 3.2 puede realizar una aplicación de función en cuanto a fila en un marco de datos al menos el doble de rápido que la ejecución de un solo subproceso.
Por ejemplo, para cada fila de un marco de datos cuadrado de un millón de enteros, podemos calcular la suma de todos los valores pares con lambda s: s.loc[s % 2 == 0].sum(). Cuando se usa Python 3.13t (la “T” denota la variante de subpuesto libre), la duración (medida con ipython %timeit) cae en más del 60%, de 21.3 ms a 7.89 ms:
# Python 3.13.5 experimental free-threading build (main, Jun 11 2025, 15:36:57) [Clang 16.0.0 (clang-1600.0.26.6)] on darwin
>>> import numpy as np; import static_frame as sf
>>> f = sf.Frame(np.arange(1_000_000).reshape(1000, 1000))
>>> func = lambda s: s.loc[s % 2 == 0].sum()
>>> %timeit f.iter_series(axis=1).apply(func)
21.3 ms ± 77.1 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)
>>> %timeit f.iter_series(axis=1).apply_pool(func, use_threads=True, max_workers=4)
7.89 ms ± 60.1 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
La aplicación de la función de la fila en staticframe usa el iter_series(axis=1) interfaz seguida de cualquiera apply() (para la aplicación de un solo hilo) o apply_pool() para múltiples subprocesos (use_threads=True) o multiprocesado (use_threads=False) solicitud.
Los beneficios del uso de Python de subproceso libre son robustos: el rendimiento superior es consistente en una amplia gama de formas y composiciones de marcos de datos, es proporcional tanto en MacOS como en Linux, y escala positivamente con el tamaño del marco de datos.
Cuando se usa Python estándar con el procesamiento GIL habilitado, con múltiples subprocesos de procesos unidos a CPU a menudo degrada el rendimiento. Como se muestra a continuación, la duración de la misma operación en Python estándar aumenta de 17.7 ms con un solo hilo a casi 40 ms con hilo múltiple:
# Python 3.13.5 (main, Jun 11 2025, 15:36:57) [Clang 16.0.0 (clang-1600.0.26.6)]
>>> import numpy as np; import static_frame as sf
>>> f = sf.Frame(np.arange(1_000_000).reshape(1000, 1000))
>>> func = lambda s: s.loc[s % 2 == 0].sum()
>>> %timeit f.iter_series(axis=1).apply(func)
17.7 ms ± 144 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
>>> %timeit f.iter_series(axis=1).apply_pool(func, use_threads=True, max_workers=4)
39.9 ms ± 354 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
Hay compensaciones cuando se usan Python de hilo libre: como aparente en estos ejemplos, el procesamiento de subproceso único es más lento (21.3 ms en 3.13T en comparación con 17.7 ms en 3.13). Python de hilo libre, en general, incurre en gastos generales de rendimiento. Esta es un área activa del desarrollo de CPython y se esperan mejoras en 3.14t y más allá.
Además, aunque muchos paquetes de extensión C como Numpy ahora ofrecen ruedas binarias precompiladas por 3.13T, todavía existe riesgos como contención de subprocesos o carreras de datos.
StaticFrame evita estos riesgos al hacer cumplir la inmutabilidad: la seguridad del hilo está implícita, eliminando la necesidad de bloqueos o copias defensivas. StaticFrame hace esto usando matrices Numpy inmutables (con flags.writeable empezar a False) y prohibir la mutación en el lugar.
Pruebas de rendimiento de marcos de datos extendidos
Evaluar las características de rendimiento de una estructura de datos compleja como un marcado de datos requiere probar muchos tipos de marcos de datos. Los siguientes paneles de rendimiento realizan una aplicación de función en forma de fila en nueve tipos de marco de datos diferentes, probando todas las combinaciones de tres formas y tres niveles de homogeneidad de tipo.
Para un número fijo de elementos (por ejemplo, 1 millón), se prueban tres formas: Tall (10,000 por 100), cuadrado (1,000 por 1,000) y anchos (100 por 10,0000). Para variar la homogeneidad del tipo, se definen tres categorías de datos sintéticos: columnar (ninguna columna adyacente tiene el mismo tipo), mixtos (grupos de cuatro columnas adyacentes comparten el mismo tipo) y uniforme (todas las columnas son del mismo tipo). StaticFrame permite que las columnas adyacentes del mismo tipo se representen como matrices numpy bidimensionales, reduciendo los costos de transversal de columnas y formación de filas. En el extremo uniforme, un marco de datos completo puede representarse mediante una matriz bidimensional. Los datos sintéticos se producen con el accesorios de cuadro paquete.
Se utiliza la misma función: lambda s: s.loc[s % 2 == 0].sum(). Si bien es posible una implementación más eficiente usando Numpy directamente, esta función se aproxima a aplicaciones comunes donde muchas intermedias Series se crean.
Figura Legends Document Configuración de concurrencia. Cuando use_threads=Truese usa múltiples subprocesos; cuando use_threads=Falsese usa multiprocesamiento. Staticframe usa el ThreadPoolExecutor y ProcessPoolExecutor interfaces de la biblioteca estándar y expone sus parámetros: el max_workers El parámetro define el número máximo de subprocesos o procesos utilizados. A chunksize El parámetro también está disponible, pero no se varía en este estudio.
Aplicación de función multiproceso con Python 3.13T de subproceso libre
Como se muestra a continuación, los beneficios de rendimiento del procesamiento multiproceso en 3.13T son consistentes en todos los tipos de marco de datos probados: el tiempo de procesamiento se reduce en al menos un 50%y, en algunos casos, en más del 80%. El número óptimo de hilos (el max_workers Parámetro) es más pequeño para los marcos de datos altos, ya que el procesamiento más rápido de filas más pequeñas significa que el subconjunto adicional en realidad degrada el rendimiento.
La escala a marcos de datos de 100 millones de elementos (1e8), el rendimiento superior mejora. El tiempo de procesamiento se reduce en más del 70% para todos menos dos tipos de marcos de datos.
La sobrecarga de múltiples subprocesos puede variar mucho entre las plataformas. En todos los casos, el rendimiento superior del uso de Python de hilo libre es proporcionalmente consistente entre MacOS y Linux, aunque MACOS muestra beneficios marginalmente mayores. El procesamiento de 100 millones de elementos en Linux muestra un rendimiento relativo similar:
Sorprendentemente, incluso pequeños marcos de datos de solo diez mil elementos (1e4) pueden beneficiarse del procesamiento múltiple en 3.13t. Si bien no se encuentra ningún beneficio para los amplios marcos de datos, el tiempo de procesamiento de los marcos de datos altos y cuadrados se puede reducir a la mitad.
Aplicación de función multiproceso con Python estándar 3.13
Antes de la Python de hilo libre, el procesamiento multiproceso de aplicaciones unidas a CPU dio como resultado un rendimiento degradado. Esto se aclara a continuación, donde se realizan las mismas pruebas con Python 3.13 estándar.
Aplicación de función multiprocesada con Python 3.13 estándar
Antes de Python de hilo libre, el procesamiento múltiple era la única opción para la concurrencia unida a CPU. Sin embargo, el procesamiento múltiple solo entregó beneficios si la cantidad de trabajo por proceso era suficiente para compensar el alto costo de crear un intérprete por proceso y copiar datos entre procesos.
Como se muestra aquí, la aplicación de la función de procesamiento múltiple en cuanto a la fila degrada significativamente el rendimiento, el tiempo de proceso aumenta de dos a diez veces la duración de un solo hilo. Cada unidad de trabajo es demasiado pequeña para compensar la sobrecarga múltiple.
El estado de la pitón de subproceso libre
PEP 703“Hacer que el intérprete global sea opcional en Cpython”, fue aceptado por el Consejo Directivo de Python en julio de 2023 con la guía de que, en la primera fase (para Python 3.13) es experimental y no default; En la segunda fase, se vuelve no experimental y oficialmente compatible; En la tercera fase, se convierte en la implementación predeterminada de Python.
Después de un significativo desarrollo de CPython, y el apoyo por paquetes críticos como Numpy, PEP 779“Criterios para el estado compatible para Python de subproceso libre” fue aceptado por el Consejo Directivo de Python en junio de 2025. En Python 3.14, Python de subproceso libre ingresará a la segunda fase: no experimental y oficialmente compatible. Si bien aún no es seguro cuando la pitón de tinta libra se convertirá en el valor predeterminado, está claro que se establece una trayectoria.
Conclusión
La aplicación de la función en cuanto a filas es solo el comienzo: las operaciones grupales, la aplicación de la función ventana y muchas otras operaciones en marcos de datos inmutables son igualmente adecuados para la ejecución concurrente y es probable que muestren ganancias de rendimiento comparables.
El trabajo para hacer que Cpython sea más rápido ha tenido éxito: se dice que Python 3.14 es del 20% al 40% más rápido que Python 3.10. Desafortunadamente, esos beneficios de rendimiento no se han realizado para muchos trabajando con Dataframes, donde el rendimiento está en gran medida dentro de las extensiones C (ya sea Numpy, Arrow u otras bibliotecas).
Como se muestra aquí, el pitón de subproceso libre permite una ejecución paralela eficiente utilizando hilos de bajo costo y eficientes en memoria, que ofrece una reducción del 50% al 90% en el tiempo de procesamiento, incluso cuando el rendimiento está principalmente vinculado en bibliotecas de extensión C como Numpy. Con la capacidad de compartir de manera segura estructuras de datos inmutables en hilos, las oportunidades para mejoras sustanciales de rendimiento ahora son abundantes.