Método del Cursor Paralelo
Posted by Floren en agosto 30, 2008
El rendimiento es un pilar muy importante en la construcción de nuestros programas. Aunque la mayoría de las veces no disponemos de un entorno de desarrollo/integración adecuado para la realización de pruebas de volumen aceptables (comparables a la realidad de los sistemas productivos), existen algunas herramientas en el Workbench de SAP con las que conseguir optimizar los programas que vamos a entregar.
Éste es un área en el que tenemos que investigar y aprender mucho porque es muy amplio y cambiante. Las últimas versiones de SAP incorporan al lenguaje ABAP nuevas instrucciones de acceso a base de datos, nuevos tipos de tablas internas… Hay algunos manuales pero sería bueno hacer una revisión profunda de los mismos y debemos asegurarnos su mayor difusión.
No es nada nuevo el decir que las transacciones ST05 Trace Requests y SE30 Runtime Analysis son las principales herramientas para ello. Un comentario. Se debe salir y entrar a estas transacciones cada vez que se quieran utilizar, pues aparentemente SAP no inicializa los valores obtenidos en la anterior ejecución, lo que conduce a que se acumulen los valores de forma muy engañosa.
Muchas veces nos fijamos y tratamos de mejorar los accesos a bases de datos, lo cual es muy importante. La correcta utilización de la cláusula for all entries, la optimización en la selección de campos en la claúsula select y en la restricción de registros a recuperar en la cláusula where, el empleo de índices al realizar los accesos… son algunos de los muchos puntos que hay que cuidar. Sin embargo no hay que descuidar la programación del tratamiento de los datos, una vez recuperados de la base de datos.
Voy a comentar aquí un ejemplo que mejora sensiblemente el rendimiento en el proceso de tablas internas.
Imaginemos que tenemos dos tablas internas, I_HEADER e I_DETAIL con los datos de cabecera y detalle de documentos de, por ejemplo, FI. Ambas tablas tienen, digamos (para simplificar) el campo BELNR (‘numero de documento’) como enlace común. La forma natural o instintiva de realizar su tratamiento es
Ejemplo 1.
LOOP AT I_HEADER.
LOOP AT I_DETAIL WHERE BELNR = I_HEADER-BELNR.
PROCESO PARA CADA POSICION
ENDLOOP.
ENDLOOP.
Este tratamiento hace que para cada registro de I_HEADER se recorra desde el principio la tabla I_DETAIL hasta encontrar aquel(los) registro(s) con el mismo BELNR que dicho registro de I_HEADER. Esto se puede evitar con el llamado método del curso paralelo (MCP), que ‘recuerda’ qué registros de la tabla I_HEADER ya se han leído en pasos anteriores por el bucle para seguir buscando a partir del último ya leído en el paso anterior:
Ejemplo 2.
V_COUNTER = 1.
LOOP AT I_HEADER.
LOOP AT I_DETAIL FROM V_COUNTER.
V_COUNTER = SY-TABIX.
IF I_DETAIL-BELNR < I_HEADER-BELNR.
CONTINUE.
ELSEIF I_DETAIL-BELNR > I_HEADER-BELNR.
EXIT.
ELSEIF I_DETAIL-BELNR = I_HEADER-BELNR.
PROCESO PARA CADA POSICION
ENDIF.
ENDLOOP.
ENDLOOP.
Aunque de apariencia algo más compleja, los resultados obtenidos son realmente sorprendentes.
Esto requiere que las dos tablas tengan uno o varios campos que las relacionen y exige que ambas tablas estén ordenadas de igual forma por dichos campos. Lógicamente el ejemplo que aquí se muestra está simplificado: se restringe a un solo campo de enlace entre las tablas, pero se podría aplicar para n campos de enlace. Asímismo se puede complicar el proceso, introduciendo además condiciones de restricción where al segundo loop en el Ejemplo 1, siendo entonces sustituidas por condiciones de restricción check dentro del segundo loop en el Ejemplo 2.
Véanse a continuación los tiempos obtenidos al modificar simplemente el tratamiento de tres parejas de tablas en tres forms (F1, F2, F3) en un ejemplo real. El tiempo está expresado en microsegundos, tal y como lo refleja la transacción SE30; la tabla de cabecera contenía únicamente 700 registros. Con un volumen real de datos, los resultados serán impresionantes a todas luces:
Ejemplo 1 % Total Ejemplo 2 % Total Reducción (veces)
TOTAL 24.728.827 100 7.534.817 100
Base Datos 5.587.376 22,6 5.666.839 75.2 0,98 (aprox 1: circunstancial)
ABAP 19.023.286 76,9 1.765.707 23,4 10,8
R3 118.165 0,5 102.271 1,4 1,15 (aprox 1: circunstancial)
F1 8.794.506 125.033 70,3
F2 4.950.223 80.004 61,8
F3 3.905.878 421.887 9,2
Adviértase cómo se traslada el peso del consumo de tiempo desde la lógica de proceso (76,9% en el Ejemplo 1; 23,4% en el Ejemplo 2) en ABAP al acceso a base de datos (22,6% en el Ejemplo 1; 75,2% en el Ejemplo 2). El tiempo de proceso ABAP general se reduce 11 veces. En particular, en los tres forms en los que se puede aplicar esta mejora, se reduce 70, 60 y 9 veces respectivamente. Un análisis detallado del form F3 podría arrojar pistar sobre el motivo de por qué la reducción es ‘sólo’ de 9 veces; pero esto es otra problemática y se escapa al propósito inicial de este mensaje.
gunshit said
Buenas,
A mi con este método me ocurre algo extraño. En efecto al usar el ejemplo 2 los microsegundos se reducen en el total, pero al contrario de lo que dice Floren, a mi el trabajo de ABAP me aumenta considerablemente en el ejemplo 2, disminuyendo la parte de BBDD. Os dejo aquí mi código y los gráficos de la SE30:
Además curiosamente el JOB del ejemplo 1 termina antes que el JOB del ejemplo 2:
JOB Ejemplo 1: 32
JOB Ejemplo 2: 169
Gráficos SE30:
EJEMPLO 1: http://gunshit.250free.com/SAP/test_1.PNG
EJEMPLO 2: http://gunshit.250free.com/SAP/test_2.PNG
Sabeis a que pueden deberse estos comportamientos y como mejorar el acceso en este caso concreto?
Salu2.
EJEMPLO 1: http://paste.ideaslabs.com/show/vgvje9MPQ
EJEMPLO 2: http://paste.ideaslabs.com/show/d7ENcJsC
Fabio A. Rodriguez said
Yo he probado el ejemplo con el cursor paralelo y mas performante que no usarlo. Pero también existe otro algoritmo de cursor paralelo que me parece más performante aún (se los dejo debajo). Por otra parte, cuando realizamos las mediciones de costos de las consultas (testing), debemos considerar que la Base de Datos trabaja en forma interna con estadísticas de las consultas realizadas (para mejorar la Performance); por lo cual, cuando se realiza una segunda consulta seleccionando los mismos datos, es normal que tarde menos que la anterior.
Bueno les dejo mis saludos y el algoritmo:
LOOP AT i_mara INTO w_mara.
READ TABLE i_makt TRANSPORTING NO FIELDS
WITH KEY matnr = w_mara-matnr
BINARY SEARCH.
IF sy-subrc EQ 0.
v_tabix = sy-tabix.
LOOP AT i_makt INTO w_makt FROM v_tabix.
IF w_makt-matnr NE w_mara-matnr.
EXIT.
ENDIF.
ENDLOOP.
ENDIF.
ENDLOOP.
Floren said
Buen aporte Fabio,
Gracias y saludos