Archivo

Archivo para Abril, 2011

Linux en el cine: Deuda de sangre

Domingo, 24 de Abril de 2011 Gaspar Fernández Sin comentarios

Esta noche estuve viendo la película Deuda de Sangre (Blood Work) de Clint Eastwood (2002). En ella, en el minuto 28 podemos ver cómo el protagonista se acerca a charlar con un implicado en un asesinato (Rick Hoffman), éste es informático.
linux_deuda_de_sangre_ En el fotograma podemos ver cómo detrás de él hay varios libros de Linux, y otro en el que se puede leer GNU. A la derecha del personaje hay uno de Red Hat Linux 6.1 (versión de Octubre del 99).

Seguiré publicando más cameos linuxeros :) Y agradecería que, si conocéis alguno lo dejéis en los comentarios.

Limitando recursos en ImageMagick

Jueves, 21 de Abril de 2011 Gaspar Fernández 2 comentarios

A veces, utilizar ImageMagick puede ser horrible, sobre todo porque en ocasiones, se come todos los recursos de nuestro sistema, al procesar imágenes grandes, múltiples imágenes, vídeo, etc… en ocasiones puede que el OOM Killer de Linux detenga la tarea, y puede que mientras procesamos las imágenes, necesitemos el ordenador para algo.

NOTA: Utilizaré convert para poner ejemplos de cómo utilizar esta herramienta, pero estos consejos valen para todos los programas de tratamiento de imagen que componen imagemagick: convert, mogrify, montage…

Bien, ImageMagick tiene opciones para modificar cuánta memoria va a acaparar. Si sabemos que es una tarea muy pesada para la memoria (una imagen de 5000×5000 ocupará cerca de 70Mb, una imagen de 10000×10000 cerca de 300Mb, sin contar la memoria utilizada para el tratamiento de la imagen) podemos decir a ImageMagick que utilice como mucho 20Mb de RAM (el resto de memoria que necesite la cogerá del disco duro, de nuestro directorio temporal).
Para esto, podemos utilizar lo siguiente:

$ convert imagenA.jpg -limit memory 20MiB -resize 50% imagenB.png

Si durante el proceso, ejecutamos top podremos ver la diferencia de memoria utilizada.
Podemos ver los recursos que podemos limitar con:

$ identify -list resource
File Area Memory Map Disk Thread Time
——————————————————————————-
768 4.2324GB 2.9563GiB 7.8835GiB 18.446744EB 1 unlimited

Las peticiones de memoria que hace ImageMagick como vemos aquí son las siguientes:
Se intenta reservar memoria para los pixels, pero si la memoria excede el límite de memory, o falla (puede que el sistema no disponga de memoria suficiente), lo escribirá en disco y lo mapeará en memoria, pero si se excede la memoria del mapa de memoria, todo va a disco, si ya no tenemos disco duro suficiente, o hemos limitado el disco disponible fallará.
Tenemos que tener en cuenta que hacemos distinción entre MiB (MebiBytes) (1MiB=1.048.576 bytes) y MB (MegaByte) (1MB = 1.000.000 bytes)

Así que también podemos limitar la memoria de los mapas de memoria, de disco, los threads a utilizar (o podemos darle más, si queremos), por ejemplo, si queremos insertar imágenes en un pdf:

$ convert *.jpg -limit memory 200MiB -limit map 200MiB resultado.pdf

También es muy útil el parámetro de area, este vale para limitar la memoria de forma absoluta, es decir, para que ImageMagick no intente hacer nada en memoria si excedemos esta cantidad de memoria (a veces es mejor limitar la memoria, antes de que intente encajar las reservas de memoria en los otros dos parámetros, y tenemos más control sobre la memoria utilizada):

$ convert *.jpg -limit area 300M resultado.pdf

Todas estas opciones de ImageMagick pueden ser variables de entorno: MAGICK_AREA_LIMIT, MAGICK_DISK_LIMIT, MAGICK_FILE_LIMIT, MAGICK_MEMORY_LIMIT, MAGICK_MAP_LIMIT, MAGICK_THREAD_LIMIT, MAGICK_TIME_LIMIT. Podemos hacer lo siguiente para ver que los cambios se aplican correctamente:

