The document discusses how nested loops can significantly impact performance compared to SELECT statements when handling large amounts of data.
The document discusses how nested loops can perform much worse than inefficient SELECT statements, especially when large amounts of data are involved.
Examples are provided showing the performance of nested loops vs SELECT statements on small and large test datasets from SAP tables.
Getting Started Newsletters Store
Products Services & Support About SCN Downloads
Industries Training & Education Partnership Developer Center Lines of Business University Alliances Events & Webinars Innovation Log On Join Us Hi, Guest Search the Community Activity Communications Actions Browse ABAP Testing and Troubleshooting Previous post Next post 0 Tweet 0 This BLOG is an expanded version of a post that I made to the ABAP forum a while ago. What I wanted to show was that the effect that when performance tuning, the effect of nested loops can be far worse than poorly constructed SELECT statements. I wrote a small program which illustrates this point. I’ve posted the code below, but in a nutshell, what it does is select a number of FI document headers and line items from BKPF and BSEG. The select statement from BSEG is very inefficient. I did it that way to prove a point. It then reads all records from both tables using a nested loop. Then it reads all records from both tables using a much more efficient method. Finally, it reads all records from both tables using a different method which may be even more efficient, but may be somewhat more difficult to program. It keeps track of the number of records and time taken for each operation. I ran the program twice in our QA environment – once selecting a small amount of data and again selecting a moderate amount. The outputs are: First run - Time for unindexed select : 00:06:09 Number of BKPF entries: 5,863 Number of BSEG entries: 17,683 Time for nested loop : 00:00:53 Number of BKPF reads : 5,863 Number of BSEG reads : 17,683 Time for indexed loop : 00:00:00 Number of BKPF reads : 5,863 Number of BSEG reads : 17,683 Time for parallel cursor : 00:00:00 Number of BKPF reads : 5,863 Number of BSEG reads : 17,683 Second run – Time for unindexed select : 00:21:07 Number of BKPF entries: 55,777 Number of BSEG entries: 205,285 Time for nested loop : 02:16:21 Number of BKPF reads : 55,777 Number of BSEG reads : 205,285 Time for indexed loop : 00:00:01 Number of BKPF reads : 55,777 Number of BSEG reads : 205,285 Time for parallel cursor : 00:00:01 Number of BKPF reads : 55,777 Number of BSEG reads : 205,285 So what can we conclude? In the first case, a gain in time of almost a minute is not much, but in a dialogue program, it would be worthwhile. But in the second case the other method gains over two hours. This would allow a program that has to run in the background to run in the foreground. The most striking thing to me though, is the fact that the nested loop takes substantially longer than an extremely inefficient select statement. In both cases, the loop using the parallel cursor method did not produce a substantial savings over the loop using a binary search followed by an indexed read; however, if you run this and extract a very large amount of data, it does run more quickly. In the program I have provided, the parallel cursor method does appear to be somewhat easier to program, but I have found that if the outer loop does not contain all of the records in the inner loop, then programming complexity is increased and I don't think it warrants the extra effort. The binary search method is easy and runs quickly. Performance of Nested Loops Posted by Rob Burbank in ABAP Testing and Troubleshooting on Feb 7, 2006 3:23:15 PM Share 0 Like Average User Rating (0 ratings) 0 Tweet 0 The select screen for the program is quite standard. The amount of data returned is determined by whatever the user enters and the programmer really has no control over it. So I’m suggesting that if you can’t guarantee that the amount of data is small, then you really ought to use the indexed read method. Also note that the first select statement returns 17,683 rows from BSEG and took 06:09 to run and the second one returns 205,285 rows and takes 21:07. The second one retrieves almost 11.5 times as much data but takes only about 3.5 times as long to execute. The two runs of the program were on separate evenings when there shouldn’t be any load, so buffering and workload shouldn’t be issues. So, my conclusion is: when tuning a program that you know will return a small amount of data, tune the select statement and don’t worry too much about loops; however, if the program may return a large amount of data, avoid nested loops. And a final thought: once you get a program tuned to a certain point, it doesn't make a lot of sense to try to spend a lot of time trying to reduce execution time by a small amount. (More on this later) Code follows: report ztest_nested_loop. data: bkpf type bkpf, bseg type bseg. select-options: s_bukrs for bseg-bukrs memory id buk obligatory, s_gjahr for bseg-gjahr memory id gjr obligatory, s_lifnr for bseg-lifnr memory id lif obligatory. data: bkpf_tab type standard table of bkpf, bkpf_lin like line of bkpf_tab, bseg_tab type standard table of bseg, bseg_lin like line of bseg_tab. data: start_time type sy-uzeit, end_time type sy-uzeit, difference type sy-uzeit, bkpf_entries type sy-tabix, bseg_entries type sy-tabix, bkpf_reads type sy-tabix, bseg_reads type sy-tabix. start-of-selection. perform unindexed_select. perform nested_loop. perform indexed_loop. PERFORM parallel_cursor. *&---------------------------------------------------------------------* *& Form unindexed_select *&---------------------------------------------------------------------* form unindexed_select. get time field start_time. select * from bseg into table bseg_tab where bukrs in s_bukrs and gjahr in s_gjahr and lifnr in s_lifnr. if sy-subrc <> 0. message id '00' type 'E' number '001' with 'No entries selected'. endif. select * from bkpf into table bkpf_tab for all entries in bseg_tab where bukrs = bseg_tab-bukrs and belnr = bseg_tab-belnr and gjahr = bseg_tab-gjahr and bstat = space. clear bseg_tab. refresh bseg_tab. select * from bseg into table bseg_tab for all entries in bkpf_tab where bukrs = bkpf_tab-bukrs and belnr = bkpf_tab-belnr and gjahr = bkpf_tab-gjahr. get time field end_time. difference = end_time - start_time. describe table bkpf_tab lines bkpf_entries. describe table bseg_tab lines bseg_entries. write: /001 'Time for unindexed select:', difference, /005 'Number of BKPF entries:', bkpf_entries, /005 'Number of BSEG entries:', bseg_entries. skip 1. endform. " unindexed_select *&---------------------------------------------------------------------* *& Form nested_loop *&---------------------------------------------------------------------* form nested_loop. get time field start_time. loop at bkpf_tab into bkpf_lin. bkpf_reads = bkpf_reads + 1. loop at bseg_tab into bseg_lin where bukrs = bkpf_lin-bukrs and belnr = bkpf_lin-belnr and gjahr = bkpf_lin-gjahr. bseg_reads = bseg_reads + 1. endloop. endloop. get time field end_time. difference = end_time - start_time. write: /001 'Time for nested loop:', difference, /005 'Number of BKPF reads:', bkpf_reads, /005 'Number of BSEG reads:', bseg_reads. skip 1. endform. " nested_loop *&---------------------------------------------------------------------* *& Form indexed_loop *&---------------------------------------------------------------------* form indexed_loop. data: bkpf_index like sy-tabix, bseg_index like sy-tabix. clear: bkpf_reads, bseg_reads. get time field start_time. sort: bkpf_tab by bukrs belnr gjahr, bseg_tab by bukrs belnr gjahr. loop at bkpf_tab into bkpf_lin. read table bseg_tab into bseg_lin with key bukrs = bkpf_lin-bukrs belnr = bkpf_lin-belnr gjahr = bkpf_lin-gjahr binary search. bkpf_reads = bkpf_reads + 1. bseg_index = sy-tabix. while sy-subrc = 0. bseg_index = bseg_index + 1. bseg_reads = bseg_reads + 1. read table bseg_tab into bseg_lin index bseg_index. if bseg_lin-bukrs <> bkpf_lin-bukrs or bseg_lin-belnr <> bkpf_lin-belnr or bseg_lin-gjahr <> bkpf_lin-gjahr. sy-subrc = 99. else. endif. endwhile. endloop. get time field end_time. difference = end_time - start_time. write: /001 'Time for indexed loop:', difference, /005 'Number of BKPF reads:', bkpf_reads, /005 'Number of BSEG reads:', bseg_reads. skip 1. endform. " indexed_loop *&---------------------------------------------------------------------* *& Form parallel_cursor *&---------------------------------------------------------------------* * text *----------------------------------------------------------------------* FORM parallel_cursor. DATA: bkpf_index LIKE sy-tabix, bseg_index LIKE sy-tabix. CLEAR: bkpf_reads, bseg_reads. GET TIME FIELD start_time. SORT: bkpf_tab BY bukrs belnr gjahr, bseg_tab BY bukrs belnr gjahr. bseg_index = 1. LOOP AT bkpf_tab INTO bkpf_lin. bkpf_reads = bkpf_reads + 1. LOOP AT bseg_tab INTO bseg_lin FROM bseg_index. IF bseg_lin-bukrs <> bkpf_lin-bukrs OR bseg_lin-belnr <> bkpf_lin-belnr OR bseg_lin-gjahr <> bkpf_lin-gjahr. bseg_index = sy-tabix. EXIT. ELSE. bseg_reads = bseg_reads + 1. ENDIF. ENDLOOP. ENDLOOP. GET TIME FIELD end_time. difference = end_time - start_time. WRITE: /001 'Time for parallel cursor :', difference, /005 'Number of BKPF reads :', bkpf_reads, /005 'Number of BSEG reads :', bseg_reads. ENDFORM. " parallel_cursor
More information can be found in SE30 (Tips and Tricks) 1002 Views Topics: abap Tags: beginner, analytics, loop Share 0 Like 21 Comments Like (0) Matthew Gif f ord Feb 8, 2006 5:45 AM Your code for the Index and Nested loops is misleading, as these do not retrieve the data from the database. However this would make the Nested loop times greater by up to 20? miniutes. Another thing to look out for is the sequence that you try each of these processes. The first process to fill an internal table will take longer than a process to re-fill it. The memory to store the data has to be allocated. I was however suprised at how much longer the nested loop took to process the data. MattG. Like (0) Rob Burbank Feb 8, 2006 7:10 AM (in response to Matthew Gif f ord) Well, the idea was to compare how the performance of nested loops compared to an inefficient SELECT statement, so I retrieved the data entirely separately from LOOPing through it. Rob Like (0) A. de Smidt Jun 21, 2007 8:11 AM (in response to Rob Burbank) As mentioned in my thread I noticed that the parallel loop fails if the nested table contains keys which the main table doesn't have. the very slow standard nested loop doesn't have this problem with additional keys in the nested table. Before I do the parallel looop I delete all the entries which are not in the main table from the nested itab. perhaps there is a nicer alternative :) Rob Burbank Jun 21, 2007 9:22 AM (in response to A. de Smidt) In this case you have to use the parallel cursor technique. Rob Like (0) Like (0) Feb 8, 2006 6:30 AM Hi Rob,James Gaddis Like (0) Rob Burbank Feb 8, 2006 12:46 PM (in response to ) Thanks for the kind comments. I don't have time today, but I'll have a look at your code as soon as I can. Rob Like (0) Cem Dedeoglu Feb 10, 2006 7:44 AM (in response to ) thnx for good posts.. You may also use "transporting no fields" addition while finding index of bseg_tab with binary search. this may reduce the assign overhead. just for extreme performance ;) Shaban Like (0) Rob Burbank Feb 15, 2006 3:07 PM (in response to ) I added your code to my program and modified it so that it did some more efficient selecting (the bad one was just to prove a point). Then I ran the three more efficient loops a number of times. What I found was that you code ran in about in about 80% of the time that the indexed loop took and about 70% of the time of the parallel cursor. So your code is faster. But then I wasn't really trying to find the fastest way to process two tables, just to show that nested loops have to be avoided at all costs for large tables. Incidentally, from the above, the indexed loop method consistently outperforms the parallel cursor method. I find this surprising because the records in each table will be read only once using parallel cursors, but the inner table will be read more often using the indexed loop. The only reason I can think of for this is that the SAP kernel has somehow optimized the binary search. Rob Like (0) Matthew Gif f ord Feb 17, 2006 3:09 AM (in response to Rob Burbank) Rob Just something to check with these methods is to only sort the data once. Record the time taken to sort the tables, and add it to the runtime of the methods that need sorted data. Sort methods, generally, are quicker at sorting unsorted data than sorted data. Like (0) Rob Burbank Feb 17, 2006 6:02 AM (in response to Matthew Gif f ord) Good point - I agree. Rob Matthew Gif f ord Feb 9, 2006 7:25 AM It is interesting to compare the times for very small number of records. (This is for SCARR, SPFLI on miniSAP). My times, in microseconds:<br/>Select: 96439<br/>Nested: 31<br/>Indexed: 59<br/>Other: 31<br/>This was for 6 SCARR and 10 SPFLI records.<br/>So this test would show that a Nested loop was the best option. (James's 'Other' logic does not include the required sort). <br/><br/>I tried to find out what was the better method to update an internal table, assign or modify. I found that the overhead of the first assign was 20 times that of subsequent assigns. This assign was 6 times longer than the first modify, itself longer than later modifies.<br/><br/>What was odd about the results, it made a difference to the times if I modify loop first or second. <br/><br/>My code: <br/>&---- <br/>*& Report Z_MG_TYPE_FS_ITAB <br/>& <br/>&-- <br/>& A little test program to see if loop at assigning would work. <br/>& This also proves that Like (0) the field-symbol has structure. <br/>& <br/><br/>REPORT Z_MG_TYPE_FS_ITAB .<br/><br/><br/>CONSTANTS: C_DONUMBER TYPE I VALUE 10.<br/><br/>TYPES: BEGIN OF TYP_SPFLI.<br/>INCLUDE STRUCTURE SPFLI. <br/>TYPES: TABIX1 TYPE SYTABIX,<br/> TABIX2 TYPE SYTABIX,<br/> END OF TYP_SPFLI.<br/>FIELD-SYMBOLS: <SPFLI> TYPE TYP_SPFLI.<br/>DATA: T_SPFLI TYPE STANDARD TABLE OF TYP_SPFLI,<br/> H_SPFLI TYPE TYP_SPFLI.<br/><br/>TYPES: BEGIN OF TYP_RESULT,<br/> INDEX TYPE SYINDEX,<br/> ASSIGN TYPE I,<br/> AFIRST TYPE I,<br/> MODIFY TYPE I,<br/> MFIRST TYPE I,<br/> END OF TYP_RESULT. <br/>DATA: T_RESULT TYPE STANDARD TABLE OF TYP_RESULT,<br/> S_RESULT TYPE TYP_RESULT.<br/><br/>DATA: TIMESTAMP1 TYPE I,<br/> TIMESTAMP2 TYPE I, <br/> MIN_TIME_A TYPE I VALUE 1000000,<br/> MIN_TIME_M TYPE I VALUE 1000000, <br/> MAX_TIME_A TYPE I VALUE 0,<br/> MAX_TIME_M TYPE I VALUE 0,<br/> MEANTIME_A TYPE I VALUE 0,<br/> MEANTIME_M TYPE I VALUE 0.<br/><br/>PARAMETERS: P_DEBUG AS CHECKBOX,<br/> Who goes first.<br/> P_ASSIGN RADIOBUTTON GROUP GO,<br/> P_MODIFY RADIOBUTTON GROUP GO,<br/> P_XTRAAS AS CHECKBOX.<br/><br/><br/><br/>START-OF-SELECTION.<br/> IF P_DEBUG <> SPACE.<br/> BREAK-POINT.<br/> ENDIF.<br/><br/> SELECT * INTO TABLE T_SPFLI FROM SPFLI.<br/><br/>* Assign field-symbol, so it is not initial.<br/> IF P_XTRAAS = 'X'.<br/> ASSIGN H_SPFLI TO <SPFLI>.<br/> ENDIF.<br/><br/> DO C_DONUMBER TIMES.<br/> S_RESULT-INDEX = SY- INDEX.<br/> IF P_MODIFY = SPACE.<br/> PERFORM UPDATE_USING_ASSIGN.<br/> PERFORM UPDATE_USING_MODIFY.<br/><br/> ELSE.<br/> PERFORM UPDATE_USING_MODIFY.<br/> PERFORM UPDATE_USING_ASSIGN.<br/> ENDIF.<br/> APPEND S_RESULT TO T_RESULT.<br/> ENDDO.<br/><br/><br/> WRITE: / 'Time Assigning:', MIN_TIME_A, MAX_TIME_A, MEANTIME_A,<br/> / 'Time Modifying:', MIN_TIME_M, MAX_TIME_M, MEANTIME_M.<br/> ULINE.<br/> IF P_ASSIGN = 'X'.<br/> WRITE: / 'Assign processed first.'.<br/> ELSE.<br/> WRITE: / 'Modify processed first.'.<br/> ENDIF.<br/> ULINE. <br/> SKIP.<br/> LOOP AT T_RESULT INTO S_RESULT.<br/> WRITE: / 'Index :', S_RESULT- INDEX,<br/> 'Assign:', S_RESULT-ASSIGN,<br/> 'First assign:', S_RESULT- AFIRST,<br/> 'Modify:', S_RESULT-MODIFY,<br/> 'First modify:', S_RESULT- MFIRST.<br/> ENDLOOP.<br/> ULINE.<br/><br/><br/>*& <br/>& Form UPDATE_USING_ASSIGN<br/>*& <br/> text<br/>* <br/>FORM UPDATE_USING_ASSIGN.<br/> DATA: L_FIRSTTIME TYPE I.<br/><br/> GET RUN TIME FIELD TIMESTAMP1.<br/> LOOP AT T_SPFLI ASSIGNING <SPFLI>.<br/> <SPFLI>-TABIX1 = SY- TABIX.<br/> IF SY-TABIX = 1.<br/> GET RUN TIME FIELD L_FIRSTTIME.<br/> ENDIF.<br/> ENDLOOP.<br/> GET RUN TIME FIELD TIMESTAMP2.<br/> TIMESTAMP2 = TIMESTAMP2 - TIMESTAMP1.<br/> IF TIMESTAMP2 < MIN_TIME_A.<br/> MIN_TIME_A = TIMESTAMP2.<br/> ENDIF.<br/> IF TIMESTAMP2 > MAX_TIME_A.<br/> MAX_TIME_A = TIMESTAMP2.<br/> ENDIF. <br/> ADD TIMESTAMP2 TO MEANTIME_A.<br/> S_RESULT-ASSIGN = TIMESTAMP2.<br/> S_RESULT-AFIRST = L_FIRSTTIME - TIMESTAMP1.<br/>ENDFORM. " UPDATE_USING_ASSIGN<br/><br/><br/>& <br/>& Form UPDATE_USING_MODIFY<br/>*& <br/> text<br/>*-- *<br/>FORM UPDATE_USING_MODIFY.<br/> DATA: L_FIRSTTIME TYPE I.<br/><br/> GET RUN TIME FIELD TIMESTAMP1.<br/> LOOP AT T_SPFLI INTO H_SPFLI.<br/> H_SPFLI-TABIX2 = SY- TABIX.<br/> MODIFY T_SPFLI FROM H_SPFLI.<br/> IF SY-TABIX = 1.<br/> GET RUN TIME FIELD L_FIRSTTIME.<br/> ENDIF.<br/> ENDLOOP.<br/> GET RUN TIME FIELD TIMESTAMP2. <br/> TIMESTAMP2 = TIMESTAMP2 - TIMESTAMP1.<br/> IF TIMESTAMP2 < MIN_TIME_M.<br/> MIN_TIME_M = TIMESTAMP2.<br/> ENDIF.<br/> IF TIMESTAMP2 > MAX_TIME_M.<br/> MAX_TIME_M = TIMESTAMP2.<br/> ENDIF.<br/> ADD TIMESTAMP2 TO MEANTIME_M.<br/> S_RESULT-MODIFY = TIMESTAMP2.<br/> S_RESULT-MFIRST = L_FIRSTTIME - TIMESTAMP1. <br/>ENDFORM. " UPDATE_USING_MODIFY Like (0) Rob Burbank Feb 9, 2006 7:50 AM (in response to Matthew Gif f ord) Hi Matthew - yes, for small amounts of data, there really is not much to be gained by using either of the techniques I've pointed out. But I'm really not sure if you can count on nested loops giving better performance. The time to do a loop is dependent on the number of records in the table. From F1 on READ: "The runtime required to read a line from a table with 100 entries using the index is around ca. 7 msn (standard microseconds), to read a line using a key of 30 bytes using a binary search, around 25 msn, and without binary search, around 100 msn." Thanks for taking the time to look at this. I appreciate it. Rob Feb 9, 2006 11:29 AM (in response to Matthew Gif f ord) RE: "James's 'Other' logic does not include the required sort." Very true. Thanks for pointing this out. I was originally using a sorted itab (no BINARY SEARCH), and neglected to add the sort when I morphed my own code to fit the blog's example. Regards, Like (0) James Like (0) Feb 14, 2006 1:05 AM bkpf_lin like line of bkpf_tab: IS this the efficient way of declaring internal table Like (0) Rob Burbank Feb 15, 2006 7:42 AM (in response to ) I don't know if it's more efficient, but according to "Obsolete ABAP Language Constructs", the use of OCCURS when defining an internal table is obsolete. https://www.sdn.sap.com/irj/servlet/prt/portal/prtroot/docs/library/uuid/5ac31178-0701-0010- 469a-b4d7fa2721ca Rob Like (0) Naimish Dayah Feb 14, 2006 4:09 AM Can you also provide the code for parallel cursor method please Like (0) Rob Burbank Feb 15, 2006 7:39 AM (in response to Naimish Dayah) My mistake - I have added the code. Rob Like (0) Arun Sambargi Apr 11, 2006 11:34 PM Hi Rob, As a beginner I was really confused with performance of various Loop options, I have a clear picture about these now. Like (0) Rob Burbank Apr 12, 2006 6:21 AM (in response to Arun Sambargi) You're welcome Arun. I'm glad it helped. Rob Like (0) Daniel Grau Jun 26, 2007 3:12 AM Hi all, found this and played a little with the coding. Why not just replace in the data defenition: bseg_tab type standard table of bseg, by bseg_tab type sorted table of bseg with non- non-unique key bukrs belnr gjahr buzei, (or unique key)? Result in a test with around 400k entries was near the indexed loop. Greetings, Daniel Rob Burbank Jun 27, 2007 10:52 AM (in response to Daniel Grau) Yes - that's a good point. There are a number of different techniques that can be used instead of the one I presented here. I like to use standard tables because I tend to re-use and re-sort them. But the main thrust of the blog isn't how to fix the problem, it's recogizing the problem in the first place. Many questions in the forum ask "How can I improve the performace of this code...?" The code will have a bunch of selects and then one or more nested loops. Most of the responses try to improve the selects only and ignore the nested loops. A lot of people just don't see this as a problem. Rob Follow SCN Site Index Contact Us SAP Help Portal Privacy Terms of Use Legal Disclosure Copyright Like (0)