Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Procesamiento de datos: paralelismo y rendimiento
Johnson M. Hart
Descargar el ejemplo de código
El procesamiento de colecciones de datos es una tarea de procesamiento fundamental y varios problemas prácticos son inherentemente paralelos, lo que puede permitir potencialmente una mejora en el rendimiento y la capacidad de proceso en sistemas de varios núcleos. Compararé varios métodos diferentes con base en Windows para resolver problemas con un alto grado de paralelismo de datos.
Para esta comparación, usaré como referencia el problema de búsqueda (“Geonames”) del capítulo 9 del libro de Troy Magennis “LINQ to Objects Using C# 4.0” (Addison-Wesley, 2010). Las soluciones alternativas son las siguientes:
- Parallel Language Integrated Query (PLINQ) y C# 4.0, con y sin mejoras al código original.
- código nativo de Windows usando C, la API de Windows, subprocesos y archivos asignados en memoria.
- C# / Código multiproceso de Microsoft .NET Framework de Windows.
El código fuente para todas las soluciones está disponible en mi sitio web (jmhartsoftware.com). No se analizarán directamente otras técnicas de paralelismo, como Windows Task Parallel Library (TPL), aunque PLINQ esté superpuesto en TPL.
Comparación y evaluación de soluciones alternativas
Los criterios de evaluación de solución, ordenados en orden de importancia, son los siguientes:
- rendimiento total, en cuanto al transcurso de tiempo para completar la tarea.
- escalabilidad con el grado de paralelismo (número de tareas), número de núcleos y tamaño de la colección de datos.
- simpleza, elegancia y facilidad de mantención de código, entre otros factores intangibles similares.
Resumen de resultados
Este artículo le mostrará que, en un problema de búsqueda de referencia representativo:
- se puede explotar satisfactoriamente a los sistemas de 64 bits de varios núcleos de manera que mejoren el rendimiento en varios problemas de procesamiento de datos, y PLINQ puede ser parte de la solución.
- el rendimiento competitivo y escalable de PLINQ exige objetos de colección de datos indizados; no basta con la compatibilidad con la interfaz IEnumerable.
- las soluciones de C#/.NET y de código nativo son las más rápidas.
- la solución PLINQ original es más lenta por un factor de casi 10 y no existe escala entre ambas tareas, mientras que la otra solución se escala bien hasta con seis tareas con seis núcleos (el número máximo que se probó). Sin embargo, las mejoras de código mejoran significativamente a la solución original.
- el código PLINQ es el más simple y más elegante en todo término, puesto que LINQ proporciona capacidades de consulta declarativa para datos residentes en memoria y externos. El código nativo es tosco y el código C#/.NET es considerablemente mejor que el PLINQ, aunque no es tan simple.
- Todos los métodos se escalan bien a medida que se aumenta el tamaño de los archivos, hasta alcanzar el límite de la memoria física del sistema de prueba.
El problema de referencia: Geonames
La idea de este artículo surgió a partir del capítulo 9 del libro de LINQ de Magennis, el cual hace una demostración de PLINQ al buscar en una base de datos geográfica que contiene más de 7,25 millones de nombres de lugares, dentro de un archivo de 825 MB (esto es más de una ubicación por cada 1.000 personas). Cada lugar está representado por un registro de línea de texto UTF8 (es.wikipedia.org/wiki/UTF-8), de largo variable, con más de 15 columnas de datos separadas por tabulaciones. Nota: la codificación UTF-8 asegura que un valor de tabulación (0x9) o de avance de línea (0xA) no ocurrirán como parte de una secuencia de múltiples bytes. Esto es fundamental para varias implementaciones.
El programa Geonames de Magennis implementa una solicitud codificada en forma rígida para identificar todas las ubicaciones con una elevación (columna 15) mayor a 8.000 metros, mostrando el nombre, país y elevación de la ubicación de mayor a menor elevación. Si desea saberlo, existen 16 ubicaciones con esta característica; el Monte Everest es el más alto de ellos, con 8.846 metros.
Los informes de Magennis indican un tiempo transcurrido de 22,3 segundos (un núcleo) y 14,1 segundos (dos núcleos). Experiencias previas (por ejemplo, consulte mi artículo “Windows Parallelism, Fast File Searching and Speculative Processing” en informit.com/articles/article.aspx?p=1606242) indican que los archivos de este tamaño se pueden procesar en pocos segundos y que el rendimiento se escala bien con el número de núcleos. Por lo tanto, decidí intentar replicar esa experiencia e intentar mejorar el código PLINQ de Magennis para obtener un mejor rendimiento. Las mejoras iniciales a PLINQ casi duplicaron el rendimiento, pero no mejoraron la escalabilidad. Sin embargo, las mejoras proporcionan, en efecto, un rendimiento casi tan bueno como el código nativo y el código multiproceso de C#.
Esta referencia es interesante por varios motivos:
- el sujeto (lugares y características geográficos) es inherentemente interesante y es fácil generalizar la búsqueda.
- existe un alto grado de paralelismo de datos. En principio, cada registro se podría procesar simultáneamente.
- El tamaño del archivo es modesto para los estándares de hoy, pero es fácil probar archivos más grandes simplemente al concatenar el archivo allCountries.txt de Geonames a sí mismo varias veces.
- el proceso no carece de estados: es necesario determinar los límites de línea y campo para poder segmentar el archivo y las líneas se deben procesar para identificar los campos individuales.
Un supuesto: suponga que el número de registros identificados (en esta instancia, ubicaciones de altitud superior a los 8.000 metros) es pequeña, de manera que el tiempo de organización y muestra es mínima en relación al tiempo total del proceso, que implica examinar cada byte.
Otro supuesto: los resultados del rendimiento representan la cantidad de tiempo necesaria para procesar colecciones de datos residentes en memoria, como datos producidos por una etapa previa del programa. El programa de referencia lee un archivo, pero los programas de prueba se ejecutan varias veces para asegurarse de que el archivo resida en memoria. Sin embargo, se hará mención del tiempo necesario para cargar el archivo inicialmente, el cual es aproximadamente el mismo para todas las soluciones.
Comparación de rendimiento
El primer sistema de prueba es un sistema de escritorio de seis núcleos que ejecuta Windows 7 (AMD Phenom II, 2.80 GHz, 4 GB de RAM). Más adelante se presentarán los resultados para otros tres sistemas con hyper-threading o HT (es.wikipedia.org/wiki/HyperThreading) y diferentes números de núcleos.
La figura 1 indica los resultados de seis soluciones de Geonames diferentes, con el tiempo transcurrido (en segundos) como una función del “grado de paralelización” o DoP (el número de tareas paralelas que se pueden configurar para superar al número de procesadores). El sistema de prueba tiene seis núcleos, pero las implementaciones controlan al DoP. Seis tareas son lo óptimo. Usar más que esto degrada el rendimiento. Todas las pruebas usan el archivo de datos allCountries.txt original de 825 MB de Geonames.
Figura 1 Rendimiento de Geonames como una función de grado de paralelismo
Las implementaciones son las siguientes (se proporcionarán explicaciones más detalladas posteriormente):
- Geonames Original. Esta es la solución original de PLINQ de Magennis. El rendimiento no es competitivo ni se escala con el número de procesadores.
- Geonames Helper. Una versión con rendimiento mejorado de Geonames Original.
- Geonames MMChar. Este fue un intento infructuoso de mejorar Geonames Helper con una clase de archivo asignado en memoria similar al usado en Geonames Threads. Nota: la asignación de memoria permite hacer referencia a un archivo como si estuviese en memoria sin operaciones explícitas de I/O y puede proporcionar beneficios de rendimiento.
- Geonames MMByte. Esta solución modifica MMChar para procesar el bit individual del archivo de entrada, mientras que las tres soluciones previas convierten los caracteres UTF-8 a Unicode (con 2 bytes cada uno). Este muestra el mejor rendimiento de las primeras cuatro soluciones y es más del doble que el de Geonames Original.
- Geonames Threads no usa PLINQ. Esta es la implementación C#/.NET que usa subprocesos y un archivo asignado en memoria. Su rendimiento es superior al de Index (el elemento a continuación) y casi el mismo de Native. Esta solución y Geonames Native proporcionan el mejor paralelismo de escalabilidad.
- Geonames Index. Esta solución PLINQ procesa previamente el archivo de datos (lo que toma alrededor de nueve segundos) para crear un objeto List<byte[]> residente en memoria para procesamiento posterior de PLINQ. El costo de procesamiento previo se puede amortizar a través de consultas múltiples, lo que proporciona un rendimiento ligeramente inferior a Geonames Native y Geonames Threads.
- Geonames Native (que no se indica en la figura 1) no usa PLINQ. Esta es mi implementación de la API de Windows de C que usa subprocesos y un archivo asignado en memoria, como se muestra en el capítulo 10 de mi libro, “Windows System Programming” (Addison-Wesley, 2010). La optimización total del compilador es esencial para estos resultados, la optimización predeterminada proporciona sólo la mitad del rendimiento.
Todas las implementaciones son versiones de 64 bits. Las versiones de 32 bits funcionan en la mayor parte de los casos, pero producen errores para archivos de mayor tamaño (ver la figura 2). La figura 2 muestra el rendimiento con DoP 4 y usando archivos de mayor tamaño.
Figura 2 Rendimiento de Geonames como una función de tamaño de archivo
El sistema de prueba de este caso tiene cuatro núcleos (AMD Phenom Quad-Core, 2.40 GHz, 8GB RAM). Estos archivos de mayor tamaño se crearon al concatenar varias copias del archivo original. La figura 2 muestra sólo las tres soluciones más rápidas, lo que incluye Geonames Index, la solución PLINQ más rápida (sin contar el procesamiento previo de archivos), y el rendimiento se escala a medida que aumenta el tamaño del archivo hasta el límite de la memoria física.
A continuación se describirán las implementaciones desde la dos a la siete y se analizarán las técnicas de PLINQ en más detalle. A continuación, se analizarán los resultados en otros sistemas de prueba y se dará una síntesis de los datos.
Las soluciones PLINQ mejoradas: Geonames Helper
La figura 3 muestra a Geonames Helper con mis cambios (en negrita) al código de Geonames Original.
Figura 3 Geonames Helper con los cambios al código original de PLINQ resaltados
class Program
{
static void Main(string[] args)
{
const int nameColumn = 1;
const int countryColumn = 8;
const int elevationColumn = 15;
String inFile = "Data/AllCountries.txt";
if (args.Length >= 1) inFile = args[0];
int degreeOfParallelism = 1;
if (args.Length >= 2) degreeOfParallelism = int.Parse(args[1]);
Console.WriteLine("Geographical data file: {0}.
Degree of Parallelism: {1}.", inFile, degreeOfParallelism);
var lines = File.ReadLines(Path.Combine(
Environment.CurrentDirectory, inFile));
var q = from line in
lines.AsParallel().WithDegreeOfParallelism(degreeOfParallelism)
let elevation =
Helper.ExtractIntegerField(line, elevationColumn)
where elevation > 8000 // elevation in meters
orderby elevation descending
select new
{
elevation = elevation,
thisLine = line
};
foreach (var x in q)
{
if (x != null)
{
String[] fields = x.thisLine.Split(new char[] { '\t' });
Console.WriteLine("{0} ({1}m) - located in {2}",
fields[nameColumn], fields[elevationColumn],
fields[countryColumn]);
}
}
}
}
Dado que es posible que varios lectores no estén familiarizados con PLINQ y C# 4.0, proporcionaré algunos comentarios sobre la figura 3, lo que incluye descripciones de las mejoras:
- Las líneas 9 a 14 permiten al usuario especificar el nombre de archivo de entrada y el grado de paralelismo (el número máximo de tareas simultáneas) en la línea de comando. Estos valores se codificaron de forma rígida en el original.
- Las líneas 16 y 17 empiezan a leer las líneas de archivo de forma asíncrona y escriben implícitamente líneas como una matriz String de C#. Los valores de las líneas no se usan hasta las líneas 19 a 27. Otras soluciones, como Geonames MMByte, usan una clase distinta en su método ReadLines, y esas líneas de código son las únicas que se deben cambiar.
- Las líneas 19 a 27 son código LINQ junto con la extensión PLINQ AsParallel. El código es similar a SQL y la variable “q” se escribe implícitamente como una matriz de objetos que consisten de una elevación de entero y un objeto String. Observe que PLINQ realiza todo el trabajo de administración de subprocesos. El método AsParallel es todo lo necesario para convertir el código de serie LINQ a código PLINQ.
- Línea 20. La figura 4 muestra el método Helper.ExtractIntegerField. El programa original usa el método String.Split en una manera similar a la usada para mostrar los resultados en la línea 33 (figura 3). Esto es la clave para el rendimiento mejorado de Genome Helper en contraste a Genome Original, pues ya no es necesario ubicar objetos String en cada campo de cada línea.
Figura 4 El método ExtractIntegerField de la clase Helper de Geonames
class Helper
{
public static int ExtractIntegerField(String line, int fieldNumber)
{
int value = 0, iField = 0;
byte digit;
// Skip to the specified field number and extract the decimal value.
foreach (char ch in line)
{
if (ch == '\t') { iField++; if (iField > fieldNumber) break; }
else
{
if (iField == fieldNumber)
{
digit = (byte)(ch - 0x30); // 0x30 is the character '0'
if (digit >= 0 && digit <= 9)
{ value = 10 * value + digit; }
else // Character not in [0-9]. Reset the value and quit.
{ value = 0; break; }
}
}
}
return value;
}
}
Observe que el método AsParallel que se usó en la línea 19 se puede usar en cualquier objeto IEnumerable. Como se mencionó anteriormente, la figura 4 muestra el método ExtractIntegerField de la clase Helper. Este simplemente extrae y evalúa el campo especificado (en este caso, la elevación), evitando métodos de biblioteca para un mejor rendimiento. La figura 1 muestra que esta mejora duplica el rendimiento con DoP 1.
Geonames MMChar y Geonames MMByte
Geonames MMChar es un intento infructuoso de mejorar el rendimiento al asignar en memoria el archivo de entrada usando una clase personalizada, FileMmChar. Geonames MMByte, por otra parte, efectivamente proporciona beneficios significativos, puesto que los bytes de entrada de archivo no se expanden a Unicode.
MMChar necesita una clase nueva, FileMmChar, que es compatible con la interfaz de IEnumerable<String>. La clase FileMmByte es similar a los objetos byte[] y trabaja con ellos en lugar de con los objetos String. El único cambio significativo de código se puede ve en la figura 3, las líneas 16 a 17, las cuales ahora son:
var lines = FileMmByte.ReadLines(Path.Combine(
Environment.CurrentDirectory, inFile));
El código de
public static IEnumerable<byte[]> ReadLines(String path)
que es compatible con la interfaz de IEnumerable<byte[]> en FileMmByte construye un objeto FileMmByte y un objeto IEnumerable<byte[]> que analiza el archivo asignado en búsqueda de líneas individuales.
Observe que las clases FileMmChar y FileMmByte "no son seguras", puesto que crean y usan punteros para tener acceso a los archivos y usan interoperabilidad de código nativo C#. Sin embargo, todo el uso de los punteros se hace aisladamente en un ensamblado separado y el código usa matrices en lugar de eliminar referencias de punteros. La clase MemoryMappedFile de .NET Framework 4 no es de ayuda, puesto que es necesario usar funciones de descriptor de acceso para mover datos a la memoria asignada.
Geonames Native
Geonames Native saca partido de la API de Windows, los subprocesos y la asignación en memoria de archivos. Los patrones de código básico se describen en el capítulo 10 de “Windows System Programming”. El programa debe administrar los subprocesos directamente y deben también asignar el archivo a la memoria. El rendimiento es superior al de todas las implementaciones de PLINQ, a excepción de Geonames Index.
Sin embargo, existe una distinción importante entre el problema de Geonames y una transformación o búsqueda simple, sin estados. El desafío es determinar el método correcto para realizar la partición de los datos ingresados, de manera de asignar distintas particiones a distintas tareas. No hay una manera evidente de determinar los límites de las líneas sin analizar el archivo completo, de manera que no es factible asignar una partición de tamaño fijo a cada tarea. Sin embargo, la solución es sencilla cuando se ilustra mediante DoP 4:
- se divide el archivo de entrada en cuatro particiones iguales, donde la ubicación de partida de la partición se comunica con cada subproceso como parte del argumento de función de subproceso.
- cada subproceso debe ponerse a procesar todas las líneas que comienzan en la partición. Esto significa que un subproceso probablemente analizará la siguiente partición con el fin de completar el procesamiento de la última línea que comienza en la partición.
Geonames Threads
Geonames Threads usa la misma lógica que Geonames Native. De hecho, parte del código es el mismo, o casi el mismo. Sin embargo, las expresiones lambda, los métodos de extensión, los contenedores y otras características de C#/.NET simplifican significativamente el código.
Tal como MMByte y MMChar, la asignación en memoria de archivos exige clases "no seguras" e interoperabilidad de código nativo y C# para poder usar punteros en la memoria asignada. Sin embargo, el esfuerzo vale la pena, puesto que el rendimiento de Geonames Threads es el mismo que el de Geonames Native, pero con código mucho más simple.
Geonames Index
Los resultados de PLINQ (Original, Helper, MMChar y MMByte) son decepcionantes en comparación a los de Native y de .NET Threads. ¿Existe alguna manera de explotar la simpleza y elegancia de PLINQ sin sacrificar el rendimiento?
Aunque es imposible determinar exactamente cómo PLINQ procesa la consulta (las líneas 16 a 27 en la figura 3), es probable que PLINQ no tenga una manera eficaz de realizar una partición de las líneas de entrada para el procesamiento paralelo con tareas separadas. Supongamos, como hipótesis funcional, que la partición puede ser la causa de los problemas de rendimiento de PLINQ.
Según el libro de Magennis (págs. 276-279), la líneas matriz de String brindan compatibilidad con la interfaz IEnumerable<String> (ver también el libro de John Sharp “Microsoft Visual C# 2010 Step by Step” [Microsoft Press, 2010], capítulo 19). Sin embargo, no se han indizado las líneas, de manera que PLINQ probablemente use “partición de fragmentos”. Además, los métodos IEnumerator.MoveNext para las clases FileMmChar y FileMmByte son lentos, puesto que deben analizar cada caracter, hasta que se ubique la línea nueva.
¿Qué pasaría si las líneas de matriz String se indizaran? ¿Podría mejorarse el rendimiento de PLINQ, en particular cuando se suplementa con la asignación en memoria del archivo de entrada? Geonames Index muestra que esta técnica mejora el rendimiento y proporciona resultados comparables al código nativo. Sin embargo, en general o existe un costo inicial para mover las líneas a una lista o matriz en memoria, la cual se indiza (por lo cual el costo se puede amortizar en varias consultas) o el archivo u otra fuente de datos ya está indizado, quizás durante la generación en una etapa anterior del programa, lo que elimina el costo de procesamiento previo.
La operación de indización inicial es simple, sólo haga acceso a cada línea, una a la vez, y después agregue la línea a la lista. Use los objetos de lista de las líneas 16 a 17, como se muestra en la figura 3 y en este fragmento de código, que muestra el procesamiento previo:
// Preprocess the file to create a list of byte[] lines
List<byte[]> lineListByte = new List<byte[]>();
var lines =
FileMmByte.ReadLines(Path.Combine(Environment.CurrentDirectory, inFile));
// ... Multiple queries can use lineListByte
// ....
foreach (byte[] line in lines) { lineListByte.Add(line); }
// ....
var q = from line in lineListByte.AsParallel().
WithDegreeOfParallelism(degreeOfParallelism)
Observe que es un poco más eficiente procesar aún más los datos al convertir la lista a una matriz, aunque esto incrementa el tiempo de procesamiento previo.
Una mejora final al rendimiento
El rendimiento de Geonames Index se puede mejorar aún más al indizar los campos en cada línea de manera que el método ExtractIntegerField no necesite analizar todas las características en una línea que vaya al campo especificado.
La implementación, Geonames IndexFields, modifica el método ReadLines de tal manera que las líneas que se regresan son objetos que contienen a una matriz byte[] y una matriz uint[] que contienen las ubicaciones de cada campo. Esto tiene como resultado una mejora del rendimiento de un 33 por ciento superior a Geonames Index y acerca bastante el rendimiento a las soluciones nativa y C#/.NET (Geonames IndexFields está incluido en la descarga de código). Lo que es más, ahora es mucho más sencillo construir consultas más generales, puesto que los campos individuales están disponibles.
Limitaciones
Todas las soluciones eficientes exigen datos residentes en memoria y las ventajas de rendimiento no se extienden a colecciones de datos muy grandes. En este caso, "muy grande” quiere decir un tamaño de datos que se acercan al tamaño de la memoria física del sistema. En el ejemplo Geonames, el archivo de 3.302 MB (el archivo original copiado cuatro veces) se pudo procesar en el sistema de prueba de 8 GB. Por otra parte, una prueba con ocho copias concatenadas del archivo resultó ser muy lento, con todas las soluciones.
Como se mencionó antes, el rendimiento se vuelve óptimo cuando los archivos de datos están "activados", entendiendo esto como que se tuvo acceso a ellos recientemente y que es probable que estén almacenado en la memoria. La paginación en el archivo de datos durante la ejecución inicial puede tomar 10 o más segundos y es comparable a la operación de indización en el fragmento de código mostrado anteriormente.
En resumen, los resultados en este artículo se aplican a estructuras de datos residentes en memoria y el tamaño y precio de las memorias de hoy permiten que objetos de datos de gran tamaño, como un archivo con 7,25 millones de nombres de lugares, residan en memoria.
Resultados adicionales de prueba de sistema
La figura 5 muestra resultados de prueba en sistemas adicionales (Intel i7 860, 2.80 GHz, cuatro núcleos, ocho subprocesos, Windows 7, 4 GB de RAM). El procesador es compatible con hyper-threading, de manera que los valores de DoP evaluados son 1, 2, ..., 8. La figura 1 se basa en un sistema de prueba con seis núcleos AMD, este sistema no es compatible con hyper-threading.
Figura 5 Intel i7 860; 2.80 GHz, cuatro núcleos, ocho subprocesos, Windows 7, 4 GB de RAM
Dos configuraciones de prueba adicionales arrojaron resultados similares (los datos completos están disponibles en mi página web):
- Intel i7 720, 1.60 GHz, cuatro núcleos, ocho subprocesos, Windows 7, 8 GB de RAM
- Intel i3 530, 2.93 GHz, dos núcleos, cuatro subprocesos, Windows XP64, 4 GB de RAM
Algunas características interesantes del rendimiento son las siguientes:
- Geonames Threads proporcionó de forma constante el mejor rendimiento, junto con Geonames Native.
- Geonames Index es la solución PLINQ más rápida, al acercarse al rendimiento de Geonames Threads. Nota: GeonamesIndexFields es ligeramente más rápido, pero esto no se muestra en lafigura 5*.*
- Fuera de Geonames Index, todas las soluciones PLINQ escalan de forma negativa con un DoP mayor a dos; esto significa que el rendimiento disminuye a medida que aumenta el número de tareas paralelas. Según este ejemplo, PLINQ produce un buen rendimiento sólo cuando se usa con objetos indizados.
- La contribución al rendimiento proporcionado por hyper-threading es marginal. Por lo tanto, el rendimiento de Geonames Threads y Geonames Threads no aumenta significativamente con DoP mayores a cuatro. Esta escalabilidad de HT pobre puede ser consecuencia de la programación de dos subprocesos en procesadores lógicos del mismo núcleo, en vez de asegurarse de que se ejecuten en núcleos diferentes, cuando esto sea posible. Sin embargo, esta explicación no parece ser plausible, dado que Mark E. Russinovich, David A. Solomon y Alex Ionescu afirman que los procesadores físicos están programados posteriormente a los procesadores lógicos en la página 40 de su libro, “Windows Internals, Fifth Edition” (Microsoft Press, 2009). Los sistemas AMD sin HT (figura 1) proporcionaron entre tres a cuatro veces el rendimiento con DoP mayor a cuatro, en comparación a los resultados de un DoP secuencial para Threads, Native e Index. La figura 1 señala que el rendimiento óptimo ocurre cuando el DoP es idéntico al número de núcleos, mientras que el rendimiento de varios subprocesos es 4,2 veces el rendimiento de un DoP.
Resumen de resultados
PLINQ proporciona un modelo excelente para procesamiento de estructuras de datos en memoria y es posible mejorar el rendimiento de código existente con algunos cambios simples (como, por ejemplo, Helper) o con técnicas más avanzadas, como se mostró en MMByte. Sin embargo, ninguna de las mejoras simples proporciona un rendimiento final que se acerque al del código nativo o de varios subprocesos de C#/.NET. Lo que es más, la mejora no se escala con el número de núcleos y DoP.
PLINQ se acerca al rendimiento del código nativo y de C#/.NET, pero exige el uso de objetos de datos indizados.
Uso del código y de los datos
Todo el código está disponible en mi sitio web (jmhartsoftware.com/SequentialFileProcessingSupport.html). Siga estas instrucciones para obtenerlo:
- Vaya a esa página para descargar un archivo ZIP que contiene el código de PLINQ y de Geonames Threads. Todas las variaciones de PLINQ están en el proyecto GeonamesPLINQ (Visual Studio 2010; basta con Visual Studio 2010 Express). GeonamesThreads está en el proyecto Visual Studio 2010 GeonamesThreads. Ambos proyectos están configurados para el lanzamiento de 64 bits. El archivo ZIP también contiene una hoja de cálculo con los datos de rendimiento en bruto usadas en las figuras 1, 2 y 5. Un comentario “Usage" simple en el encabezado del archivo explica la opción de línea de comando para seleccionar el archivo de entrada, el DoP y la implementación.
- Vaya a la página de soporte técnico de Windows System Programming (jmhartsoftware.com/comments_updates.html) para descargar el código y los proyectos de Geonames Native (un archivo ZIP), donde encontrará el proyecto Geonames. El archivo ReadMe.txt explica la estructura.
- Descargue la base de datos de GeoNames desde download.geonames.org/export/dump/allCountries.zip.
Preguntas inexploradas
En este artículo se comparó el rendimiento de varias técnicas distintas para resolver el mismo problema. El enfoque fue usar interfaces estándar tal como se describió y suponer la existencia de un modelo de memoria compartido simple para el procesador y los subprocesos. Sin embargo, no hubo un esfuerzo significativo para explorar en profundidad la implementación subyacente o las características específicas de las máquinas de prueba y quedaron numerosas preguntas por ser investigadas en el futuro. Estos son algunos ejemplos:
- ¿Cuál es el efecto de los errores de caché y hay algún método para reducir el impacto?
- ¿Cuál es el impacto de los discos de estado sólido?
- ¿Existe alguna manera de reducir la brecha de rendimiento entre la solución Index de PLINQ y las soluciones Threads y Native? Los experimentos que reducen la cantidad de datos que se copian en los métodos IEnumerator.MoveNext y Current de FileMmByte no muestran beneficios significativos.
- ¿Se acerca el rendimiento al máximo teórico, como se determinó de acuerdo al ancho de banda de la memoria, la velocidad de la CPU y otras características arquitectónicas?
- ¿Existe alguna manera de obtener un rendimiento escalable en sistemas de HT (ver la figura 5) que sea comparable a sistemas sin HT (figura 1)?
- ¿Pueden usarse herramientas de creación de perfiles y de Visual Studio 2010 para identificar y eliminar cuellos de botella de rendimiento?
Espero que puedan investigar más sobre esto.
Johnson (John) M. Hart* es consultor especializado en la arquitectura de aplicaciones y desarrollo, entrenamiento técnico y escritura en Microsoft Windows y Microsoft .NET Framework. Cuenta con varios años de experiencia como ingeniero informático, administrador de ingeniería y arquitecto en Cilk Arts Inc. (adquirida por Intel Corp.), Sierra Atlantic Inc., Hewlett-Packard Co. y Apollo Computer. Se ha desempeñado por muchos años como profesor de informática y es autor de cuatro ediciones de “Windows System Programming” (Addison-Wesley, 2010).*
Gracias a los siguientes expertos técnicos por su ayuda en la revisión de este artículo: Michael Bruestle,Andrew Greenwald, Troy Magennis y CK Park