$ export MAGICK_AREA_LIMIT=50M
$ export MAGICK_MEMORY_LIMIT=10MiB
$ export MAGICK_MAP_LIMIT=30MiB
$ convert -list resource
File Area Memory Map Disk Thread Time
——————————————————————————-
768 50MB 10MiB 30MiB 18.446744EB 1 unlimited

Como habréis podido comprobar, también se puede limitar por tiempo, incluyendo -limit time [segundos] o con la variable de entorno MAGICK_TIME_LIMIT.

Pero no acaba aquí, podemos hacer que el procesado de imagen sea un poco más lento, pero nos deje el ordenador libre para otros usos, esto es con la variable de entorno MAGICK_THROTTLE, ImageMagick periodicamente dejará respirar el procesador tantos milisegundos como le hayamos asignado a la variable:

export MAGICK_THROTTLE=100

Dejará de procesar 100ms en varias ocasiones durante el proceso, lo podremos usar si vemos que el proceso será largo, no nos importa la velocidada y va a utilizar toda nuestra CPU.

También podemos acompañar todas estas opciones de ImageMagick con nice e ionice. Por ejemplo, con ionice podremos retrasar o priorizar los accesos a disco y con nice la prioridad del proceso. Si de verdad no nos importa cuánto tarde la conversión podemos hacer lo siguiente:

nice -n 20 ionice -c3 convert *.jpg -limit area 200M resultado.pdf

Con lo que asignaremos la menor prioridad al proceso, y además sólo accederá a disco cuando nadie más lo haga (si otro proceso quiere escribir en disco, convert tendrá que esperar)

Distribuyendo la compilación en varios equipos

Jueves, 14 de Abril de 2011 Gaspar Fernández 2 comentarios

linux_cluster Cuando se instala Gentoo o Linux From Scratch, una cosa que llega a ser desesperante son los tiempos de compilación, y es que estos tipos de sistemas son un poco “yo me lo guiso, yo me lo como“, es decir, a partir del código fuente, tú te compilas el sistema operativo completo.

Si a veces, para compilar el kernel, nos podemos tirar varias horas (siempre que lo tengo que hacer, le doy la mínima prioridad al proceso, para poder utilizar mientras el ordenador); cuando toca compilar un entorno de escritorio tipo GNOME, o KDE, puede llegar a ser eterno, sobre todo en máquinas más o menos antiguas.

Una de las grandes ayudas que descubrí en mis años de Gentoo es distcc, un pequeño programa que nos ayuda a distribuir la compilación en varios ordenadores. Gentoo viene preparado para poder utilizar distcc de serie en todas las compilaciones, y se nota bastante cuando por ejemplo se colocan tres ordenadores en red para compilar un paquete tipo OpenOffice (aunque en Gentoo hay versión precompilada, hace años no funcionaba muy bien y era mejor compilarla tú mismo).

Actualmente, me encuentro desde Linux Mint haciendo algunos programas bastante pesados, todos llevan su entorno gráfico hecho en gtkmm y algunas modificaciones conllevan una compilación que suele tardar un minuto, lo malo es que a veces falla algún pequeño detalle y tengo que compilar varias veces, por ello, he decidido aprovechar la potencia de todos los ordenadores de la casa y para ello he instalado distcc en todos:

Lo podemos encontrar en los repositorios de nuestras distribuciones, y si podemos instalar el último paquete “distcc-pump” mejor, por ejemplo, para instalar en Linux Mint/Ubuntu:

$ sudo apt-get install distcc-pump distccmon-gnome

En Gentoo podremos hacerlo así

root$ USE=”gtk” emerge distcc

En Arch:

root$ pacman -S distcc

luego, antes de hacer nada, tenemos que cargar el demonio en todos los ordenadores donde queramos compilar, imaginemos que nuestro equipo (el que dará la orden de compilar) es 192.168.0.6:

$ distccd –allow 192.168.0.6 # Como usuario vale

Tras esto, una vez cargado el demonio en todos los ordenadores, podemos ejecutar en el ordenador principal (el nuestro) la orden de compilar un paquete o un programa hecho por nosotros, para eso, primero debemos establecer una serie de ordenadores donde querremos compilar (todos aquellos donde hemos cargado el demonio):

