Advanced break and continue Statements
There’s more to AS3’s break
and continue
statements than you might think. Chances are, you’ve used them to skip to after a loop (break
) and skip the current loop iteration (continue
), but they can do so much more. Today’s article will cover some of the advanced ways to use the break
and continue
statements in AS3 resulting in nicer—and maybe even faster—code.
Perhaps the most useful advanced usage of the break
and continue
statements is to break out of a nested loop. Consider this search for a value in a two-dimensional Vector
:
function search(numbers:Vector.<Vector.<int>>, target:int): void { var found:Boolean; outerLoop: for each (var row:Vector.<int> in numbers) { for each (var val:int in row) { if (val == target) { found = true; break outerLoop; } } } if (!found) { trace(target + " not found!"); } }
The key here is that we put outerLoop:
before the outer loop and then, if we find the target value, we can specify the loop to break out of by not just saying break;
, but instead by saying break outerLoop;
. Just saying break;
would only break out of the inner loop and we would continue needlessly searching the rest of the rows. For big Vectors
, this can save a lot of CPU time!
To see how we would use continue
in this way, consider an example where the values of a three-dimensional Vector
are sorted by row and column in ascending order and we want to count the number of elements in the Vector
that are less than some value threshold
:
function lessThan(numbers:Vector.<Vector.<Vector.<int>>>, threshold:int): int { var count:int; for each (var layer:Vector.<Vector.<int>> in numbers) { outerLoop: for each (var row:Vector.<int> in layer) { for each (var val:int in row) { if (val >= threshold) { // the rest of this 2D grid must be greater too // skip this 2D grid but keep checking subsequent 2D grids continue outerLoop; } count++; } } } return count; }
These uses of break
and continue
are not strictly necessary, but if we didn’t have them we would be forced to add Boolean
flags to our loop conditions. These flags would slow down the loop by adding needless checking, not to mention the extra typing, stack size, and SWF bloat that would result when multiplied across all the loops where a tidy break label;
could have been applied.
While the above is very useful, it is not the only advanced usage of break
and continue
in AS3. Labels allow us to break
or continue non-loop blocks too. Consider this case with a plain “curly braces-only” block:
trace("pre"); block: { trace("in block"); break block; trace("after break"); } trace("post"); // OUTPUT: // pre // in block // post
This example illustrates that if we are willing to add a label to the block, we can name it when we use break
or continue
. Of what use is this outside of contrived examples? Well, consider the following function that performs cumulative actions as more if
statements are passed:
function atLeast(x:int): void { trace("--begin " + x + "--"); end: { if (x < 1) break end; trace("\tat least 1"); if (x < 2) break end; trace("\tat least 2"); if (x < 3) break end; trace("\tat least 3"); } trace("--end--"); }
The requirements are that we always print the “begin” and “end” lines as well as the appropriate “at least…” lines when x
is greater than 1, 2, or 3. We can implement this by simply typing out the three “at least…” lines and then adding cutoff points between them. How do we cut the printing off and jump to the “end” line? We use a named block (end
), of course!.
Before you think that this is yet another contrived example, consider how you would implement this without the named block/break
to label functionality. We can’t simply use return
to end the function when you want to cut off the printing because that would skip printing the “end” line. We could decide to duplicate the “end” line printing in the cutoff detection if
, but then we have to duplicate a bunch of code like this:
function atLeastEndDupe(x:int): void { trace("--begin " + x + "--"); if (x < 1) { trace("--end--"); return; } trace("\tat least 1"); if (x < 2) { trace("--end--"); return; } trace("\tat least 2"); if (x < 3) { trace("--end--"); return; } trace("\tat least 3"); trace("--end--"); }
What a mess! If we had more lines of code at the end (e.g. multiple lines to print), we would have even more to duplicate in each and every cutoff. Another way we could implement this would be to duplicate the “at least…” lines like this:
function atLeastBodyDupe(x:int): void { trace("--begin " + x + "--"); if (x >= 3) { trace("\tat least 1"); trace("\tat least 2"); trace("\tat least 3"); } else if (x >= 2) { trace("\tat least 1"); trace("\tat least 2"); } else if (x >= 1) { trace("\tat least 1"); } trace("--end--"); }
This version is even longer! Yes, we’ve avoided duplicating the end statements, but now we have to duplicate all the middle statements. It would have been so much easier to just use a named break
statement.
While named break
statements are clearly better in terms of typing, maintenance, and (arguably) readability, we should investigate their performance to make sure we’re not inadvertently harming our app in that arena. Who knows, maybe we’ll end up with a performance gain. Here’s a performance-testing app that tests out the above three methods with some minor modifications:
trace
statements (“begin”, “end”, “at least…”) replaced by simpleint
setting- Replaced
return
withcontinue
in the inline version ofatLeastEndDupe
. Ironically, a named block would have been necessary if this wasn’t being done in a loop!
package { import flash.display.*; import flash.utils.*; import flash.text.*; public class LabelsTest extends Sprite { private var __logger:TextField = new TextField(); private function log(msg:*): void { __logger.appendText(msg + "\n"); } public function LabelsTest() { __logger.autoSize = TextFieldAutoSize.LEFT; addChild(__logger); var beforeTime:int; var afterTime:int; const REPS:int = 100000000; var x:int; var y:int; log("Method,Time"); beforeTime = getTimer(); for (x = 0; x < REPS; ++x) { atLeast(x); } afterTime = getTimer(); log("Labels," + (afterTime-beforeTime)); beforeTime = getTimer(); for (x = 0; x < REPS; ++x) { atLeastEndDupe(x); } afterTime = getTimer(); log("End Dupe," + (afterTime-beforeTime)); beforeTime = getTimer(); for (x = 0; x < REPS; ++x) { atLeastBodyDupe(x); } afterTime = getTimer(); log("Body Dupe," + (afterTime-beforeTime)); beforeTime = getTimer(); for (x = 0; x < REPS; ++x) { y = 0; end: { if (x < 1) break end; y = 1; if (x < 2) break end; y = 2; if (x < 3) break end; y = 3; } y = 4; } afterTime = getTimer(); log("Labels (inline)," + (afterTime-beforeTime)); beforeTime = getTimer(); for (x = 0; x < REPS; ++x) { y = 0; if (x < 1) { y = 4; continue; } y = 1; if (x < 2) { y = 4; continue; } y = 2; if (x < 3) { y = 4; continue; } y = 3; y = 4; } afterTime = getTimer(); log("End Dupe (inline)," + (afterTime-beforeTime)); beforeTime = getTimer(); for (x = 0; x < REPS; ++x) { y = 0; if (x >= 3) { y = 1; y = 2; y = 3; } else if (x >= 2) { y = 1; y = 2; } else if (x >= 1) { y = 1; } y = 4; } afterTime = getTimer(); log("Body Dupe (inline)," + (afterTime-beforeTime)); } private function atLeastBodyDupe(x:int): void { var y:int; y = 0; if (x >= 3) { y = 1; y = 2; y = 3; } else if (x >= 2) { y = 1; y = 2; } else if (x >= 1) { y = 1; } y = 4; } private function atLeastEndDupe(x:int): void { var y:int; y = 0; if (x < 1) { y = 4; return; } y = 1; if (x < 2) { y = 4; return; } y = 2; if (x < 3) { y = 4; return; } y = 3; y = 4; } private function atLeast(x:int): void { var y:int; y = 0; end: { if (x < 1) break end; y = 1; if (x < 2) break end; y = 2; if (x < 3) break end; y = 3; } y = 4; } } }
I ran the above performance test on this environment:
- Flex SDK (MXMLC) 4.1.0.16076, compiling in release mode (no debugging or verbose stack traces)
- Release version of Flash Player 10.3.181.14
- 2.4 Ghz Intel Core i5
- Mac OS X 10.6.7
And got these results:
Method | Time |
---|---|
Labels | 744 |
End Dupe | 741 |
Body Dupe | 727 |
Labels (inline) | 214 |
End Dupe (inline) | 202 |
Body Dupe (inline) | 183 |
Here is the above table in chart form:
For all its merits, the labeled break
is unfortunately slower than the alternatives by 2.3% in the function case and a whopping 17% in the inline case. You will have to decide if the performance tradeoff is worth it, but it probably is when performance isn’t critical.
Know of any other advanced uses for break
and continue
? Post a comment below!
#1 by Henke37 on June 6th, 2011 ·
Are you sure they are operators? I could have sworn that they are statements.
#2 by jackson on June 6th, 2011 ·
You’re correct. I’ve updated the article.
#3 by Jason on June 20th, 2011 ·
Great article, some things I never knew you could do with AS3! Wow – pointers back to loops and code blocks? Interesting.
Would be nice to see some comparisions with switch statements too. I know not all your examples would apply to a switch statement, but it would be good to compare against or talk about any advanced things you could do with those, since you can break out of those too.
Anyway thanks for the great post.
#4 by lyua on June 22nd, 2011 ·
Hi Jackson, thanks for article!
Though there are several ways to avoid labels, assume it’s not a big deal. Do you think they work slowly?
#5 by lyua on June 22nd, 2011 ·
oops, two last seems to read:)
#6 by jackson on June 22nd, 2011 ·
Hey Iyua. I’m not sure what your question is because I don’t see any labels being used. Could you clarify?
#7 by lyua on June 23rd, 2011 ·
yes, I meant that this task may be done w/o labels using, and it is just interesting is this faster/slower in compare with ‘labeled’ variants?
#8 by jackson on June 23rd, 2011 ·
Ah, I see now. These are ways of doing the “at least” work, but they’re much more restrictive than the “labeled” version. Consider a slightly more complex “labeled” alternate function:
So the outputs are:
Try writing a loop version of the above function. The point is that the loop doesn’t allow you to change what you do each time, other than things you can do given the numeric value of the loop iterator.
The loop is also restrictive in that it makes it harder to step unevenly. With these labeled variants, you could easily set the “at least” values to 1, 32, and 511 if you wanted. With a loop, it’d be much more complex and/or slow.
As for speed, I haven’t tested the above because, as shown, they’re not quite equivalent. I would guess, however, that the labeled versions will be much quicker as they involve much less overhead in the forms of loop iterators (initialization, incrementing), extra logical conditions (version A has two clauses with an
&&
), etc.Happy labeling!
#9 by lyua on June 24th, 2011 ·
yep, I agree with you that loops make these examples slightly complexly, but without labels it more beauty from procedural point of view :) From the speed perspective… I tried to write these examples, and they obviously look slower (at least from amount of actions, as you correct mentioned):
Anyway, thanks a lot for interesting cases with label’s using!
#10 by jackson on June 24th, 2011 ·
That is definitely a creative way to solve the problem! The untyped/dynamic lookup of the function will be slow, as will the function call itself. For the tickets version, a
String
object is created, which is not only slow but involves allocation and (later) garbage collection. The ranged version has twoArray
allocations (with accompanying garbage collection) as well as twoArray
accesses per iteration of the loop, which are rather slow themselves. So yes, from a speed perspective this version will be wildly slower.Is it better from a “beauty” standpoint? That’s up to the individual programmer. I would be concerned about the untyped
Array
access, the dynamic field access, and the untyped function call. All three are error prone as the compiler will not tell you about potential problems. For example, if you renameaction_3
toaction3
, the compiler will not catch the problem until you actually try to call a now non-existent function. Suffice to say, you know where my preference lies.Thanks for the interesting alternatives. At the very least, it’s a fun thought experiment. :)
#11 by lyua on June 27th, 2011 ·
I completely agree with you, labels go faster.
btw, for untyped access…I just check, if I’am renaming
action_3
to something else, FB4 throws me #1120 error on compile. And to to avoid untyped:So, if you stand on strict ‘procedural’ style, AS3 has enough approaches to achieve this simple ;)
Thanks!
#12 by lyua on June 27th, 2011 ·
#13 by Deranged on June 28th, 2013 ·
I felt it was really necessary to say THANK YOU for this post. This just solved a huge problem I was having in my code. So…
Thank you!!! :)
#14 by jackson on June 28th, 2013 ·
Awesome. Happy to help!