domingo, julio 19, 2009

Casi mejor no tocar la afinidad

Hola a tod@s,
Por fin de vacaciones, pero antes de marchar de viaje me gustaría saber si lo que ocurrió con el programa C le ha pasado a más gente, y por qué.

Recordar que teníamos un programita que ejecutamos marcando la afinidad a un procesador concreto y tardó 11 segundos más que sin toquetear la afinidad.

Primero una pequeña aclaración que analizaremos otro día:
El ordenador que utilicé en esas pruebas es un ADM Phenon x3, el cual tiene un único procesador con 3 cores (o nucleos en castellano). Windows nos dirá que hay tres procesadores y que podemos determinar la afinidad para forzar la ejecución en los procesadores que queramos, pero sólo hay un procesador y los núcleos comparten ancho de banda en el acceso a la caché L3 (memoria compartida por todos los cores). La caché L2 es la memoria que tiene cada core.

Bueno, ahora vamos a leer lo que otro (Iñaki Ayucar) ha analizado:

Artículo original en inglés: http://www.codeproject.com/Articles/35679/Parallel-computing-and-processor-affinity-Never-underestimate-the-Windows-Vista-Scheduler.aspx?display=PrintAll

Y en castellano: http://graphicdna.blogspot.com/2009/01/programacion-paralela-y-processor.html

¿Cual es el resumen?

1.-Al menos habría que abril un hilo por core físico en la máquina. Ya, pero el programa no es mio y el autor metió programación paralelizada en el programa original Fortran, pero no en el C. Esto será una mejora para el después de vacaciones.

2.-Este punto lo voy a poner tal cual está, porque lo explica de maravilla. El autor nos dice: "En un mundo ideal, una tarea simple, que no va a ser paralelizada más y que vive solita (no con las docenas de vecinos que un proceso tiene en un SO moderno), se gestiona mejor con una sola CPU, porque esto incrementa los aciertos de cache y elimina el consumo de la infraestructura de cambio entre hilos. Pero en la vida real, los procesos son interrumpidos por las operaciones del sistema, IO, otros procesos y muchas otras cosas. Una maquina multi-núcleo es perfecta para manejar todas esas interrupciones, porque puede repartirlas por los núcleos existentes, pero si comenzamos a fijar nuestras aplicaciones a CPUs específicas, la capacidad del sistema para evitar bloqueos y esperas se reduce considerablemente".

Uff, este punto número 2 lo podemos separar en otros cuantos:

2.1.-Cuidado, porque puede ocurrir que la ejecución en tu viejo portátil con una CPU pueda ir mejor, ya que hay más aciertos de cache y hay menor consumo por cambio de hilos.

2.2.-El hecho de que hay un montonazo de procesos extra en ejecución con sus respectivas interrupciones, etc. etc. da pie a que en tu nuevo pc multinucleo la cosa funcione mejor.

2.3.-En el caso de que estemos en nuestro pc nuevo multinucleo, fijar las CPU-s puede perjudicar ya que el sistema tiene menos opciones para evitar los bloqueos, esperas, etc.

En el artículo pasa a describir el tema de cómo fijar la afinidad y luego viene lo interesante:

Las pruebas:

Las pruebas se componen por 5 partes:

Parte I: Programa NO multihilo (como el nuestro).
Si se establece la afinidad el programa tarda 28 segundos más que si no se hace nada.
Mejor dejar hacer a Windows.

Parte II: Dos hilos de ejecución (principal + secundario con algún cálculo).
El programa tarda 3 segundos menos si no se establece la afinidad.
Mejor dejar hacer a Windows.

Parte III: Tres hilos de ejecución (principal + dos hilos de cálculo).
El programa tarda 2 segundos menos si no se establece la afinidad.
Mejor dejar hacer a Windows.

Parte IV: Cinco hilos de ejecución (principal + cuatro hilos de cálculo).
Empate técnico (bueno, por décimas gana la afinidad).
Windows ha perdido por la mínima.

Parte V: Nueve hilos de ejecución (principal + ocho hilos de cálculo).
El programa tarda 3 segundos menos si no se establece la afinidad.
Mejor dejar hacer a Windows.

Los resultados cuadran totalmente con lo que nos ocurría en el programita.