$ export DISTCC_HOSTS=’192.168.0.6 192.168.0.4 192.168.0.2 192.168.0.20′

He incluido el localhost (192.168.0.6) con la ip de red, porque en algunos equipos, no funciona bien al querer utilizar localhost con IPv6, depende de la configuración del SO.

Ya estamos listos para compilar

$ make -j8 CC=distcc

Esto nos bastaría para enviar a los demás equipos compilar nuestro programa. -j8 indica que podremos compilar en paralelo 8 archivos (debemos jugar con esto en función de los equipos que tengamos).

Ahora bien, este es el método de toda la vida, ahora disponemos de distcc-pump, lo que hace este sistema es que también es capaz de delegar el preprocesamiento a los demás ordenadores (antes estaba todo limitado al host principal), por otra parte, realiza una transmisión más rápida de todos los archivos necesarios para compilar desde tu ordenador hacia los demás ordenadores de la red.

Para utilizar distcc-pump la tarea es algo diferente (nota: dependiendo de la distribución el ejecutable es diferente, distcc-pump, pump, pupmpe… así que tendremos que buscarlo).

$ export DISTCC_POTENTIAL_HOSTS=’192.168.0.6 192.168.0.4 192.168.0.2 192.168.0.20′
$ distcc-pump make -j8 CC=distcc

Ahora es cuestión de montarnos un cluster para hacer nuestras compilaciones…

También podemos compilar a través de Internet, aunque tenemos que asegurarnos de que el tiempo de transmisión compensará con el tiempo de compilación.

Por último, podemos ver el estado de nuestra compilación ejecutando al mismo tiempo que se compila el programa distccmon-text o distccmon-gnome (este segundo tiene entorno gráfico); nos permiten ver el trabajo que está haciendo cada uno.

Linux en TV: The Closer 2×05

Miércoles, 13 de Abril de 2011 Gaspar Fernández Sin comentarios

the-closer_linux_recortadoQuería compartir con todos vosotros una imagen. Últimamente estoy viendo la serie “The Closer” y en la segunda temporada, en el capítulo 5; llaman por teléfono al inspector Tao que se encontraba leyendo un libro “Understanding Linux 9″ (Entendiendo Linux 9) de Chris Pettit, Laura Iler y Sal Amuso.

Aunque tanto el libro como los autores son ficticios.

Señoras y señores, Linux existe también en las series de televisión (en Big Bang Theory encontramos esto, por ejemplo… añadimos otra más a nuestras series).

Skype + Webcam en Linux Mint / Ubuntu 64bit

Lunes, 11 de Abril de 2011 Gaspar Fernández 2 comentarios

skype-webcam_recorteLa semana pasada, el 6 de abril salió la nueva versión Beta de Skype 2.2 para Linux. Aunque esta versión esté a años luz de la versión para Windows (aunque por otra parte lo prefiero, no quiero que recarguen tanto el programa), había algunas cosas básicas que deberían haber hecho hace tiempo, sobre todo en el campo de la compatibilidad con la cámara web, por ejemplo.

El programa no es libre, ni piensa serlo, aunque el protocolo de voz de skype es de lo mejor que he visto, ya que permite la comunicación con mucha compresión, más nítida que con otros medios y un ancho de banda similar. Y aunque no se les vea muy comprometidos, hay que darles la enhorabuena por hacer su software para esta plataforma, y  al menos permitir comunicarnos con otros usuarios de skype.

Hasta el momento, para mí había varios problemas:

  • Con ALSA, a veces quería enviar más información de la cuenta o menos de la que se espera, y ALSA se quejaba, dejaba de escucharse un tiempo. Skype decía que era culpa de ALSA, ALSA, que era culpa de skype…
  • Con Pulseaudio a veces se perdía el sonido, era fallo de skype, pero bueno, se reinicia y no pasa nada.
  • Webcams:
    • Con una gráfica Intel, olvídate de que se vea tu webcam (es raro, pero ni ves ni envías)
    • Con otra gráfica, pero un SO de 64bit, olvídate también, no se podrá acceder a la webcam.

