Fastest "1 TO x" loop when x is not a constant

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