Optimización de Rendimiento en ABAP: Desarrollar Programas Más Rápidos

Kategorie
ABAP-Statements
Veröffentlicht
Autor
Johannes

La optimización del rendimiento es crucial para programas ABAP fáciles de usar y que ahorran recursos. Este artículo muestra las técnicas más importantes para mejorar el rendimiento.

Herramientas de Análisis de Rendimiento

HerramientaTransacciónDescripción
SQL TraceST05Analizar accesos a base de datos
ABAP TraceSATAnálisis en tiempo de ejecución
Code InspectorSCIAnálisis estático de código
ABAP ProfilerSATPerfilado detallado
Explain PlanST05Plan de ejecución SQL

Optimización de SQL

Leer Solo las Columnas Necesarias

" Malo - leer todas las columnas
SELECT * FROM mara INTO TABLE @DATA(lt_mara)
WHERE mtart = 'FERT'.
" Bueno - solo columnas necesarias
SELECT matnr, maktx, mtart, matkl
FROM mara
INTO TABLE @DATA(lt_mara_opt)
WHERE mtart = 'FERT'.

SELECT con Uso de Índice

" Malo - no se puede usar índice
SELECT * FROM vbap INTO TABLE @DATA(lt_vbap)
WHERE matnr = @lv_matnr.
" Bueno - usar índice primario
SELECT * FROM vbap INTO TABLE @DATA(lt_vbap)
WHERE vbeln = @lv_vbeln
AND posnr = @lv_posnr.
" Bueno - usar índice secundario (si existe)
SELECT * FROM vbap INTO TABLE @DATA(lt_vbap)
WHERE matnr = @lv_matnr
%_HINTS ORACLE 'INDEX(VBAP VBAP~Z01)'.

Uso Correcto de FOR ALL ENTRIES

" ¡La verificación de tabla vacía es OBLIGATORIA!
IF lt_orders IS NOT INITIAL.
SELECT vbeln, posnr, matnr, kwmeng
FROM vbap
FOR ALL ENTRIES IN @lt_orders
WHERE vbeln = @lt_orders-vbeln
INTO TABLE @DATA(lt_items).
ENDIF.
" Alternativa: JOIN en lugar de FOR ALL ENTRIES
SELECT v~vbeln, v~posnr, v~matnr, v~kwmeng
FROM vbap AS v
INNER JOIN @lt_orders AS o ON v~vbeln = o~vbeln
INTO TABLE @DATA(lt_items_join).

Usar Funciones de Agregación

" Malo - leer todos los datos y sumar
SELECT * FROM vbap INTO TABLE @DATA(lt_all)
WHERE vbeln = @lv_vbeln.
DATA(lv_sum) = REDUCE kwmeng( INIT sum = 0
FOR wa IN lt_all
NEXT sum = sum + wa-kwmeng ).
" Bueno - la base de datos suma
SELECT SUM( kwmeng ) AS total
FROM vbap
WHERE vbeln = @lv_vbeln
INTO @DATA(lv_sum_db).

Activar Buffering

" Activar buffering de tablas en SE11:
" - Buffering completo (tablas pequeñas)
" - Buffering genérico (parte de campos clave)
" - Buffering de registro individual
" Omitir buffer cuando sea necesario
SELECT SINGLE * FROM t001 BYPASSING BUFFER
INTO @DATA(ls_t001)
WHERE bukrs = @lv_bukrs.

Operaciones en Masa

" Malo - INSERTs individuales
LOOP AT lt_new_data INTO DATA(ls_data).
INSERT ztable FROM ls_data.
ENDLOOP.
" Bueno - Array INSERT
INSERT ztable FROM TABLE lt_new_data.
" Bueno - Array UPDATE
UPDATE ztable FROM TABLE lt_update_data.
" Bueno - Array DELETE
DELETE ztable FROM TABLE lt_delete_data.

Tablas Internas

Elegir el Tipo de Tabla Correcto

" STANDARD TABLE - acceso secuencial, APPEND
DATA: lt_standard TYPE STANDARD TABLE OF sflight.
" SORTED TABLE - búsqueda binaria, acceso ordenado
DATA: lt_sorted TYPE SORTED TABLE OF sflight
WITH UNIQUE KEY carrid connid fldate.
" HASHED TABLE - acceso directo por clave
DATA: lt_hashed TYPE HASHED TABLE OF sflight
WITH UNIQUE KEY carrid connid fldate.

Optimizar READ TABLE

" Malo - búsqueda lineal
READ TABLE lt_standard INTO DATA(ls_line)
WITH KEY carrid = 'LH'.
" Bueno - búsqueda binaria (¡la tabla debe estar ordenada!)
SORT lt_standard BY carrid.
READ TABLE lt_standard INTO ls_line
WITH KEY carrid = 'LH' BINARY SEARCH.
" Bueno - SORTED o HASHED TABLE
READ TABLE lt_sorted INTO ls_line
WITH TABLE KEY carrid = 'LH' connid = '0400' fldate = '20250115'.

Usar Claves Secundarias