En esta nueva versión han hecho cambios en esos aspectos, dicen que han mejorado el audio con Pulseaudio, y al no poder provocar el error no lo he podido probar todavía, y han mejorado el soporte de webcams. Con un equipo con gráfica Intel no he probado, pero sí en 64bit.

Para funcionar con la webcam en 64bit (con ciertas cámaras, por ejemplo la OV511/519, yo tengo una Creative muy vieja, también se repite el problema con algunas Genius y algunas Logitech). Primero tendremos que instalar los módulos de compatibilidad de Video4Linux de 32 bit.

$ sudo apt-get install lib32v4l-0

Y a la hora de ejecutar skype:

$ LD_PRELOAD=/usr/lib32/libv4l/v4l1compat.so skype

Esto ya funcionaba en la Beta 2.1 con algunas cámaras, pero no con todas. Ahora funciona con algunas cámaras más.

Podremos configurar el acceso directo a skype para que ejecute también el LD_PRELOAD.

Ahora mismo, dejo las instrucciones para Linux Mint / Ubuntu, aunque en cualquier distribución podremos instalar las librerías Video4Linux de 32bit y precargarla antes de ejecutar skype.

¡ Siempre olvido la alineación de las variables ! GRR

Jueves, 7 de Abril de 2011 Gaspar Fernández 2 comentarios

1GB DDR3 Memory Module

Algo que pocas veces tenemos en cuenta es la alineación de las variables en la memoria RAM. Muchas veces, ni nos va, ni nos viene, aunque en ciertas ocasiones suele causar calentamientos de cabeza.

Tiene que ver con la forma que tiene la CPU para dialogar con la RAM y la arquitectura de éstas. Partimos del hecho de que pedir un dato a la memoria es algo lento, sí se hace muchos millones de veces por segundo, pero mientras viene o no viene el dato, la CPU simplemente espera.  Ahora, tenemos que tener en cuenta:

  • El diálogo CPU<–>Memoria es a través de palabras y la palabra, en un sistema de 32bit es de 32bit (4bytes), en uno de 64bit serán 8bytes, no en todos los procesadores es así, pero casi.
  • Cada byte en memoria tiene una dirección, para que la CPU y la memoria puedan ubicar el dato.
  • Por otra parte,  las palabras en memoria tienen una posición que coincide con que su dirección sea divisible por la longitud de la palabra.
  • Sólo podemos acceder a una palabra en un mismo instante.

Por tanto, en mi PC de 32bit, cuando quiero un int de 32bit, normalmente me lo traigo de direcciones que sean divisibles por 4, por lo tanto, sólo hago una lectura a la memoria; por ejemplo, mi dato ocupa desde la dirección 0×0030 a la 0×0033 (4bytes), me lo traigo de una sola tirada. De otro modo, si mi dato ocupara desde la direccion 0×0032 a la 0×0035 (4bytes también), tendría que hacer dos peticiones, la palabra 0×0030–0×0033 y la palabra 0×0034-0×0036, y luego coger los dos últimos bytes de una y los dos últimos bytes de la otra; cosa que por experimentar está bien, pero no es óptimo, perdemos mucho tiempo, tardamos el doble. Si nos queremos traer una variable de 8bytes, tendremos que hacerlo de dos tandas, y por diseño, es más fácil traérsela de una dirección de memoria divisible por 8

También tenemos que tener en cuenta que perdemos memoria, es decir, tendremos bytes en memoria que no usaremos para nada (y a veces no nos sobra precisamente), pero sólo podemos aguantarnos… u optimizar más nuestros programas (siempre que compense). Por ejemplo cuando declaramos dos variables, un short (2bytes) y un int(4 bytes), la primera de ellas (el short) se situará en memoria en la dirección 0×0030 y la segunda (el int) en la 0×0034, dejando 2 bytes dentro de la memoria inutilizados. Aunque los compiladores modernos, a la hora de declarar variables intentan hacerlo de la forma más inteligente posible, para aprovechar los huecos al máximo, aunque no declaremos las variables por orden, a la hora de situarlas en memoria, se suelen colocar bien:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>

