lunes, 16 de febrero de 2015

Optimizando código

Recientemente hemos pasado por un proceso de optimización en la empresa donde trabajamos para intentar ahorrar MIPs y pagarle menos a IBM ;-)

Para ello el departamente de calidad se puso a escudriñar nuestras transacciones online con el STROBE, para detectar los picos de consumo, tanto del DB2 como del cobol. Para los que nunca halláis visto un informe de STROBE, es una cosa tal que así (muuuy largo):


El informe  ** SQL CPU USAGE SUMMARY ** nos da información sobre el consumo de CPU que se va en SQL por componente. Nos saldrá el desglose de programas ejecutados y su consumo:


Con esto ya podemos ir a tiro fijo a por los programas con más "cruces" :P

Hay otro apartado que se llama ** CPU USAGE BY SQL STATEMENT ** donde para cada programa te analiza sus accesos DB2:


En este ejemplo podéis ver que el programa CONSUL67 realiza la SELECT que viene indicada. En las estadísticas la columna STMT CNT nos indica que se realizan 1.218 selects (unas cuantas).
Aquí podríamos tener un problema de accesos recurrentes que podrían no ser necesarios.

En este otro ejemplo vemos un programa con varios cursores:


Aquí de primeras nos llamaría la atención el segundo OPEN. Si lo comparamos con el tercero, vemos que 567 OPENs del cursor 10 consumen el 2,46%, mientras que 1.491 OPENs del cursor 09 consumen el 0,24%. Lo cual es bastante sospechoso.
En nuestro caso, la tabla del cursor 10 tenía el índice mal definido.

Puliendo cada uno de los programas con mayores consumos iremos optimizando nuestras transacciones.

Más cosas que pueden producir "altos" consumos:
  • Tablas muy accedidas con pocos registros (menos de 7 páginas) que deben cargarse en working en lugar de acceder cada vez que necesitamos consultar algo.
  • Movimientos de grandes áreas de memoria.
  • Inicializar grandes áreas de memoria repetidas veces. 
  • Evitar el uso de Initialize, especialmente para variables tipo tabla (definidas en working o en copy) puesto que el programa tiene que evaluar cada campo antes de asignarle el valor cero o espacios para saber si es numérico o alfanumérico, y eso lo hace para cada ocurrencia de la tabla (aunque el campo ya lo haya analizado en la ocurrencia anterior).
  • Demasiada profundidad en llamadas a programas (programa1 llama a programa 2 que llama a programa 3 que llama a programa 4 que....). Cambiar por llamadas más directas.
  • Variables/copys de working que no se estén utilizando en el programa y que aumentan el área de memoria innecesariamente.
  • Trabajar con tablas demasiado grandes (más de 10000 occurs/500KB).
  • En sentencias SELECT en las que sólo interesa conocer la existencia o no de un registro, siendo indiferente que exista 1 o n veces (lo importante es saber si al menos existe una vez) utilizar el FETCH FIRST 1 ROW ONLY para que en cuanto encuentre una coincidencia deje de buscar.

Accesos DB2 que no van por índice.
En este caso podemos ayudarnos de las estadísticas que genera el  EXPLAIN que nos dirá los índices que utilizan cada uno de los accesos de nuestro programa. 

DECLARE  CUR-CC CURSOR FOR                         
SELECT   COD_CAMPO1                                
        ,QNU_CAMPO2                            
        ,COD_CAMPO3                               
FROM     TABLA001                                   
WHERE    COD_CAMPO4 = :DCLTABLA001.001-COD-CAMPO4
ORDER    BY COD_CAMPO1, QNU_CAMPO2


>>> Explain info from DSN.PLAN_TABLE 2014-08-05-15.47.37.112305
    Access table DSN.TABLA001                                  
       using index DSN.IABLA0013 (1 COL)                        
       Intent share;                                            
       group member is DS1L                             

Esto significa que el cursor está yendo por 1 solo campo del índice 3 de la tabla. En nuestro caso este índice tiene 3 campos y solo estamos informando el primero. Si podemos afinar el WHERE informando los otros 2 campos (que no siempre es posible) mejoraríamos el acceso.
Si el explain nos indica que no se está usando ningún índice, tendremos que revisar ese acceso.

OJO! Aunque se codifique en el WHERE por = y exista un índice con esos campos, no siempre quiere decir que DB2 va a utilizar esos accesos. DB2 tiene un algoritmo muy complejo para decidir cual es el acceso óptimo a los datos que se necesitan. Recordad que es importante el orden de los campos del índice así como el hecho de que sea único (los índices secundarios no siempre sirven para mejorar accesos).

Animaros a ampliar esta información dejando un comentario o escribiendo al correo del consultorio : )








5 comentarios:

Manuel Medina Tarrero dijo...

Super interesante la verdad :)

jamcogar dijo...

De acuerdo al artículo en caso de que tengas una variable tabla con occurs de 500 no es recomendable utilizar el INITIALIZE, en su lugar debería ser reemplazado por MOVE, ¿Es válido un MOVE LOW-VALUES?

jamcogar dijo...

Este comentario ha sido eliminado por un administrador del blog.

Tallian dijo...

Hola jamcogar. Es válido siempre que eso sea lo que necesitas en tu programa. Si vas a controlar los campos comparando con SPACES, entonces tendrías que hacer un MOVE SPACES : )
Recuerda que a los campos numéricos deberás hacerles un MOVE ZEROES en caso necesario.

Swimpi dijo...

Buenas tardes,

Antes de nada felicitarles por los estupendos conocimientos que aquí tienen, son de gran ayuda.
Tengo una duda en cuanto a rendimiento de un programa Cobol se refiere.
¿Afecta en el rendimiento de un programa la colocación o no de asteriscos en las líneas en blanco que a veces se ponen para clarificar código?

El caso es que el código objeto que se genera de ambos fuentes (uno con asteriscos y otro con líneas en blanco) es exactamente el mismo. Entiendo que si el código objeto es el mismo, el ejecutable y el tiempo de ejecución también lo serán. ¿Es correcto?

Otro tema sería el tiempo de compilación que, como va el compilador línea a línea, es posible que sea distinto.

Muchas gracias de antemano, un saludo