lunes, 24 de junio de 2013

Mejorar la calidad de código como manera de optimizar el rendimiento

Artículo perteneciente a la sección de calidad del código

En muchas ocasiones oiréis (o incluso diréis): "he tenido que hacer esta guarrada para poder optimizar este caso concreto; se que no es demasiado bonito, pero era la única manera de optimizarlo". Tenemos metido en la cabeza, desde que empezamos a programar, que los códigos o son mantenibles o son óptimos, cuando la verdad es que normalmente cuando "optimizamos" siempre enguarramos el código mientras que cuando mejoramos la mantenibilidad del código casi siempre mejoramos el rendimiento de nuestro software, casi casi sin quererlo.

Es cierto que esta mejora del rendimiento es genérica y se esparce de igual manera por todo nuestro código y que tal vez no afecte tanto como nos gustaría a nuestra parte más crítica del programa. En este caso, cuando ya hemos hecho todo lo posible para mejorar la calidad del código, es cuando es lícito que apliquemos optimizaciones (como el uso de asm) a las zonas críticas, pero no antes.

El motivo por el cual la mejora de la calidad de código mejora nuestro rendimiento es sencillo. Mejorar la calidad de nuestros programas tiene dos componentes muy marcados: reducir duplicaciones de código(refactorizar) y disminuir la complejidad ciclomática.

Disminuyendo la complejidad ciclomática disminuimos el número de condicionales de nuestras funciones, es decir, reducimos cases dentro de los switchs y reducimos if's. Estas funciones condicionales son de las que más impactan en el rendimiento de nuestro código ya que son las que rompen el pipeline del procesador (lo explicaremos en otro artículo con más calma)  y hacen que el rendimiento de este baje en picado, por lo tanto, reduciendo condicionales aumentamos de forma directa la velocidad de proceso de nuestra aplicación.

Disminuyendo las duplicaciones de código por un lado permitimos que nuestras funciones quepan mejor en la cache de la CPU (nuevamente, lo explicaremos en otro artículo) haciendo que la velocidad de ejecución pueda aumentar drásticamente y por otro lado nos aseguramos que las optimizaciones que posteriormente se quieran implementar se harán solo una vez, evitando fallos y malfuncionamientos.

De resultas de estas dos características se acostumbra a tener un código más pequeño y con menos llamadas innecesarias a funciones, haciendo que el rendimiento de nuestra aplicación también aumente por este motivo.

A modo de ejemplo, imaginemos que tenemos una zona de código donde hemos detectado un cuello de botella. El problema en concreto reside en que tenemos una función que nos devuelve el modulo 10 del número que le pasamos como parámetro (recordad que solo es un ejemplo) y ha de hacerlo muchas veces y mur rápido para cumplir con las especificaciones que nos piden.

int mod_original(int i)
{
return (i%10);
}

Un programador con algo de experiencia sabe que según que operaciones matemáticas cuestan lo suyo por lo tanto, aunque sea una guarrada se podría implementar un vector precalculado o un switch con  todos los casos que nos afecten para no tener que realizar la operación costosa que nos atañe. El código de ejemplo sería este (seguid recordando que esto es un ejemplo :) )

int mod_switch(int i)
{
switch(i)
{
case 0: return 0;
case 1: return 1;
case 2: return 2;
case 3: return 3;
case 4: return 4;
case 5: return 5;
case 6: return 6;
case 7: return 7;
case 8: return 8;
case 9: return 9;
case 10: return 0;
default: return 0;
}
}

Con esta guarrada conseguiriamos mejorar algo el rendimiento, pero a coste de empeorar muchisimo la mantenibilidad.Aparte estariamos introduciendo un error ya que si en el futuro quisieramos usar esta función para calcular el modulo 10 de un número superior a 10 no funcionaria correctamente(default: return 0;). Entonces es cuando un segundo programador con más experiencia o más sentido común debería decir que ese tipo de guarradas no deberían hacerse y buscar otra solución. Al darle vueltas al problema no solo encontraría una forma mejor de implementar la función sino que se daría cuenta que el coste real no estaría en la operación de módulo sino en la llamada a la función, así que propondría una solución como la siguiente:

#define mod_opt(i) (i%10)

Cúal de los tres códigos es el más legible? Y cual el más eficiente?

En cuanto a legibilidad, para mi el tercero (aunque entiendo que es una cuestión personal) y en cuanto a eficiencia, os dejo los valores reales de la ejecución de los tres códigos:

Original:
Case:0 Milis:34
Case:1 Milis:30
Case:2 Milis:29
Case:3 Milis:30
Case:4 Milis:30
Case:5 Milis:30
Case:6 Milis:30
Case:7 Milis:30
Case:8 Milis:34
Case:9 Milis:34
Switch:
Case:0 Milis:29
Case:1 Milis:29
Case:2 Milis:28
Case:3 Milis:29
Case:4 Milis:28
Case:5 Milis:29
Case:6 Milis:28
Case:7 Milis:29
Case:8 Milis:29
Case:9 Milis:28
Opt:
Case:0 Milis:3
Case:1 Milis:3
Case:2 Milis:3
Case:3 Milis:3
Case:4 Milis:3
Case:5 Milis:3
Case:6 Milis:3
Case:7 Milis:3
Case:8 Milis:4
Case:9 Milis:4

Creo que no hace falta decir que en este caso , como en otros muchos, mejorar la legibilidad y la mantenibilidad del código no solo son buenas prácticas de programación sino que permiten mejorar el rendimiento de nuestras aplicaciones de forma directa.

Espero que os haya gustado.

Si tenéis dudas no tengáis reparos hacérmelas llegar.

Nos vemos


LordPakusBlog

domingo, 23 de junio de 2013

Mujeres en la historia de la informática


Hace años, cuando era estudiante en la universidad, me sorprendía que prácticamente no hubiera mujeres programadoras, y luego laboralmente vi que este patrón se repetia. Una cosa que siempre me ha parecido paradójica es por qué motivo hay tan pocas estudiantes de ingenieria en general (y de programación en particular) si después las que puedes encontrar en el ámbito laboral acostumbran a tener un nivel ligeramente superior a la media (percepción personal mía).

Buscando sobre el tema no he encontrado ningún motivo real para que pase esto pero he ido confeccionando una lista con mujeres de alta repercusión en la historia de la informática y la programación. La gran mayoría han sido matemáticas dedicadas a  la computación teórica. ( ¿ tal vez por eso no las encontramos en las carreras de ingenieria y en las empresas de software ? :) )  

La fecha que aparece es el momento en el que se dieron a conocer de forma pública mundial por la publicación de algún trabajo relevante o por ganar algún premio que normalmente no se otorgaba a mujeres.

1842: Ada Lovelace (1815-1852), matemática e hija del famoso poeta Lord Byron. Dedicó una parte importante de su vida a traducir y analizar los estudios sobre la máquina analítica de Charles Babbage. (lo que vendría a ser el diseño del primer computador que jamás pudo ser construido por problemas tecnológicos).  Aún sin saberlo, describió las bases para los lenguajes de programación utilizando tarjetas perforadas como método de introducción de información. Murió sin que se le reconociera ningún merito

1926: Grete Hermann (1901 - 1984), matemática alemana especializada en mecánica cuántica, publica un artículo que se considera la base del algebra computacional

1946: En plena segunda guerra mundial los hombres están en el frente y el gobierno de estados unidos hace una petición de personal femenino con estudios en matemáticas para realizar cálculos balísticos mediante el ENIAC (primera computadora digital de uso general) . Las escogidas fueron: Jean Jennings Bartik,Betty Snyder Holberton,Frances Bilas Spence,Kathleen McNulty Mauchlt Antonelli, Marylin Wescoff Meltzer y Ruth Lichterman Teitelbaum. Despues de este proyecto cada una siguió su camino, algunas siguieron investigando y otras se apartaron de la informatica para formar una familia. Más allá de los galardones individuales por otros trabajos,  no se reconoció su trabajo como grupo hasta 1997.