int main()
{
  char c,c2,c3,c4,c5,c6;
  int i;
  short k;
  long long i2;
  long long ai2;
  short k2;


  printf("%lX [c]\t\t(%lu bytes)\n",    (long unsigned)&c,   sizeof(c));
  printf("%lX [c2]\t\t(%lu bytes)\n",   (long unsigned)&c2,  sizeof(c2));
  printf("%lX [c3]\t\t(%lu bytes)\n",   (long unsigned)&c3,  sizeof(c3));
  printf("%lX [c4]\t\t(%lu bytes)\n",   (long unsigned)&c4,  sizeof(c4));
  printf("%lX [c5]\t\t(%lu bytes)\n",   (long unsigned)&c5,  sizeof(c5));
  printf("%lX [c6]\t\t(%lu bytes)\n",   (long unsigned)&c6,  sizeof(c6));
  printf("%lX [i]\t\t(%lu bytes)\n",    (long unsigned)&i,   sizeof(i));
  printf("%lX [i2]\t\t(%lu bytes)\n",   (long unsigned)&i2,  sizeof(i2));
  printf("%lX [k]\t\t(%lu bytes)\n",    (long unsigned)&k,   sizeof(k));
  printf("%lX [ai2]\t\t(%lu bytes)\n",  (long unsigned)&ai2, sizeof(ai2));
  printf("%lX [k2]\t\t(%lu bytes)\n",   (long unsigned)&k2,  sizeof(k2));

  return 0;
}

Nota: represento variables de tipo long unsigned para representar bien los valores en sistemas de 64bit.
El resultado es algo así:

BFAF2BCF [c]            (1 bytes)
BFAF2BCE [c2]           (1 bytes)
BFAF2BCD [c3]           (1 bytes)
BFAF2BCC [c4]           (1 bytes)
BFAF2BCB [c5]           (1 bytes)
BFAF2BCA [c6]           (1 bytes)
BFAF2BC4 [i]            (4 bytes)
BFAF2BB8 [i2]           (8 bytes)
BFAF2BC2 [k]            (2 bytes)
BFAF2BB0 [ai2]          (8 bytes)
BFAF2BAE [k2]           (2 bytes)

Esto resultará lo siguiente:

BFAF2BA8 BFAF2BAB BFAF2BAC k2 k2 BFAF2BAF
BFAF2BB0 ai2 ai2 ai2 ai2 BFAF2BB3 BFAF2BB4 ai2 ai2 ai2 ai2 BFAF2BB7
BFAF2BB8 i2 i2 i2 i2 BFAF2BB7 BFAF2BBC i2 i2 i2 i2 BFAF2BBF
BFAF2BC0 k k BFAF2BC3 BFAF2BC4 i i i i BFAF2BC7
BFAF2BC8 c6 c5 BFAF2BC7 BFAF2BCC c4 c3 c2 c1 BFAF2BCF

El caso es que las variables siempre encajan.

Pero el tema es que cuando creamos un struct en C, lógicamente las variables también tienen que encajar, y además tienen que estar en el mismo orden en que las ponemos, el compilador, esta vez no tiene la libertad de antes de reubicar información.
Vemos el siguiente ejemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>

typedef struct
{
  short s1;         /* 2 */
  int   i1;         /* 4 */
  long long l1;         /* 8 */
  int   i2;         /* 4 */
  long long l2;         /* 8 */
  short s2;         /* 2 */
  char  c1;         /* 1 */
  short s3;         /* 2 */
} mi_struct;

int main()
{
  mi_struct e1;
  printf("%lX [e1]\t\t(%lu bytes)\n",       (long unsigned)&e1,      sizeof(e1));
  printf("%lX [e1.s1]\t\t(%lu bytes)\n",    (long unsigned)&e1.s1,   sizeof(e1.s1));
  printf("%lX [e1.i1]\t\t(%lu bytes)\n",    (long unsigned)&e1.i1,   sizeof(e1.i1));
  printf("%lX [e1.l1]\t\t(%lu bytes)\n",    (long unsigned)&e1.l1,   sizeof(e1.l1));
  printf("%lX [e1.i2]\t\t(%lu bytes)\n",    (long unsigned)&e1.i2,   sizeof(e1.i2));
  printf("%lX [e1.l2]\t\t(%lu bytes)\n",    (long unsigned)&e1.l2,   sizeof(e1.l2));
  printf("%lX [e1.s2]\t\t(%lu bytes)\n",    (long unsigned)&e1.s2,   sizeof(e1.s2));
  printf("%lX [e1.c1]\t\t(%lu bytes)\n",    (long unsigned)&e1.c1,   sizeof(e1.c1));
  printf("%lX [e1.s3]\t\t(%lu bytes)\n",    (long unsigned)&e1.s3,   sizeof(e1.s3));
 
  return 0;
}