Parece que cuando la programación es multihilo es mejor no tocar la afinidad, aunque la diferencia es pequeña. Lo que está claro es que si el programa no es multihilo hay grandes diferencias en tiempo de ejecución siendo más rápido sin tocar la afinidad.

Saludos.

lunes, julio 13, 2009

Siguiendo los consejos recibidos el otro día la semana pasada hice la prueba de ejecución "con afinidad".

No fijé la afinidad por programación. Simplemente lancé la ejecución, y a toda leche modifiqué la afinidad con el Administrador de tareas de Windows.

Estoy con mi viejo portátil con un solo procesador por lo que no os puedo enseñar la opción, pero la explico...



Ahí arriba está el Administrador de tareas. He pinchado en una tarea y tengo opción de subir o bajar la prioridad de un proceso.

Si tuviera más de un procesador, debajo de la opción de prioridad tendríamos algo así como "afinidad". Al pinchar aparecen un check box donde podemos elegir uno o varios procesadores. Yo elegí el procesador 0.

Así fue la ejecución:

El PC estaba ligerito de trabajo:


Lanzo el proceso, pongo en marcha el cronometro y establezco la afinidad:

En la imagen de arriba ya se ve que un procesador se pone al 100% mientras que los otros dos prácticamente vuelven a la situación inicial.

Al final termina el proceso y todo vuelve a su ser:


Ahora lo importante, ¿qué ha pasado con el tiempo de ejecución?

Pues ha tardado 18 minutos 42 segundos. Once segundos más que sin tocar la afinidad.

Creo que vamos a tener que enterarnos de la forma de funcionamiento de estos procesadores AMD64 para entender algo.

Saludos.

jueves, julio 09, 2009

Un poco de rigor por favor...

Lo siento por los amantes de la informática y las cosas raras, pero hoy no he podido dedicar un minuto al tema.

Llevo toda la semana escuchando un error repetido por todos los medios de comunicación y no podía dejarlo así.

Ya se sabe que una mentira repetida cientos de veces se convierte en verdad y por lo menos quiero poner este granito de arena para aclarar el tema.

Los titulares que he leído en prensa (principalmente en la red), oído por la radio (varias emisoras) y visto en televisión (unos cuantos telediarios de diferentes cadenas) dicen algo así como...







Más abajo el texto firmado por la Agencia EFE dice así:

Luego viene lo curioso, y es que enumeran las etapas y precisamente aparece la etapa fantasma que es la que os voy a relatar a continuación.



¿Qué es lo que está mal?

Bueno, estos medios han podido considerar que Euskadi no es España y entonces no hay nada que objetar, pero por si no es así, les quiero recordar lo siguiente:

Como se puede ver en la hemeroteca la primera etapa fue una prólogo en San Sebastián que ganó Indurain. Era el 4 de julio de 1992:

http://www.elpais.com/articulo/deportes/Indurain/confirma/gran/momento/gana/prologo/contrarreloj/San/Sebastian/elpepidep/19920705elpepidep_11/Tes

http://hemeroteca.lavanguardia.es/preview/1992/07/03/pagina-41/33524616/pdf.html?search=tour

El 5 de julio hubo otra etapa que salía de San Sebastian y llegaba a San Sebastian. Esta etapa era en línea (ni prólogo ni contrareloj). En el recuadro de abajo se puede ver cómo no se pasaba la frontera.



http://hemeroteca.lavanguardia.es/preview/1992/07/03/pagina-42/33524617/pdf.html?search=tour

Por último el día 6 fue la etapa que ganó el corredor de Agurain Javier Murguialday. La meta estaba en Pau.
http://www.elpais.com/articulo/deportes/Murguialday/logra/Victoria/importante/vi/mantiene/protagonismo/espanol/elpepidep/19920707elpepidep_17/Tes

¿Qué ha podido ocurrir?

Bueno, si nos fijamos en la Wikipedia vemos que pone que la 2ª etapa la ganó Murguialday. Sabemos que era el tercer día de tour. Esto ocurre porque las etapas prólogo (suelen ser de menos de 8 km) no las consideran etapas.


De igual forma en esta web vemos que ponen al ciclista de Castorama Dominique Arnould como ganador de la primera etapa (el segundo día de tour).

Y aquí ponen que la segunda etapa de aquel año era la de San Sebastian-Pau.