" Definir clave secundaria
DATA: lt_flights TYPE SORTED TABLE OF sflight
WITH UNIQUE KEY primary_key COMPONENTS carrid connid fldate
WITH NON-UNIQUE SORTED KEY by_plane COMPONENTS planetype.
" Acceso mediante clave secundaria
READ TABLE lt_flights INTO DATA(ls_flight)
WITH KEY by_plane COMPONENTS planetype = 'A380'.
LOOP AT lt_flights INTO ls_flight USING KEY by_plane
WHERE planetype = 'A380'.
ENDLOOP.

Optimización de LOOP

" Malo - WHERE sin índice
LOOP AT lt_large_table INTO DATA(ls_line)
WHERE status = 'A'.
ENDLOOP.
" Bueno - WHERE en tabla ordenada
LOOP AT lt_sorted INTO ls_line
WHERE carrid = 'LH'. " Usa la ordenación
ENDLOOP.
" Bueno - ASSIGNING en lugar de INTO (sin copia)
LOOP AT lt_table ASSIGNING FIELD-SYMBOL(<ls_line>).
<ls_line>-status = 'P'. " Modificación directa
ENDLOOP.
" Bueno - REFERENCE INTO para operaciones de lectura
LOOP AT lt_table REFERENCE INTO DATA(lr_line).
DATA(lv_value) = lr_line->field.
ENDLOOP.

Joins de Tablas en Memoria

" Malo - LOOPs anidados
LOOP AT lt_orders INTO DATA(ls_order).
LOOP AT lt_items INTO DATA(ls_item)
WHERE order_id = ls_order-order_id.
ENDLOOP.
ENDLOOP.
" Bueno - SORTED TABLE con WHERE
DATA: lt_items_sorted TYPE SORTED TABLE OF ty_item
WITH NON-UNIQUE KEY order_id.
lt_items_sorted = lt_items.
LOOP AT lt_orders INTO ls_order.
LOOP AT lt_items_sorted INTO ls_item
WHERE order_id = ls_order-order_id.
ENDLOOP.
ENDLOOP.

Paralelización

Llamadas RFC Paralelas

DATA: lv_task TYPE string,
lv_counter TYPE i,
lt_results TYPE TABLE OF ty_result.
" Iniciar tareas paralelas
LOOP AT lt_work_packages INTO DATA(ls_package).
lv_counter = lv_counter + 1.
lv_task = |TASK{ lv_counter }|.
CALL FUNCTION 'Z_PROCESS_PACKAGE'
STARTING NEW TASK lv_task
DESTINATION IN GROUP DEFAULT
CALLING on_task_complete ON END OF TASK
EXPORTING
is_package = ls_package
EXCEPTIONS
resource_failure = 1
communication_failure = 2.
IF sy-subrc <> 0.
" Fallback: procesar síncronamente
CALL FUNCTION 'Z_PROCESS_PACKAGE'
EXPORTING
is_package = ls_package
IMPORTING
es_result = DATA(ls_result).
APPEND ls_result TO lt_results.
ENDIF.
ENDLOOP.
" Esperar a todas las tareas
WAIT UNTIL lv_completed = lv_counter UP TO 300 SECONDS.
" Método de callback
FORM on_task_complete USING p_task TYPE clike.
DATA: ls_result TYPE ty_result.
RECEIVE RESULTS FROM FUNCTION 'Z_PROCESS_PACKAGE'
IMPORTING
es_result = ls_result.
APPEND ls_result TO lt_results.
lv_completed = lv_completed + 1.
ENDFORM.

Framework SPTA

" SPTA para procesamiento paralelo
DATA: lt_input TYPE spta_t_input,
lt_output TYPE spta_t_output.
" Preparar input
LOOP AT lt_work_items INTO DATA(ls_item).
APPEND INITIAL LINE TO lt_input ASSIGNING FIELD-SYMBOL(<ls_input>).
<ls_input>-data = ls_item.
ENDLOOP.
" Procesar en paralelo
CALL FUNCTION 'SPTA_PARA_PROCESS_START_2'
EXPORTING
server_group = 'parallel_generators'
max_no_of_tasks = 10
before_rfc_callback = 'PREPARE_PACKAGE'
in_rfc_callback = 'PROCESS_PACKAGE'
after_rfc_callback = 'COLLECT_RESULT'
CHANGING
user_param = lt_input
EXCEPTIONS
OTHERS = 1.

Operaciones de String

Concatenación Eficiente

" Malo - CONCATENATE repetido
DATA: lv_result TYPE string.
LOOP AT lt_parts INTO DATA(lv_part).
CONCATENATE lv_result lv_part INTO lv_result.
ENDLOOP.
" Bueno - String Templates
lv_result = REDUCE string( INIT r = ``
FOR part IN lt_parts
NEXT r = r && part ).
" Bueno - CONCATENATE LINES OF
CONCATENATE LINES OF lt_parts INTO lv_result SEPARATED BY space.

Cachear Expresiones Regulares