Y en este ejemplo sí que podemos ver gran diferencia entre 32bit y 64bit.

32bit
BFC5E510 [e1]           (36 bytes)
BFC5E510 [e1.s1]                (2 bytes)
BFC5E514 [e1.i1]                (4 bytes)
BFC5E518 [e1.l1]                (8 bytes)
BFC5E520 [e1.i2]                (4 bytes)
BFC5E524 [e1.l2]                (8 bytes)
BFC5E52C [e1.s2]                (2 bytes)
BFC5E52E [e1.c1]                (1 bytes)
BFC5E530 [e1.s3]                (2 bytes)
BFC5E510 e1.s1 e1.s1 ? ? BFC5E513 BFC5E514 e1.i1 e1.i1 e1.i1 e1.i1 BFC5E517
BFC5E518 e1.l1 e1.l1 e1.l1 e1.l1 BFC5E51B BFC5E51C e1.l1 e1.l1 e1.l1 e1.l1 BFC5E51F
BFC5E520 e1.i2 e1.i2 e1.i2 e1.i2 BFC5E523 BFC5E524 e1.l2 e1.l2 e1.l2 e1.l2 BFC5E527
BFC5E528 e1.l2 e1.l2 e1.l2 e1.l2 BFC5E52B BFC5E52C e1.s2 e1.s2 e1.c1 ? BFC5E52F
BFC5E530 e1.s3 e1.s3 ? ? BFC5E533

Ya de primeras vemos que la estructura ocupa 36bytes y la suma de sus partes (2 + 4 + 8 + 4 + 8 + 2 + 1 + 2 = 31 bytes), por lo que vemos que la estructura ocupa más.
Si vemos el mapa de memoria (la tabla de arriba), vemos cinco interrogaciones, es decir, 5 huecos donde no trabajamos, y sólo servirán para poder alinear los demás datos.
Vemos que las primeras interrogaciones están en BFC5E512 y es que lo próximo que tenemos que colocar es un entero, por ello nos desplazamos hasta BFC5E514. El siguiente byte problemático es justo después de un char, y lo siguiente será al final, porque se nos quedan algunos bytes “colgados” y no vamos a poder aprovechar.

64bit
7FFFB3652EF0 [e1]               (40 bytes)
7FFFB3652EF0 [e1.s1]            (2 bytes)
7FFFB3652EF4 [e1.i1]            (4 bytes)
7FFFB3652EF8 [e1.l1]            (8 bytes)
7FFFB3652F00 [e1.i2]            (4 bytes)
7FFFB3652F08 [e1.l2]            (8 bytes)
7FFFB3652F10 [e1.s2]            (2 bytes)
7FFFB3652F12 [e1.c1]            (1 bytes)
7FFFB3652F14 [e1.s3]            (2 bytes)

En la tabla acortaré las direcciones de memoria:

B3652EF0 e1.s1 e1.s1 ? ? e1.i1 e1.i1 e1.i1 e1.i1 B3652EF7
B3652EF8 e1.l1 e1.l1 e1.l1 e1.l1 e1.l1 e1.l1 e1.l1 e1.l1 B3652EFF
B3652F00 e1.i2 e1.i2 e1.i1 e1.i2 ? ? ? ? B3652F07
B3652F08 e1.l2 e1.l2 e1.l2 e1.l2 e1.l2 e1.l2 e1.l2 e1.l2 B3652F0F
B3652F10 e1.s2 e1.s2 e1.c1 ? e1.s3 e1.s3 ? ? B3652F17

De primeras vemos que en 64bit la estructura ocupa 40bytes en lugar de 31bytes… pero esta vez, vemos cómo a la hora de colocar un long int (8bytes) en B3652F04, se lo lleva a B3652F08, así será capaz de leerlo en una lectura a memoria (de la palabra entera de 8bytes) en lugar de 2 lecturas (de 4bytes cada una en dos palabras diferentes).

