Administra tu Blog

¡Crea tu Blog Ya! Fácil y Gratis

Debería funcionar
Divulgación y, sobretodo, divagación sobre computación, mayormente práctico.

Categoría: programación

08/02/2008 GMT 1

Programando en pequeñito (II): la memoria de trabajo del cerebro

fermat @ 00:50

A través de la comunidad formada alrededor de la revista Scientific American, he llegado a un artículo escrito por Andrew W. McCollough y Edward K. Vogel, de la Universidad de Oregon, reflexionando sobre la memoria de trabajo, e incluso calificándola de filtro antispam del cerebro.

Así, y recordando mi primer artículo sobre programación bajo recursos limitados, imagina que nuestra memoria de trabajo fuera de 1MB. Imagina ahora que ese espacio correspondiese al que tenemos para nuestra bandeja de entrada (inbox, pudiendo tener carpetas con más tamaño, que corresponderían a otras áreas de nuestro cerebro), y que los mensajes nuevos sólo pudiesen llegar a dicha bandeja de entrada. Parece conveniente tener un buen sistema que no nos muestre (filtre) la basura en dicha bandeja de entrada porque estaría ocupando espacio, a priori innecesario (quien sabe, hay gente que se lee los mensajes de viagra...), que no podría ser ocupado por mensajes que sí nos interesasen más.

Tal como comentan estos investigadores del Laboratorio de Memoria de Trabajo Visual y Atención de Oregon, existen dos explicaciones para esta limitación en la capacidad. Por un lado, esta memoria podría ser comparada con un espacio de almacenamiento (o disco duro), que variaría de tamaño en cada persona. Por el otro, la capacidad dependería de la eficiencia con la que se use el mismo (o muy parecido) espacio. Por tanto, una mayor capacidad vendría dada por ser mejor en cuanto al descarte de información irrelevante. Es decir, un mejor filtro antispam.

Según sus investigaciones, parece que esta segunda explicación cobra fuerza. Su sorpresa está en que dichas investigaciones darían a entender que las personas con menor capacidad (es decir, peor filtro antispam) tendrían más información residiendo en la memoria de trabajo, pero irrelevante, la mayor parte. Es decir, cuarenta mensajes de basura y dos legítimos.

En dicho artículo continúan con la pregunta clave que tratan de responder: ¿Dónde reside dicho filtro antispam? (Parece que unos investigadores del Instituto del Cerebro de Estocolmo lo han respondido). Pero bueno, en el campo de las investigaciones psicológicas, yo lo dejo aquí (aunque bien podéis seguir los enlaces :)

En cambio, en el plano de las tecnologías de la información, estos estudios también me parecen muy interesantes (y aplicables)... y voy a seguir por aquí.

No sólo podemos deducir que la precisión, en el procesamiento de información, es más importante que su almacenamiento indiscriminado, ni que el mantenimiento de las bandejas de entrada de los usuarios de correo electrónico exija un filtrado óptimo del spam para que éstos puedan fijar su atención, si no que habría que preocuparse, también, por la gestión del conocimiento que nos ayuda a realizar nuestras tareas. Historiales, Marcadores, Mis Favoritos, etc. son buenas ayudas para nuestros propósitos en el campo de los navegadores Web. Los Wikis llevan más de una década sirviendo para esta causa, y sitios Web, como Amazon, nos recuerdan nuestros paseos anteriores por su tienda virtual, mostrándonos lo que piensan que más nos puede interesar, y ahorrándonos (posiblemente) tiempo de paseo, intentando que caminemos por una librería (o tienda, en general) personalizada.

Los motores de búsqueda también suelen incorporar cookies (o recuerdos) relacionadas con nuestro ordenador, ayudando a reencontrar información anteriormente buscada, o intentando mejorar los resultados devueltos, de acuerdo con nuestras anteriores intenciones.