1949: Grace Hopper (1906-1992) matemática y militar, fue la primera programadora del Mark I,y precursora de los compiladores y los lenguajes de programación. Se la considera la madre del COBOL y colaboró en el diseño del lenguaje FORTRAN.

1950: Ida Rhodes (1900-1986) fue una pionera del análisis de sistemas de programación. Diseño el lenguaje C-10 para el computador UNIVAC. También fue la diseñadora del primer computador usado para la administración de la seguridad social.

1961: Dana Ulery (1938-) Fue la primer mujer ingeniera del JPL desarrollando sistemas de tracking en tiempo real para la aviación de norteamerica (su trabajo acabo desarrollando un computador de 40 bits)

1962: Jean E.Sammet (1928 -) Matemática que trabajo en diversos proyectos de programación, como el desarrollo del COBOL y el FORMAC (del cual es considerada su madre). Trabajó para diversas empresas (IBM entre otras) y fue la primera presidente mujer del ACM (Associartion for Computing Machinery)

1965: Mary Allen Wilkes:(1937-)  Prpgramadora y electrónica. Aunque no está demostrado al 100% se la considera la primera usuaria de computadora en su domicilio particular (computadora que ella misma hizo) y la primera desarrolladora de un sistema operativo (S.O. LAP para minicomputadora LINC)

1965: Hermana Mary Kenneth Keller (1914-1985) Fue la primera mujer americana en conseguir un PhD en ciencias de la computación. Fue hermana de la caridad desde 1940.

1968: Barbara Liskov (1939- ) Primera mujer en estados unidos en conseguir el doctorado en ciencias de la computación. Ganadora de numerosos premios por sus trabajos y profesora de universidad.

1971: Erna Schneider Hoover (1926-) Es una matemática americana considerada la madre de la telefonia computerizada.

1972: Karen Spärck Jones (1935- 2007). Pionera en el campo del procesamiento de la información mediante lenguajes natural y en el proceso de recuperación de la información. Ganadora de numerosos premios por sus trabajos.

1979: Carol Shawn. Primera diseñadora de videojuegos.

1983: Adele Goldberg (1945 -) Madre del lenguaje SmallTalk-80 y precursora de los patrones de diseño

1984: Roberta Williams (1953 -) Diseñadora de aventuras gráficas conocida por su saga King's Quest.

1985: Radia Perlman (1951-). Inventora del protocolo Spanning Tree imprescindible para el normal funcionamiento de las LAN. Se la conoce como Madre de Internet.

1988: Éva Tardos (1957-) Matemática Hungara especializada en la computación teorica con gran cantidad de algoritmos desarrollados(teoria de juegos, red,etc..)

1994: Sally Floyd (1953- ) Coautora de una parte importante del protocolo TCP

2004: Jeri Ellsworth (1974-) Emprendedora y diseñadora de chips autodidacta.Creadora de una replica del commodore64 que permitia conectarse al TV.

2006: Frances E. Allen (1932 -)  Pionera en el campo de la optimización de compiladores y en paralalización

Espero que os haya gustado y que como mínimo os haya parecido curioso.

LordPakusBlog

domingo, 16 de junio de 2013

Tutorial de Batch desde 0: Como usar batch con parámetros



Hola a todos

Es posible que en vuestros desarrollos con scripts de sistema os haga falta que el susodicho batch acepte parámetros (para pasarle el nombre de un fichero por ejemplo).

Realmente es muy fácil. Lo único que se ha de tener en cuenta es que MS-DOS acepta hasta 9 parámetros y que dentro del batch se accede a cada uno de ellos de la siguiente manera %1 , %2,  %3, etc....

El caso de %0 es un parámetro especial que nos devuelve como se ha llamado a nuestro batch (una especie de echo de como nos han llamado), en general no lo usareis.

Un ejemplo sencillo del uso de parámetros puede ser el siguiente. Imaginemos que el fichero se llama saluda.bat y que está compuesto por dos lineas de código:

@echo OFF
echo Hola %1

NOTA: El simbolo @ sirve para eliminar el echo de la función llamada.

Una vez grabamos el fichero como saluda.bat podemos ejecutar la siguiente linea y obtendremos el resultado:
> saluda.bat Maria
> Hola Maria.

Espero que aunque sencillo os haya gustado

Nos vemos


LordPakusBlog

lunes, 10 de junio de 2013

¿Que es un algoritmo?

Artículo perteneciente a la sección de Algoritmos
Artículo relacionado con Historia de la computación

Si eres nuevo en el campo de la programación estarás ya aburrido de oír siempre la palabra algoritmo y no saber que significa, tranquilo que a todos nos ha pasado.

Simplificando mucho el concepto, algoritmo es la descripción formal de como hacer una tarea concreta. Si nos ceñimos a esta descripción una receta de cocina podría ser un algoritmo (de hecho, a mi parecer lo es) y no os ha de extrañar que muchas cosas de nuestra vida cotidiana (como  por ejemplo seguir las instrucciones para montar un mueble ) se puedan entender como algoritmos  debido a que el concepto de algoritmo no proviene de la informática sino de Musa al-Juarismi, un pensador árabe del siglo IX.

En el caso concreto que nos atañe, se define algoritmo dentro de la programación como el conjunto finito de instrucciones secuenciales espaciadas en el tiempo que permiten transformar un conjunto finito de datos de entrada en un conjunto finito de resultados a la salida. No es más que la receta de cocina para resolver problemas de programación concretos.

La gracia de los algoritmos reside en que cada algoritmo, independientemente de la implementación que hagamos de el, tiene asociada una complejidad de cálculo. Esto quier decir que sin implementar ni una sola linea de código podemos saber si el cálculo se hará lento , muy lento o inviable a medida que el conjunto de datos de entrada vaya creciendo. Esto a promovido que grandes informáticos teóricos hayan dedicado sus esfuerzos a pensar nuevas maneras de realizar tareas ya conocidas de forma más eficiente (el ejemplo más claro de esto es la ingente cantidad de algoritmos de búsqueda y ordenamiento que existen).

Por desgracia, en muchas ocasiones los programadores nos centramos en mejorar la implementación de nuestro código, usando código con threads o trozos en ensamblador y no recordamos que lo principal es que el algoritmo sea eficiente para el tamaño de datos con el que vamos trabajar. Además se ha de tener en cuenta que una buena elección de algoritmo va a permitir mantener la calidad de código estable ya que no nos obligará a usar optimizaciones poco mantenibles para mantener el rendimiento.

En breve os iré colgando algoritmos típicos para que veáis con ejemplos lo que os quiero explicar.

Espero que os haya gustado,

Nos vemos
LordPakusBlog

domingo, 9 de junio de 2013

10 consejos para aumentar la calidad del código

Artículo perteneciente a la sección de calidad del código

Cuando hablamos de calidad de código siempre nos planteamos un escenario perfecto, en que empezamos un proyecto desde cero, donde podemos usar TDD desde el principio, donde todo el mundo que trabaja en el proyecto tiene muy claras las normas para mantener la calidad del código y donde , evidentemente, es más importante que el proyecto sea mantenible que no llegar a una fecha en concreto. En ningún momento de mi vida he llegado a ver un proyecto con estas características, ni tan siquiera en mis proyectos personales.

La realidad es que siempre hay prisas para acabar las cosas y las chapuzas (consciente o inconscientemente) van apareciendo sin cesar. Además es bastante raro que el proyecto se haga totalmente desde 0, ya que en el mejor de los casos tendremos que usar librerías externas hechas por terceros (en el peor de los casos el proyecto ya estará empezado de hace tiempo y lo habrá tocado una cantidad ingente de programadores antes que nosotros). En conclusión, casi siempre nos vamos a tener que pelear con el legacy code 

He aquí mi lista personal de trucos para mejorar la calidad del código:

1. No cambiar todo el código de golpe.  Mi primer consejo para poder ir mejorando la calidad del código es que os lo toméis con calma y apliquéis los siguientes trucos solamente en las zonas de código que tengáis que tocar para arreglar otros fallos(yo normalmente uso la norma de 10 lineas por encima, 10 lineas por debajo, pero eso es una medida personal). No empecéis a hacer grandes cambios en todo el proyecto por que sino moriréis en el intento. Ya veréis como poco a poco la legibilidad de vuestro código (y por consiguiente el número de fallos) ira mejorando.

2. Mantener un estilo de código homogéneo. Da igual cual siempre y cuando todo el mundo esté de acuerdo. Indentaciones, uso de llaves, nomenclatura de variables y funciones, se ha de intentar que después de un tiempo nadie pueda distinguir el estilo de programación propio de cada programador dentro del código.

3. Eliminar los comentarios y usar nombres autoexplicativos: Durante muchos años se ha dicho que el código ha de estar bien comentado, pero realmente es un error. Los comentarios no se mantienen, pierden el significado, se copian en lugares que no deberían y acaban generando más confusión y error que otra cosa. Si el motivo para poner un comentario es para que el código se entienda mejor, tal vez la solución es hacer directamente que el código se entienda mejor: usar comentarios no es más que un parche ineficiente.

Si tenemos un código del estilo:
int a; //contador de bucle 
Es mejor que el código quede como:
int contador_bucle;

No solamente eliminaremos un comentario poco necesario sino que haremos que el código se entienda mucho mejor, debido a que cuando usemos la variable no tendremos que preguntarnos que hacía "a", sino que su nombre ya nos explicará perfectamente que estaba haciendo. Lo mismo aplicaría para el nombre de funciones.

4. Que las funciones hagan solamente una cosa: En muchas ocasiones se nos hace complicado separar comportamientos dentro de una función, o directamente, se añade funcionalidades a código que no estaba pensado para eso. Esto puede hacer que el reuso de código o la llamada a código desde diferentes sitios nos afecte a variables o tenga efectos paralelos no deseados con los que no contábamos. Imaginemos que tenemos una función a la que llamamos HazPan() que originariamente hacía pan,  pero que con el tiempo se quedo con las atribuciones de hacer el pan, almacenarlo, distribuirlo y comercializarlo. Obviamente el nombre de la función HazPan es incorrecto y estaríamos violando el tercer principio de esta lista, pero aunque le cambiásemos el nombre (HazTodaLaCadenaProductivaDelPan) seguiría sin ser correcto debido a que el código de esa función tendría la responsabilidad de hacer demasiadas cosas a la vez. Lo lógico sería que el código quedase de esta manera, con funciones que solo tendrían una responsabilidad.
void HazTodaLaCadenaProductivaDelPan(void)
{
         HazPan();
         AlmacenaPan();
         DistribuyePan();
         VendePan();
}
Esto permite que el testo del código sea mucho más sencillo  y reduce efectos colaterales al llamar a cualquier función.

5. Que las funciones sea cortas: Muy relacionado con el punto anterior está el hecho que las funciones deben ser cortas. ¿Cuánto es cortas? Depende de los gustos del equipo, del entorno de desarrollo , del tipo de proyecto,etc.... A mi, 10 lineas me va bien, pero eso es un tema que cada uno se lo ha de mirar en su caso concreto aunque nunca debería exceder el tamaño de la pantalla. Con esto se obliga a que el código de una función se pueda ver y entender con un simple vistazo (1000 lineas de función no se entienden ni mucho menos con la misma velocidad y seguridad que 10 lineas) y que aunque cada función haga una sola cosa nunca será una cosa muy muy compleja.

