"
Apoiado por

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!

Concorrente número 1

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:

Concorrente número 2

É 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:

Concorrente número 3

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:

Concorrente número 4

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.

6 comentários a “Partida… lagarta… fugida!”

  1. RSousa Diz:

    Mais uma grande dica, Obrigado.

    As imagens entre a HASHED e SORTED não estão trocadas?
    Cumpts,

  2. Nuno Godinho Diz:

    Olá Renato,

    As imagens estavam realmente trocadas. Já está corrigido, obrigado.

    Nuno

  3. Rui Sousa Diz:

    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

  4. Fabio Diz:

    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!

  5. raphael bertani Diz:

    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,

  6. Nuno Godinho Diz:

    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

Deixe um comentário


Acerca do Abapinho
O Abapinho é suportado pelo WordPress
Artigos (RSS) e Comentários (RSS).