En definitiva, todos estos sistemas suelen buscar el filtrado de información irrelevante (para nosotros), intentando construir una memoria de trabajo de alta capacidad que nos fije la atención y nos ayude en nuestras tareas o propósitos. Al fin y al cabo, nuestra limitada memoria de trabajo, que nos ayuda a sobrevivir cada día, requiere de una programación en pequeñito... :-)

Seguir leyendo el resto »

16/01/2008 GMT 1

Programando en pequeñito (I)

fermat @ 00:12

Muchos de nosotros (por no decir todos, que suena un poco prepotente) hemos aprendido a programar como buenamente hemos podido, pero delante de ordenadores hechos y derechos, sin dar mayor importancia (o ninguna) a circunstancias como el uso comedido de dispositivos (memoria, procesador, disco...).

Claro que tampoco tiene mucho sentido pensar en ello cuando no existe tal limitación. El problema viene cuando nuestro programa no va a utilizarse en un ordenador de sobremesa clónico, o en un portátil con 2 procesadores y más espacio en RAM que los primeros Pentiums en disco, sino cuando se utilizará un teléfono móvil para ello, o una libreta electrónica, o una webcam avanzada (y demás dispositivos limitados).

Por lo general, es necesario dar una vuelta de tuerca al diseño del software que se desarrollará para estos sistemas que, normalmente, no pueden utilizar más de 1MB de espacio, ya sea en disco o en memoria, enfocando, así, el diseño en la mayor limitación que padecemos. Además, como no somos los primeros que nos enfrentamos a estos diseños, tenemos la suerte de poder echar mano de soluciones funcionales y bien probadas para los problemas más comunes que surgen en este tipo de software. Estas soluciones probadas son lo que se conocen como patrones de diseño.

Algunas técnicas definidas en esos patrones son:

  1. Uso de estructuras de datos pequeñas/simples (para que no ocupen mucho)
  2. Diferentes enfoques en cuanto a la asignación de memoria
  3. Uso de compresión: el caso más sangrante es el de las demos de 4ks de las parties (tipo Euskal Party), que ese es el máximo que ocupan en disco (aunque luego se expande esa información en memoria hasta que deja a todo el público boquiabierto)
  4. Uso de almacenes secundarios de la información (como tarjetas sim)
  5. Cambios en los estados de los programas para permitir un máximo de ellos ejecutándose al mismo tiempo

En el primer caso, se pueden utilizar métodos de enmascaramiento de los datos para que ocupen menos. Un ejemplo podría ser la forma de almacenar una dirección IP. Lo primero que nos viene a la cabeza (o no xD) es el uso de una cadena de caracteres para almacenar cada número (y cada punto) tal como son leídos por el ser humano (ej. 127.0.0.1). La segunda posibilidad es almacenarlo como un conjunto de bytes, que es, además, cómo trabajan los sistemas operativos con estos datos. Si la forma en que dicho dato se almacenase nos diese igual, podríamos demostrar que es mejor la segunda opción por lo siguiente:

  • Un carácter ocupa 1 byte de memoria, lo que unido a 9 caracteres que forman la IP de localhost, nos dan 9 bytes utilizados (en IPv6 se complica un poco más)
  • Un entero largo, que es dónde se almacena la dirección IP en forma numérica, ocupa 8 bytes de memoria (el doble que un entero normal). De esta forma, estaríamos usando 1 byte menos que en la primera forma, pudiendo ahorrar hasta 7 bytes (pensad en una estructura repetida muchas veces y multiplicad el ahorro).

Otra recomendación, extendida a todo tipo de programas, aunque muy aconsejada en este tipo de sistemas, es el uso de clases contenedoras de datos (conocidas como DTO (Data Transfer Object)). Igualmente, se aconsejan diseños que fomenten el uso compartido de información, con el fin de evitar duplicados (aunque podría traer problemas mayores ante concurrencia en el acceso a dichos datos... para lo que utilizaríamos otro patrón de diseño conocido que evitaría inconsistencias).