Creo que de esta forma desapareció de la historia del tour aquella etapa San Sebastián-San Sebastián. Aquí se ve bastante claro como el propio tour ha olvidado esa etapa de 1992. Ponen a dos ganadores de un prólogo, pero se han dejado la que fue la primera etapa en linea.

Saludos.

Moraleja: No te creas todo lo que leas.

Moraleja2: No, porque lo ponga en el periódico no tiene por qué ser verdad.

lunes, julio 06, 2009

Could faster chips translate into slower computers?

Ya que el otro día mejoramos la ejecución en el super PC-1 (18 minutos 31 segundos), se me ha ocurrido compilar y ejecutar el programa Visual C++ en mi viejo portátil.

Con el Dev C++ sin optimizar tardaba 21 minutos 9 segundos y tenía curiosidad por saber lo que iba a mejorar.

Han sido ni más ni menos 14 minutos 20 segundos, lo que significa que ¿es la máquina más rápida? ¡Imposible!

Algo raro está ocurriendo... el resultado no es lógico, o tal vez sí...

Así es la utilización de la CPU al comenzar la ejecución. 100% de uso del único procesador hasta el final.



Y así la relajación final:


El AMD64 es multinucleo y se comporta diferente. El XP lo ve como si fueran 3 procesadores. El caso es que el uso de la CPU se mantiene bastante constante en un 34% de uso.


Cuando termina:


Esto hay que estudiarlo un poco pero el punto de partida puede ser este:

Could faster chips translate into slower computers?

Esta es la pregunta que se hicieron en este artículo de la revista Fortune e indica una posibilidad que puede estar ocurriendo, o tal vez no.

Saludos.

jueves, julio 02, 2009

Prueba con Visual C++

Seguimos con el programa de generación del primo más grande (del año 1994).

Hicimos una prueba de compilación con Dev C++ en un PC decente. El resultado fue un poco decepcionante porque los 27 minutos que tardaba eran 6 minutos más que en un viejo portátil XP.

Es verdad que no me preocupé demasiado por ajustar los parámetros del compilador. Pero he preferido pasar directamente al compilador comercial por excelencia de Windows XP: Visual C++.

1.-Instalo Visual Studio 2005 (en los próximos días probaremos el Visual Studio 2008).

2.-Iniciamos un nuevo proyecto Visual Studio. Seleccionamos el lenguaje Visual C++ y "Aplicación de consola Win32"



3.-Así tendremos el proyecto vacío:


4.-Añado el programa C original de Slowinski y lo adapto ligeramente para el Visual C++. Concretamente hay que:
  • Modificar la sintaxis de main().
  • Comentar las líneas para el control de tiempos.
  • Comentar la cabecera que Visual C++ ha añadido por defecto: stdafx.h
  • Añadir la cabecera stdlib.h para poder usar la función atoi.

Al final de este post añado el código fuente entero para el que lo quiera probar.

5.-Lanzamos la compilación-ejecución y a esperar. En total ha tardado 23 minutos 45 segundos. Mejor que el Dev C++ pero un resultado bastante pobre. Ahora toca hacer unos pequeños ajustes.

6.-Priorizamos el rendimiento frente al tamaño del ejecutable:

Para ello vamos al Menú Proyecto, propiedades del proyecto, propiedades de configuración, C/C++, Entramos en la sección "Optimización" y seleccionamos "Maximizar velocidad" que se corresponde con la opción /O2.



Ahora intentamos la compilación y...SORPRESA. Se produce un error que dice lo siguiente: "las opciones de líneas de comandos '/O2' y '/RTC1' son incompatibles".


Bueno, pues hay que volver a Proyecto, propiedades del proyecto, propiedades de configuración, C/C++, General. Vamos a "formato de la información de depuración" y seleccionamos "Deshabilitado".

Además de esto, también hay que ir a C/C++, Generación de Código, "Comprobaciones básicas en tiempo de ejecución" y marcarlo como PREDETERMINADO.



Pues aunque volvamos a intentar compilar nos sale un nuevo error: '/Gm' requiere '/Zi' o '/ZI'.


Jeje, ya estaremos un poco aburridos tanto toquetear, pero esta es la última vez (de momento). Volvemos a Proyecto, propiedades del proyecto, propiedades de configuración, C/C++, Generación de código. Vamos a "habilitar regeneración mínima" y ponemos que NO.