¿Cuándo debemos tener esto en cuenta?

Esto tampoco tiene mucha importancia si sólo vamos a almacenar una pequeña información en un registro, pero, si vamos a almacenar un array de muchos elementos de un registro, o una lista enlazada, podremos observar que si en el registro tenemos 9bytes que sólo sirven para hacer bulto, es decir, para conseguir alinear la información, en un millón de elementos, estaremos perdiendo cerca de 9Mb, nos puede ayudar a optimizar un poco la información que almacenamos.

Por otra parte, si lo que estamos haciendo es implementar una lectura/escritura de información en un formato determinado y, por ejemplo, tenemos:

  • 3 bytes de identificación
  • 4 bytes de control
  • 4 bytes tamaño
  • resto de archivo de información

No podremos crear un registro del tipo:

1
2
3
4
5
6
struct
{
  char identif[3];
  int  control;
  int  size;
} TFormato;

Ya que desde el último carácter hasta el entero de control habrá 1byte de diferencia y el formato no se respetará correctamente, por lo que seguramente haya algún fallo.

Esto tampoco quiere decir que no se pueda leer una variable que no esté alineada, siempre podemos hacerlo jugando con los bytes y trabajando un poco con el código, aunque las arquitecturas modernas lo permiten directamente con instrucciones especiales, eso sí, no se recomienda mucho su uso, ya que es más lento hacer dos peticiones a memoria, y luego trabajar con el resultado de las dos para unirlo; por otra parte, si trabajamos con un procesador que no disponga de estas instrucciones, tendremos que programar cómo hacemos las peticiones y la fusión de la información, aunque de eso se encargue el compilador, debemos tenerlo en cuenta si queremos que nuestro programa sea rápido.

Un pequeño ejemplo de un acceso no alineado

Si queremos provocar un acceso no alineado en C, podemos hacer lo siguiente: crear un array de char más o menos grande, y un puntero a un entero que apunte a una dirección dentro del array de char (con que apunte a la dirección base+1 vale para que no esté alineado.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

int main()
{
  unsigned char datos[6];
  int *punt;
  printf("%lX (%lu)\n", (unsigned long)&datos, sizeof(datos));
  punt=(int*)&datos[1];
  printf("%lX (%lu)\n", (unsigned long)punt, sizeof(*punt));
  *punt=123456789;
  printf("%d %d %d %d => %d %X <= %X %X %X %X\n", datos[1], datos[2], datos[3], datos[4], *punt, *punt, datos[4], datos[3], datos[2], datos[1]);
 
}

La salida es algo así (en 64bits, aunque sólo se nota en el tamaño de las direcciones de memoria):

7FFFC98BE8B0 (6)
7FFFC98BE8B1 (4)
21 205 91 7 => 123456789 75BCD15 <= 7 5B CD 15

Primero mostramos la dirección de memoria base del array y el tamaño del mismo, luego la dirección de memoria base de la variable entera con la que trabajaremos y su tamaño (muy importante el sizeof(*punt), sin el * nos daría el tamaño del puntero, y estamos en 64bit, es decir, sería de 8bytes.
Y tras ello, asignaremos al entero el valor 123456789, podremos ver el valor en decimal de cada uno de los bytes que componen el entero, el valor del entero, y luego el valor del mismo en hexadecimal acompañado de los valores en hexadecimal de cada uno de los bytes de antes.

Esto está diseñado para verse en un sistema little endian, en un sistema big endian, la última línea de salida sería:

7 91 205 21 => 123456789 75BCD15 <= 15 CD 5B 7

Foto: wwarby (Flickr)

De nuevo unos días inactivo, pero aquí vuelvo con un link interesante

Jueves, 7 de Abril de 2011 Gaspar Fernández 2 comentarios

De nuevo he estado un poco perdido, aunque vuelvo para publicar un link que vi hace tiempo y he vuelto a encontrar mientras buscaba información por Internet. Se trata de un manual para construir un cluster casero con un mueble del IKEA como carcasa: http://helmer.sfe.se/.

Para mañana tengo un artículo bastante grande y curioso.

Categories: General Tags: , , , ,

Visita otras webs de la red