IF AND/OR THEN versus IF THEN IF – part 2

See also: part 1 and part 2.

Updates:

  • 2022-08-03 – Added note about “THEN ELSE” per a comment left by William Astle.

Please see the first part to understand what this is about, and why I blame Robin at 8-Bit Show and Tell for leading me down this rabbit hole.


This week, JohnD over at the CoCo Discord chat server mentioned that the Getting Started with Extended Color BASIC manual actually used “THEN IF” in an example. Indeed, on page 190 you find this example for the POS function:

THEN IF in Getting Started with Extended Color BASIC, page 190.

Meanwhile on Twitter, FUED responded to this as follows:

I still think IF THEN X and having the second condition in another X line number with its own single IF to be the fastest for action, otherwise, the IF THEN IF will save some memory if speed is not required. If speed is a must, IFs should be avoided and ON GOTO be used instead.

– @FUED_hq on Twitter

This, of course, made me want to run some benchmarks.

Skip to the end, my darling.

When Color BASIC begins parsing a line, it has to continue parsing every byte of that line even if it determines the rest of the line does not need to be executed. This means line like this are very slow when the condition is not met:

IF A=42 THEN a really long line of other stuff here

If BASIC determines that A is not 42, it still has to scan the rest of the line just in case there is an ELSE there. Even if there is not, it still has to keep scanning to know where the line ends so it can find the next line. Color BASIC do skip line data when scanning forward (i.e., GOTO xxx) — each line entry has a line number and length of that line — but it does not remember any of this once it decides it needs to parse the current line.

In part 1 I demonstrated how using “IF this AND that THEN” could be made faster by using “IF this THEN if that THEN”:

IF A=1 AND B=2 AND C=3 THEN this is slower

IF A=1 THEN IF B=2 THEN IF C=3 THEN this is faster

This is because BASIC no longer needs to do the “logical/mathematical” AND comparisons and retain the results as it moves through the line. It can just start skipping forward looking for an ELSE or end of line.

BUT, no matter what, it still has to scan the rest of the line. As FUED points out, shorter lines could be faster. Here are two examples:

30 IF A=1 THEN IF A=2 THEN IF A=3 THEN PRINT

30 IF A=1 THEN 40 ELSE 60
40 IF A=2 THEN 50 ELSE 60
50 IF A=3 THEN PRINT
60 ...

In the first version, no matter if A is “1” or “not 1”, it still will have to scan the rest of the line.

In the second version, if A is not 1 it will scan to the end of the line then start skipping lines as it looks for line 60 without needing to scan through any of the lines it is skipping.

Depending on what is faster — scanning to the end of a longer line, versus scanning a short line and skipping lines — this may be a simple way to make things faster.

Benchmark, anyone?

Here is a simple benchmark:

10 TIMER=0
20 FOR I=1 TO 1000

30 IF A=1 THEN IF A=2 THEN IF A=3 THEN PRINT

60 NEXT:PRINT TIMER,TIMER/60

Running that prints 324.

Now we try a version with short lines that will just skip any lines it doesn’t need to run:

10 TIMER=0
20 FOR I=1 TO 1000

30 IF A=1 THEN 40 ELSE 60
40 IF A=2 THEN 50 ELSE 60
50 IF A=3 THEN PRINT

60 NEXT:PRINT TIMER,TIMER/60

Running that prints 330 – just a tad slower.

This means that the overhead of skipping those lines is just a tad more than scanning to the end of one longer line.Scanning forward looking for ELSE or end of line must take less work than looking at line number entries and skipping ahead. Bummer.

But is that why it’s a tad slower? I think the main thing is it has to convert the line numbers (“60” in this case) from those two ASCII characters to BASIC’s floating point representation of them. That is probably way more overhead than just skipping bytes looking for an ELSE token.

To test, I reversed the logic to reduce the number of numbers we have to convert:

10 TIMER=0
20 FOR I=1 TO 1000

30 IF A<>1 THEN 60
40 IF A<>2 THEN 60
50 IF A=3 THEN PRINT

60 NEXT:PRINT TIMER,TIMER/60

This version gives me 315 – faster than the original! And, it’s smaller code, trading an “=” and “ELSE 60” for “<>”.

This means the real thing to consider is: Which takes more time? Scanning to the end of a long line, converting bytes in to a number, or skipping lines?

This is something that could be benchmarked and then we could predict which is better to use.

But for now, I’ll just leave this here for additional comments from readers who know more about how this works than I do.

UPDATE: In a comment left by William Astle, he mentioned you could also do “IF A=1 THEN ELSE 60” as valid Syntax. This effectively creates something similar to how C might do:

if (a == 1)
{
   // Nothing to do
}
else
{
   // Something to do
}

Looking at that in BASIC, that makes sense, though I’ve never seen it done and never used it like that. So let’s add this to the mix, going back to the version using “=” and original logic:

0 'THENBENCH.BAS
10 TIMER=0
20 FOR A=1 TO 1000

30 IF A=1 THEN ELSE 60
40 IF A=2 THEN ELSE 60
50 IF A=3 THEN PRINT

60 NEXT:PRINT TIMER,TIMER/60

This gives me 315, basically matching the “<>” version without THEN. Thus, these seem comparable:

IF A<>1 THEN 60

IF A=1 THEN ELSE 60

I expect it’s just parsing a byte for the ELSE token, versus a byte for the extra character (“>”) being similar in speed. And speaking of speed, removing the spaces in those IF lines reduces it to 310, versus 307 for the “<>” version. I think this is because the “THEN ELSE” started out with four spaces versus the “<>” version only having three.

For better benchmarks, testing the code itself, all spaces should be removed, but I usually don’t do that, just for readability in these articles.

Until next time…

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.