It's ~30% faster to use "DO i = x TO 1 BY -1" than the closest "DO i = 1 TO x", even when x is calculated before the loop, unless, of course, the value of x changes in the loop and you want to change the current number of loops based of it.
This result is probably because, as documentation states,
variable = expression1 TO expression2 [ BY k ]
The expression2 is re-evaluated on each iteration of the block.
and that it is faster to evaluate a constant than even a variable.
Sample code:
&SCOPED-DEFINE NumLoops 100000
DEFINE VARIABLE i AS INTEGER NO-UNDO.
DEFINE VARIABLE iEmptyLoopTime AS INTEGER NO-UNDO.
DEFINE VARIABLE iMethodTime AS INTEGER NO-UNDO.
DEFINE VARIABLE iStringToTestIndex AS INTEGER NO-UNDO.
DEFINE VARIABLE cStringToTestList AS CHARACTER NO-UNDO INITIAL "123456789|sadfgilshdfgljshdfgklsjdhfg|sdgffffajksdghakjghakdfjghaajksdghakjghakdfjghasdjghasdkfasdkjajksdghakjghakdfjghasdjghasdkfasdkjajksdghakjghakdfjghasdjghasdkfasdkjajksdghakjghakdfjghasdjghasdkfasdkjajksdghakjghakdfjghasdjghasdkfasdkjsdjghasdkfasdkjfgasdkgasdgasdfjkghasdkfjghasdfkjghasdfhgasdfh".
DEFINE VARIABLE cStringToTest AS CHARACTER NO-UNDO.
DEFINE VARIABLE cResults AS CHARACTER NO-UNDO INITIAL "--Results--~n".
DEFINE VARIABLE iNumStringsToTest AS INTEGER NO-UNDO.
DEFINE VARIABLE iLength AS INTEGER NO-UNDO.
DEFINE VARIABLE iCharIndex AS INTEGER NO-UNDO.
ETIME(TRUE).
DO i = 1 TO {&NumLoops}:
END.
iEmptyLoopTime = ETIME.
iNumStringsToTest = NUM-ENTRIES(cStringToTestList, "|").
DO iStringToTestIndex = 1 TO iNumStringsToTest:
ASSIGN
cStringToTest = ENTRY(iStringToTestIndex, cStringToTestList, "|")
cResults = cResults + "~n~nString:" + cStringToTest
.
ETIME(TRUE).
DO i = 1 TO {&NumLoops}: /* Method 1 */
iLength = LENGTH(cStringToTest).
DO iCharIndex = 1 TO iLength:
END.
END.
iMethodTime = ETIME.
cResults = cResults + "~nMethod 1:" + STRING(iMethodTime - iEmptyLoopTime).
ETIME(TRUE).
DO i = 1 TO {&NumLoops}: /* Method 2 */
DO iCharIndex = 1 TO LENGTH(cStringToTest):
END.
END.
iMethodTime = ETIME.
cResults = cResults + "~nMethod 2:" + STRING(iMethodTime - iEmptyLoopTime).
ETIME(TRUE).
DO i = 1 TO {&NumLoops}: /* Method 2 */
DO iCharIndex = LENGTH(cStringToTest) TO 1 BY -1:
END.
END.
iMethodTime = ETIME.
cResults = cResults + "~nMethod 3:" + STRING(iMethodTime - iEmptyLoopTime).
ETIME(TRUE).
DO i = 1 TO {&NumLoops}: /* Method 1 */
iLength = LENGTH(cStringToTest).
DO iCharIndex = iLength TO 1 BY -1:
END.
END.
iMethodTime = ETIME.
cResults = cResults + "~nMethod 4:" + STRING(iMethodTime - iEmptyLoopTime).
END. /* DO iStringToTestIndex = 1 TO iNumStringsToTest */
MESSAGE cResults VIEW-AS ALERT-BOX.
Sample results on my machine:
--Results--
String:123456789
Method 1:289
Method 2:319
Method 3:206
Method 4:218
String:sadfgilshdfgljshdfgklsjdhfg
Method 1:679
Method 2:800
Method 3:476
Method 4:491
String:sdgffffajksdghakjghakdfjghaajksdghakjghakdfjghasdjghasdkfasdkjajksdghakjghakdfjghasdjghasdkfasdkjajksdghakjghakdfjghasdjghasdkfasdkjajksdghakjghakdfjghasdjghasdkfasdkjajksdghakjghakdfjghasdjghasdkfasdkjsdjghasdkfasdkjfgasdkgasdgasdfjkghasdkfjghasdfkjghasdfhgasdfh
Method 1:5740
Method 2:7655
Method 3:3953
Method 4:3996