Partida… lagarta… fugida!

Senhoras e senhores, meninos e meninas, a corrida está prestes a começar.
Introdução
Apresento-vos as 4 participantes. São 4 tabelas internas, de diferentes raças e credos, que se vão pelejar pelo título atlético do LOOP em velocidade. Aqui estão elas:
Concorrente 1: DATA: LT_ITEM TYPE TABLE
Concorrente 2: DATA: LT_ITEM_HASHED TYPE HASHED TABLE
Concorrente 3: DATA: LT_ITEM_SORTED TYPE SORTED TABLE
Concorrente 4: DATA: LT_ITEM TYPE TABLE + INTO INDEX
A pista de corridas é um programa ABAP cuja primeira parte, comum aos 4, aqui vos deixo:
REPORT YYYTEST_ABAPINHO. TYPES: BEGIN OF TY_BKPF, BUKRS TYPE BUKRS, BELNR TYPE BELNR_D, GJAHR TYPE GJAHR, END OF TY_BKPF. TYPES: BEGIN OF TY_BSEG, BUKRS TYPE BUKRS, BELNR TYPE BELNR_D, GJAHR TYPE GJAHR, BUZEI TYPE BUZEI, END OF TY_BSEG. DATA: LT_HEADER TYPE TABLE OF TY_BKPF. DATA: LS_HEADER TYPE TY_BKPF. DATA: LS_ITEM TYPE TY_BSEG. DATA: LV_COUNTER TYPE I. START-OF-SELECTION. SELECT BUKRS BELNR GJAHR FROM BKPF INTO TABLE LT_HEADER UP TO 25000 ROWS. CHECK LT_HEADER IS NOT INITIAL. (a partir daqui vem a parte específica de cada um dos concorrentes)
Como vêem, nada de mais. Começa por declarar uns tipos, umas variáveis auxiliares, depois selecciona 25 mil linhas da BKPF para a tabela de cabeçalhos LT_HEADER que é também partilhada pelas 4 concorrentes.
Atenção a todos! Se quiserem fazer apostas, é agora o momento. Porque a prova está prestes a começar.
Aos vossos lugares…
Partida…
Lagarta…
Fugida!
A primeira concorrente é a banal tabela interna de sempre, usada e abusada, como veio de fábrica em meados do século XX. Aqui fica:
DATA: LT_ITEM TYPE TABLE OF TY_BSEG. SELECT BUKRS BELNR GJAHR BUZEI FROM BSEG INTO TABLE LT_ITEM FOR ALL ENTRIES IN LT_HEADER WHERE BUKRS = LT_HEADER-BUKRS AND BELNR = LT_HEADER-BELNR AND GJAHR = LT_HEADER-GJAHR. LOOP AT LT_HEADER INTO LS_HEADER. LOOP AT LT_ITEM INTO LS_ITEM WHERE BUKRS = LS_HEADER-BUKRS AND BELNR = LS_HEADER-BELNR AND GJAHR = LS_HEADER-GJAHR. ADD 1 TO LV_COUNTER. ENDLOOP. ENDLOOP.
Mais convencional é impossível. E os resultados também não são muito animadores. A ver se à luz disto os nossos leitores começam a pensar duas vezes antes de a usar em tudo o que mexe:
É a vez da concorrente número 2, pintura metalizada, jantes de liga leve e equipado com uma HASH de rendimento que lhe dá um roncar de Ferrari. Ei-la:
DATA: LT_ITEM_HASHED TYPE HASHED TABLE OF TY_BSEG WITH UNIQUE KEY BUKRS BELNR GJAHR BUZEI. SELECT BUKRS BELNR GJAHR BUZEI FROM BSEG INTO TABLE LT_ITEM_HASHED FOR ALL ENTRIES IN LT_HEADER WHERE BUKRS = LT_HEADER-BUKRS AND BELNR = LT_HEADER-BELNR AND GJAHR = LT_HEADER-GJAHR. LOOP AT LT_HEADER INTO LS_HEADER. LOOP AT LT_ITEM_HASHED INTO LS_ITEM WHERE BUKRS = LS_HEADER-BUKRS AND BELNR = LS_HEADER-BELNR AND GJAHR = LS_HEADER-GJAHR. ADD 1 TO LV_COUNTER. ENDLOOP. ENDLOOP.
Esta concorrente tem uma chave única definida com 4 campos. Embora as HASH TABLEs sejam o supra-sumo da barbatana quando invocadas com a chave completa, aqui, por se tratar de um LOOP, tal não é naturalmente possível. Por isso, ela arrasta-se vergonhosamente até à meta. Uma vergonha como podem comprovar pelos resultados seguintes:
Segue-se a concorrente número 3, uma bela tabela que já vem ordenada para maior rendimento aerodinâmico. Rápida e silenciosa como se quer. Aqui está:
DATA: LT_ITEM_SORTED TYPE SORTED TABLE OF TY_BSEG WITH UNIQUE KEY BUKRS BELNR GJAHR BUZEI. SELECT BUKRS BELNR GJAHR BUZEI FROM BSEG INTO TABLE LT_ITEM_SORTED FOR ALL ENTRIES IN LT_HEADER WHERE BUKRS = LT_HEADER-BUKRS AND BELNR = LT_HEADER-BELNR AND GJAHR = LT_HEADER-GJAHR. LOOP AT LT_HEADER INTO LS_HEADER. LOOP AT LT_ITEM_SORTED INTO LS_ITEM WHERE BUKRS = LS_HEADER-BUKRS AND BELNR = LS_HEADER-BELNR AND GJAHR = LS_HEADER-GJAHR. ADD 1 TO LV_COUNTER. ENDLOOP. ENDLOOP.
Incrível, Senhoras e senhores! A concorrente número 3 foi muito rápida. Cerca de 2000x mais rápida a fazer os LOOPs do que as duas concorrentes anteriores! Eis sua pontuação:
Por último mas não em último, apresento-vos a concorrente número 4. A mais maluca e rebuscada delas todas. É uma espécie de modelo antigo recauchutado, como fazem àqueles Renault 5 Turbo do tempo da avozinha, que parecem uma porcaria pegada e depois voam baixinho:
DATA: LT_ITEM TYPE TABLE OF TY_BSEG. DATA: LV_INDEX TYPE SYTABIX. SELECT BUKRS BELNR GJAHR BUZEI FROM BSEG INTO TABLE LT_ITEM FOR ALL ENTRIES IN LT_HEADER WHERE BUKRS = LT_HEADER-BUKRS AND BELNR = LT_HEADER-BELNR AND GJAHR = LT_HEADER-GJAHR. SORT LT_ITEM BY BUKRS BELNR GJAHR. LOOP AT LT_HEADER INTO LS_HEADER. READ TABLE LT_ITEM TRANSPORTING NO FIELDS BINARY SEARCH WITH KEY BUKRS = LS_HEADER-BUKRS BELNR = LS_HEADER-BELNR GJAHR = LS_HEADER-GJAHR. CHECK SY-SUBRC = 0. LV_INDEX = SY-TABIX. LOOP AT LT_ITEM INTO LS_ITEM FROM LV_INDEX. IF LS_ITEM-BUKRS <> LS_HEADER-BUKRS OR LS_ITEM-BELNR <> LS_HEADER-BELNR OR LS_ITEM-GJAHR <> LS_HEADER-GJAHR. EXIT. ENDIF. ADD 1 TO LV_COUNTER. ENDLOOP. ENDLOOP.Que resultado impressionante também. Trata-se de uma tabela convencional mas que, graças a umas manobras curiosas, ganha enorme velocidade. No fundo ela faz uns truques para fingir que é mais do que realmente é. Começa por fazer um SORT. Depois, em vez de fazer o segundo LOOP logo de seguida, primeiro faz um READ TABLE BINARY SEARCH e depois faz um LOOP FROM INDEX (um truque de magia já aqui discutido). E os resultados não poderiam ser mais impressionantes pois, embora seja uma tabela da plebe, comporta-se como se sangue azul tivesse:
O Vencedor
E a grande vencedora, senhoras e senhores, é a concorrente número 3, a SORTED TABLE. Logo a seguir, a concorrente número 4, com o seu LOOP FROM INDEX. Como podem comprovar, a diferença entre a SORTED TABLE e esta última das artimanhas é muito pequena. Claro que, podendo escolher, será sempre preferível recorrer a uma SORTED TABLE. Mas caso se esteja a mexer em código existente que tenha já uma tabela das banais e que não possa ser alterada, a concorrente 4 é uma muito boa solução de compromisso. As duas outras tiveram resultados tão maus que mais vale nem falar mais delas.
É interessante comparar os gráficos e observar que o tempo despendido na base de dados é praticamente idêntico nos 4 casos apresentados. As grandes diferenças são ao nível do processamento ABAP.
E assim chega ao fim a corrida de hoje. Felizmente não houve acidentes. Espero que tenha sido tão emocionante para si como foi para nós. Até uma próxima corrida.
(Obrigado Bruno Filipa!)
O Abapinho saúda-vos.
24 de Maio de 2011 às 11:40
Mais uma grande dica, Obrigado.
As imagens entre a HASHED e SORTED não estão trocadas?
Cumpts,
24 de Maio de 2011 às 11:47
Olá Renato,
As imagens estavam realmente trocadas. Já está corrigido, obrigado.
Nuno
25 de Maio de 2011 às 15:39
Boas
Existe mais uma versao que reduz para cerca de metade do tempo do vencedor:
SORT lt_bkpf BY BUKRS BELNR GJAHR.
SORT lt_bseg BY BUKRS BELNR GJAHR.
LOOP AT lt_bkpf ASSIGNING
READ TABLE lt_bseg TRANSPORTING NO FIELDS WITH KEY
BUKRS = -BUKRS
BELNR = -BELNR
GJAHR = -GJAHR BINARY SEARCH.
CHECK sy-subrc = 0.
LOOP AT lt_bseg ASSIGNING FROM sy-tabix.
lv_counter = lv_counter + 1.
** Your logic **
AT END OF GJAHR.
EXIT.
ENDAT.
ENDLOOP.
ENDLOOP.
O AT_END pode ser substituido por um IF a comparar uma LV carregada antes do loop e no fim do Loop.
Abrax
28 de Maio de 2011 às 1:47
Olá Nuno,
Este foi um dos melhores posts que li em seu blog até agora. Parabéns!
Analisar este tipo de processamento realmente vale muito a pena pois é o dia a dia de quem trabalha com ABAP.
Apesar da concorrente 3 ter sido a vencedora na corrida, eu opitaria por utilizar a opção 4 ou algo semelhante como o Rui descreveu acima.
Certa vez precisei fazer uma lógica semelhante porém em três tabelas! Ordenar as tabelas, guardar os sy-tabix, fazer read tables sem transporte de campos e loops usando indices realmente faz a diferença e dão controle ao desenvolvedor, apesar de ser um pouco mais trabalhoso de se desenvolver.
Abraços!
7 de Junho de 2013 às 14:37
Cara excelente teu site. Que idéia de imitar o Gui do Sap.
Parabens meu fofo!
Cara sabia que dá pra mudar toda a visualização do gui do sap, desde trocar icones, mudar o som, etc? Mas só nos antigos, do signature pra frente n da. se tiver interesse.
Abraços,
7 de Junho de 2013 às 14:52
Obrigado Raphael, ainda bem que gostas. Se puderes divulga ;) Obrigado.
Sim, sabia que dava para trocar tudo, mas lá está, só no tema antigo, agora já não dá não sei porquê.
Abraços