7.-Ahora sí, ya podemos compilar y ejecutar tranquilamente. Y el rendimiento mejora bastante. El programa tarda ahora 18 minutos 31 segundos. Nuevo record, aunque todavía insuficiente.

Saludos.

Ahora el código fuente... (cuidado, algunos símbolos se han podido modificar. Cosas de blogger):


// Programa visualizador. 2009-07-02.

//#include "stdafx.h"

#include
#include
#include
#include

//Para que funcione la función atoi es necesario incluir stdlib.h.
#include

// Pequeña modificación en main:
//main(argc, argv)
//int argc;
//char *argv[];

int main(int argc, char* argv[])
{
#define EXP 859433 /* will calculate 2^859433 - 1, the 33rd Mersenne prime */
//Quitamos el cálculo de tiempos:
//double t, ftp0, ftp1;
double c0 = 0, c1 = 0, c2 = 0;
int n;
int *a, *b, *c;
int ddigit = 4; /* number decimal digits per part */
int vdig = (int)pow((double)10, (double)ddigit); /* decimal value of part */
int i, j, k, maxpart, mx;
unsigned int size;
char format[64];

n = EXP;
if (argc == 2) /* command-line param for new n? */
n = (atoi(argv[1]) > 0) ? atoi(argv[1]) : EXP;
printf("calculating 2^%d - 1\n", n);

size = (n+1)*sizeof(int);
printf ("allocating %d bytes for working arrays\n", 3*size);

if ((a = (int *)malloc(size)) == NULL) {
fprintf(stderr, "cannot allocate %d bytes for a\n", size);
exit(1);
}
if ((b = (int *)malloc(size)) == NULL) {
fprintf(stderr, "cannot allocate %d bytes for b\n", size);
exit(1);
}
if ((c = (int *)malloc(size)) == NULL) {
fprintf(stderr, "cannot allocate %d bytes for c\n", size);
exit(1);
}

for (i = 1; i <= n; i++) { a[i] = 0; /* set results array to null */ b[i] = 2; /* load up multiplicand array with n 2s */ c[i] = 0; /* set carry array to null */ } a[1] = 1; /* set initial intermediate result to 1 */ mx = 1; //#ifdef FTIME // ftime(&tp0); //#else // gettimeofday (&tp0, (struct timezone *)NULL); //#endif //#pragma _CRI parallel shared(n, a, b, c, mx, vdig) private(i, j) for (j = 1; j <= n; j++) { //#pragma _CRI taskloop vector for (i = 1; i <= mx; i++) { a[i] *= b[j]; } //#pragma _CRI taskloop vector for (i = 2; i <= mx+1; i++) c[i] = a[i-1]/vdig; //#pragma _CRI guard if (c[mx+1] > 0)
mx++;
//#pragma _CRI endguard

//#pragma _CRI taskloop vector
for (i = 1; i <= mx; i++) { a[i] = (a[i] % vdig) + c[i]; } } //#pragma _CRI endparallel /* propogate carry through one last time */ j = 0; for (i = 1; i <= n; i++) { k = a[i] + j; a[i] = k % vdig; j = k/vdig; } //#ifdef FTIME //ftime(&tp1); //ftp0 = (double)tp0.time+(double)tp0.millitm/(double)1000000; //ftp1 = (double)tp1.time+(double)tp1.millitm/(double)1000000; //#else //gettimeofday (&tp1, (struct timezone *)NULL); //ftp0 = (double)tp0.tv_sec+(double)tp0.tv_usec/(double)1000000; //ftp1 = (double)tp1.tv_sec+(double)tp1.tv_usec/(double)1000000; //#endif //t = (ftp1-ftp0 == 0) ? 0.000001 : ftp1-ftp0; //printf("wall time for main loop: %.5lf seconds\n", t); maxpart = 0; for (i = 0; i <= n; i++) if (a[i] != 0) maxpart = i; printf ("max parts used: %d (each part is %d decimal digits)\n", maxpart, ddigit); /* subtract one from answer and see if we match Slow's number */ a[1]--; if (a[1] < i =" 2;" j =" 0;" i =" maxpart;">= 1; i--) {
printf (format, a[i]);
j++;
if (j % 15 == 0)
printf ("\n");
}
printf ("\n");

return 0;
}