" Malo - compilar regex en cada llamada
LOOP AT lt_strings INTO DATA(lv_string).
FIND REGEX '\d{4}-\d{2}-\d{2}' IN lv_string.
ENDLOOP.
" Bueno - compilar regex una vez
DATA(lo_regex) = NEW cl_abap_regex( pattern = '\d{4}-\d{2}-\d{2}' ).
DATA(lo_matcher) = lo_regex->create_matcher( text = `` ).
LOOP AT lt_strings INTO lv_string.
lo_matcher->match( text = lv_string ).
ENDLOOP.

Optimización de Memoria

Liberar Tablas Grandes

" Liberar memoria explícitamente
CLEAR lt_large_table.
FREE lt_large_table.
" O procesar con REDUCE
DATA(lv_sum) = REDUCE i( INIT s = 0
FOR <line> IN lt_large_table
NEXT s = s + <line>-amount ).
FREE lt_large_table. " Liberar inmediatamente

Procesamiento por Paquetes

DATA: lv_offset TYPE i VALUE 0,
lv_limit TYPE i VALUE 1000.
DO.
SELECT * FROM large_table
ORDER BY primary_key
INTO TABLE @DATA(lt_package)
OFFSET @lv_offset
UP TO @lv_limit ROWS.
IF lt_package IS INITIAL.
EXIT.
ENDIF.
" Procesar paquete
LOOP AT lt_package INTO DATA(ls_line).
" Procesamiento...
ENDLOOP.
FREE lt_package.
lv_offset = lv_offset + lv_limit.
ENDDO.

Buffering

Shared Memory Objects

" Shared Memory para datos leídos frecuentemente
CLASS zcl_cache DEFINITION.
PUBLIC SECTION.
CLASS-METHODS get_config
RETURNING VALUE(rt_config) TYPE ty_config_tab.
ENDCLASS.
CLASS zcl_cache IMPLEMENTATION.
METHOD get_config.
" Verificar Shared Buffer
IMPORT config = rt_config FROM SHARED BUFFER indx(zc)
ID 'CONFIG'.
IF sy-subrc <> 0.
" Cargar datos
SELECT * FROM zconfig INTO TABLE rt_config.
" Guardar en Shared Buffer
EXPORT config = rt_config TO SHARED BUFFER indx(zc)
ID 'CONFIG'.
ENDIF.
ENDMETHOD.
ENDCLASS.

ABAP Shared Objects

" Shared Objects para datos complejos
" 1. Definir área (SHMA)
" 2. Crear clase raíz
TRY.
" Obtener handle del área
DATA(lo_handle) = zcl_my_area=>attach_for_read( ).
DATA(lo_root) = lo_handle->get_root( ).
" Leer datos
DATA(lt_data) = lo_root->get_cached_data( ).
lo_handle->detach( ).
CATCH cx_shm_error.
" Fallback
ENDTRY.

Análisis de Rendimiento

Evaluar SQL Trace

" Activar ST05
" Ejecutar programa
" Evaluar trace
" Problemas típicos:
" - Identical Selects (misma consulta varias veces)
" - SELECT * (demasiadas columnas)
" - Índice faltante
" - Demasiados registros individuales

Runtime Analysis (SAT)

" Crear trace SAT
SET RUN TIME ANALYZER ON.
" ... ejecutar código ...
SET RUN TIME ANALYZER OFF.
" O mediante transacción SAT
" - Vista agregada
" - Hit List
" - Call Hierarchy

Checks de Code Inspector

" SCI - variantes de check importantes:
" - SELECT en LOOP
" - APPEND en LOOP en lugar de Array
" - Condición WHERE faltante
" - INTO CORRESPONDING FIELDS
" - Operaciones de tabla ineficientes

Medición y Benchmarking

DATA: lv_start TYPE i,
lv_end TYPE i,
lv_diff TYPE i.
GET RUN TIME FIELD lv_start.
" Medir código
LOOP AT lt_table INTO DATA(ls_line).
" ...
ENDLOOP.
GET RUN TIME FIELD lv_end.
lv_diff = lv_end - lv_start.
WRITE: / 'Tiempo de ejecución:', lv_diff, 'microsegundos'.
" O con timestamps
DATA(lv_ts_start) = utclong_current( ).
" ... código ...
DATA(lv_ts_end) = utclong_current( ).
DATA(lv_seconds) = cl_abap_tstmp=>subtract(
tstmp1 = lv_ts_end
tstmp2 = lv_ts_start ).

Mejores Prácticas

  1. Medir antes de optimizar: Siempre perfilar primero, luego optimizar
  2. Reducir carga de base de datos: Menos SELECTs, WHERE eficiente
  3. Tipo de tabla correcto: HASHED para acceso por clave, SORTED para rangos
  4. Liberar memoria: FREE para tablas grandes después del uso
  5. Paralelización: Para tareas independientes
  6. Buffering: Para datos leídos frecuentemente y raramente modificados

Transacciones Importantes

TransacciónDescripción
ST05SQL/Buffer Trace
SATABAP Trace
SCICode Inspector
ST12Trace combinado
SM50Work Processes
ST02Estadísticas de buffer

Temas Relacionados