6. Que las funciones tengan el mínimo de parámetros de entrada necesarios: Esta norma está muy relacionada con el hecho que las funciones sean cortas y que solo hagan una cosa. Cuantos menos parámetros tenga una función, menos complejidad albergará y por lo tanto más complicado será que tengamos errores debido a llamadas incorrectas. En muchas ocasiones los parámetros que les pasamos a las funciones son redundantes y o bien no se usan todos o bien se solapan información entre ellos. Si al intentar reducir el número de parámetros ves que se te hace muy complicado o imposible plantéate que tal vez los puntos anteriores ( solo 1 responsabilidad por función, funciones cortas, etc... ) no se están cumpliendo como deberían.

7. Reducir el número de if's y código duplicado: Si somos capaces de ir reduciendo el número de if's (y de case dentro de los switchs) disminuiremos la complejidad ciclomática y si somos capaces de reducir el código duplicado favoreceremos el cumplimiento de los demás puntos anteriores. Con esto conseguiremos que el código sea bastante más sencillo de entender y por lo tanto de mantener. Un ejemplo sencillo de como hacer esto sería el siguiente:

Delante de este código...
if(condicion1)
{
        if(condicion2)
        {
             a++;
             b++;
        }
        else
        {
             a++;
             c++;
        }
}
else
{
        a++;
        c++;
}

Podríamos reestructurarlo tal que así:

a++;
if (condicion1 && condicion2)
{
     b++;
}
else
{
    c++;
}

Si os fijáis hace exactamente lo mismo, pero con menos código siendo bastante más fácil la comprensión.

8. Usar las herramientas adecuadas: La orientación a objetos de C++ por ejemplo o el uso de patrones de diseño está muy bien, pero mal usados (o abusados en muchas ocasiones) pueden generarnos un incremento de complejidad innecesario en el código. Usad estas herramientas solamente cuando os permitan reducir la duplicación de código o la complejidad ciclomática y usadlas solo para este fin.

9. No ser vago: Todos los trucos que os pueda explicar y toda la experiencia personal que os pueda transmitir no servirán de nada si en el momento de aplicar estas normas os ponéis en formato vago o miedoso. Si aplicáis estos trucos a medias o mal será peor que dejarlo como estaba (al menos lo que había antes estaba testeado y "seguro" que no fallaba), así que cuando os pongáis a arreglar código que no os de miedo borrar una función entera para reescribirla de nuevo, ya que en muchas ocasiones reescribir el código es la única manera para poder mejorar el código.

10. Ajustar los tiempos disponibles y necesarios: Con esto quiero decir que la variable tiempo siempre va a ser la que nos va a ir apretando para que tomemos un criterio de diseño u otro. Así que por mucho que he dicho que no se ha de ser vago tampoco se ha de ser kamikaze, si no da tiempo, no da tiempo, se apunta la zona conflictiva con una posible descripción de la solución y se archiva para cuando se tenga más tiempo, no os enfrasquéis en obras faraónicas de la programación el día antes de entregar un proyecto (ni una semana, ni un mes antes si me apuraís). Hemos de diferenciar entre ser vago y tener sentido común.

Espero que con estos sencillos consejos podáis ir mejorando la calidad de vuestro código de forma rápida y sencilla.

Espero vuestros comentarios explicando como os funciona mi método o bien con vuestras propuestas para mejorar el código.

Espero que os haya gustado

Nos vemos


LordPakusBlog

martes, 4 de junio de 2013

Boletín Mayo 2013

Ver más boletines

Hola a todos,

Este mes de Mayo no ha sido de los más productivos ultimamente, pero ya se sabe que se hace lo que se puede :). A ver si este mes de julio se presenta mejor y podemos publicar más periodicamente.

-> OpenGL desde cero:  Este mes se ha dedicado en exclusividad al curso de openGL desde 0. Tengo en la recámara el capitulo de texturas pero nunca tengo el momento para darle la pincelada final. Espero que en breve lo pueda publicar.

Dentro de este tutorial  hemos escrito tres capítulos nuevos cuyo título es autoexplicativo:
    ->  Capítulo 3 : Transformaciones del espacio
    -> Capítulo 4: Luces y materiales
    -> Capítulo 5: Cámaras

Espero que os haya gustado.

Nos vemos en el próximo boletín
.

LordPakusBlog

Entradas populares