Pasando al siguiente punto, la asignación dinámica de memoria (malloc) es uno de los grandes progresos en la codificación de programas porque permite variar su tamaño en tiempo de ejecución, pudiendo ampliar o reducir su contenido, y así dar flexibilidad a dichos programas. Pero esta flexibilidad tiene un precio, pagado (como casi siempre) en variaciones del rendimiento de nuestros programas.

En este aspecto, quizás lo más importante sea la predicción del uso de memoria. Es decir, tener en mente un rango de uso de memoria del que nunca va a salir nuestros programas. Para ello, hay diferentes aproximaciones, entre las que se podría destacar el evitar llamar a malloc cada vez que se necesite. Las razones son variadas. Por un lado, podría darse el caso de que no tuviésemos suficiente memoria para completar todo el programa. Para resolverlo, se podría asignar toda la memoria que se pretende utilizar al principio del programa (en base a una matriz de objetos o structs), y así no se ejecutará ningún proceso hasta que se esté seguro de tener espacio para ello. Por otro lado, la asignación dinámica no tiene un tiempo definido para realizar dicha asignación (depende del núcleo del sistema operativo y del recolector de basura), algo inconcebible en sistemas de tiempo real. En estos casos, quizás se debería realizar una preasignación que será utilizada de manera transparente durante el programa (haces como que asignas, pero internamente reutiliza un espacio ya asignado). El inconveniente es que se asigna toda la memoria que será utilizada, posiblemente en tiempos diferentes, en todo el programa. Por último, están 2 formas de asignación similares a las anteriores: pool de asignaciones (con nodos en forma de árbol) y asignación estática (no variable, y a la que no afecta el recolector de basura).

Durante la asignación dinámica tiene especial relevancia una sección del espacio asignado en memoria para la ejecución del programa llamada heap, lugar donde se almacena la información asignada. La forma en que construimos el proceso de asignación de memoria de nuestro programa es muy importante ya que los pequeños espacios asignados pueden fragmentar espacios mayores que se asignen posteriormente (lo que provocaría problemas de rendimiento). La idea para resolver esto sería asignar primeramente los espacios mayores, pero tarde o temprano estos espacios se librarán y volverá a surgir dicho problema. Si ésta parece una circunstancia inevitable (asignar y desasignar continuamente), quizás habría que pensar en crear grupos, o varios heaps (a nivel de usuario), con el fin de destruir dichos grupos (eliminando varios espacios del mismo grupo de una sola vez), o incluso optimizar los espacios en memoria.

El tercer punto que comentaba era el de la compresión. Aunque es difícil de implementar, creo que se gana mucho con ello. Dicha compresión puede ir enfocada a los datos (como la codificación de Huffman) o a tablas (en casos de codificaciones de caracteres extrañas, como el cirílico o el chino).

Otra forma de reducir el uso de memoria es mediante el procesamiento secuencial de la información, que se puede conseguir mediante pipes (tuberías). También suele ser interesante cargar las bibliotecas, con funciones externas a nuestro programa, de forma dinámica (es decir, en el momento que vayamos a usarlas), con el fin de reducir el tiempo que permanecen accesibles (y ocupando) en memoria.

Y por último, y para no aburrir más, recordar que estamos jugando con fuego cuando tenemos pocos recursos, donde puede ocurrir que nuestro programa sea interrumpido inesperadamente por el sistema operativo (si es que no estamos programando el sistema operativo xDD) con el fin de salvarse a sí mismo. En estos casos, deberíamos reducir los daños colaterales que se producirían (inconsistencias, etc.), e incluso pensar en una política de recuperación ante problemas (de qué prescindir en el rearranque, ...). Si estamos programando el gestor de memoria del sistema operativo, sería necesario dar a cada proceso en ejecución una prioridad, una importancia, con el fin de clasificarlos y así sacrificar el menos importante/necesario.

Seguir leyendo el resto »

Contactar con la autora o autor | Archivo | ¡Crea tu Blog Ya! Fácil y Gratis