Architectural Support for Parallel Computers with Fair Reader/Writer Synchronization
Total Page:16
File Type:pdf, Size:1020Kb
Universidad de Cantabria Departamento de Electrónica y Computadores Tesis Doctoral SOPORTE ARQUITECTÓNICO A LA SINCRONIZACIÓN IMPARCIAL DE LECTORES Y ESCRITORES EN COMPUTADORES PARALELOS Enrique Vallejo Gutiérrez Santander, Marzo de 2010 Universidad de Cantabria Departamento de Electrónica y Computadores Dr. Ramón Beivide Palacio Catedrático de Universidad Y Dr. Fernando Vallejo Alonso Profesor Titular de Universidad Como directores de la Tesis Doctoral: ARCHITECTURAL SUPPORT FOR PARALLEL COMPUTERS WITH FAIR READER/WRITER SYNCHRONIZATION realizada por el doctorando Don Enrique Vallejo Gutiérrez, Ingeniero de Telecomunicación, en el Departamento de Electrónica y Computadores de la Universidad de Cantabria, autorizan la presentación de la citada Tesis Doctoral dado que reúne las condiciones necesarias para su defensa. Santander, Marzo de 2010 Fdo: Ramón Beivide Palacio Fdo. Fernando Vallejo Alonso Agradecimientos Son tantas las personas a las que tengo que agradecer su ayuda y apoyo que esta sección debiera ser la más larga de la tesis. Por ello, no pretendo convertirla en una lista exhaustiva, que además, siempre se olvidaría de alguien importante. Ahí van, por tanto, mis agradecimientos condensados. A Mónica, por su apoyo incondicional, culpable última de que tengas esta tesis en tus manos. A mi familia, especialmente a mis padres, por el soporte y apoyo recibido. A mis directores de tesis, Mon y Fernando. Aunque esto sea una carrera en solitario, es necesaria una buena guía para llegar al final. A los revisores de la tesis, tanto los revisores externos oficiales, Rubén González y Tim Harris, como todos aquellos que se molestaron en leer el trabajo y ayudaron con sugerencias y posibles mejoras. A los compañeros del Grupo de Arquitectura y Tecnología de Computadores de la Universidad de Cantabria, por la ayuda y colaboración en el desarrollo del trabajo, en especial a Carmen Martínez. A la gente del BSC-MSR Center y de la UPC, en especial a Mateo Valero y Adrián Cristal. Sin su trabajo previo en Kilo-Instruction Processors esta tesis hubiera sido imposible; pero también lo hubiera sido sin su guía, ayuda y apoyo y dirección durante su desarrollo. També voldria agrair l’ajuda i col·laboració dels companys del Departament d'Arquitectura de Computadors de la UPC, especialment a Miquel Moretó, i del BSC. You all know who you are, locos. I meet a lot of bright and helpful people during my internship in the Systems and Networking Group in Microsoft Research Cambridge, and they all deserve a sincere acknowledgement. But specially, I had the luck to work under the supervision of Tim Harris. He deserves a double acknowledgement. First, for his help, support and encouraging during my internship and the subsequent work. Second, because most of the work presented here would have been impossible without his prior pioneering work on nonblocking synchronization and Transactional Memory. Thanks, Tim. A todos mis amigos que tuvieron que sufrir de vez en cuando alguna aburrida explicación (culpa suya, por preguntar…) y supieron recordarme que de vez en cuando que existe un mundo real al otro lado de la pantalla. This work has been partially supported by the Spanish Ministry of Education and Science / Ministry of Science and Innovation, under contracts TIN2004-07440-C02-01, TIN2007-68023- C02-01, CONSOLIDER Project CSD2007-00050 and grant AP-2004-6907, by Microsoft Research Cambridge and by the joint BSC-Microsoft Research Centre. The author would also like to thank the support of the European Network of Excellence on High-Performance Embedded Architecture and Compilation (HiPEAC). Para Mónica Resumen Resumen La evolución tecnológica en el campo de los microprocesadores nos ha llevado a sistemas paralelos con múltiples hilos de ejecución simultánea, o threads. Estos sistemas son más difíciles de programar y presentan sobrecargas en la ejecución mayores que los sistemas uniprocesadores tradicionales, lo que puede limitar su rendimiento y escalabilidad. Estas sobrecargas se deben principalmente a los mecanismos de sincronización, el protocolo de coherencia y el modelo de consistencia y otros detalles requeridos para garantizar una ejecución correcta. El Capítulo 1 de esta tesis hace una introducción a los mecanismos necesarios para diseñar y hacer funcionar tal sistema paralelo, detallando en gran medida el trabajo reciente del área. En primer lugar, el apartado 1.1 estudia los mecanismos que hacen posible la construcción de sistemas de memoria común: El protocolo de coherencia y el modelo de consistencia del sistema. Estos dos mecanismos garantizan que los diferentes procesadores tienen una visión uniforme de la memoria del sistema, y especifican cómo puede comportarse el sistema cuando hay carreras en el acceso a datos compartidos. A continuación, en el apartado 1.2 se exponen los fundamentos de la programación paralela en tal modelo de memoria común, con énfasis en los mecanismos de sincronización: barreras y secciones críticas protegidas por locks. Se discuten las principales dificultades que presenta esta programación paralela con respecto a la programación single-threaded: la ley de Ahmdal, que limita la escalabilidad del sistema por la componente secuencial de los programas; el compromiso en cuanto al nivel de granularidad del paralelismo, en que una granularidad más fina permite más paralelismo pero presenta más overhead en la ejecución; los problemas de deadlock (interbloqueo), inversión de prioridad y starvation (inanición), inherentes a sistemas basados en locks; y finalmente, se analiza la complejidad del diseño de estructuras de datos concurrentes, especialmente por dos problemas: la dificultad de ampliar una estructura de datos con nuevos métodos sin conocer en detalle la implementación del resto de métodos, y la falta de composibilidad, es decir, que la construcción de una estructura de datos de un nivel superior, a partir de estructuras basadas en locks más sencillas, puede dar lugar a deadlock. Existen múltiples implementaciones alternativas de locks, que se revisan en el apartado 1.3. En esencia, un lock es una abstracción que protege una cierta zona de memoria o parte del código, de forma que sólo un thread pueda acceder a la zona protegida. Alternativamente, existen locks de lectura/escritura, que permiten un único escritor que modifique los datos, o múltiples lectores que accedan a la vez a los datos compartidos sin modificarlos. Hay implementaciones centralizadas, en que el lock es una única palabra de memoria a la que se accede mediante operaciones atómicas del procesador. Una implementación así genera contienda: todos los threads que quieren tomar el lock acceden a la misma posición de memoria, lo que implica múltiples invalidaciones a nivel de coherencia, empeorando el rendimiento del sistema. Alternativamente, existen implementaciones distribuidas, como los locks basados en colas: cada thread que quiera tomar el lock reserva una estructura denominada “nodo” en memoria, y los diferentes nodos de cada thread se enlazan en forma de una cola de solicitantes. En estos diseños el lock se pasa en orden de un nodo al siguiente, lo que evita la contienda, y además garantiza el fairness: existe una política que garantiza que todos los threads que pretenden tomar un lock lo consiguen hacer en un tiempo razonable, sin producirse starvation. En este apartado 1.3 se discuten también múltiples alternativas de locks, como aquellos que abortan si el lock no se toma en un cierto periodo de tiempo (trylocks), las implementaciones jerárquicas, o locks diseñados para comportarse especialmente bien en caso de existir un comportamiento patológico, como ser adquirido siempre por el mismo thread (biased locks). Una propuesta relativamente reciente que aborda los problemas de programación paralela es la Memoria Transaccional (Transactional Memory, TM), que se introduce en el apartado 1.4. Un sistema de Memoria Transaccional proporciona al programador la abstracción de una transacción con la que puede realizar modificaciones en memoria compartida. Las transacciones en memoria son análogas a las transacciones de las bases de datos, garantizando las propiedades de atomicidad (se ejecuta toda la transacción, o se aborta toda la transacción, pero no parte de ella) y aislamiento (ningún thread es capaz de ver el estado intermedio de otra transacción) al código que corre dentro de la transacción. Esto permite eliminar la gestión de locks de la tarea de programación al sustituir secciones críticas por transacciones, evitar el problema de deadlock, simplificar la modificación de código existente y proporcionar composibilidad. Para garantizar una ejecución correcta, el sistema de Memoria Transaccional debe ejecutar el código transaccional de manera especulativa, almacenando los cambios que realiza la transacción (el write-set) en algún tipo de buffer, y utilizar un mecanismo de validación (commit) que garantice que los cambios se hacen visibles a nivel global (al resto de threads) de forma atómica, sin que hayan ocurrido cambios en las posiciones de memoria leídas (el read- set). Además, el sistema debe detectar las violaciones: si dos transacciones acceden de manera concurrente a los mismos datos en memoria (solapándose parcialmente el write-set de una de ellas con el read-set o el write-set de la otra) una de las transacciones debe pararse y esperar al commit de la otra, o abortar y reiniciarse. El mecanismo que decide la acción cuando se detecta un conflicto se denomina contention manager. Existen múltiples implementaciones de Memoria Transaccional,