The CASE for less IFs (or maybe NOT!)
Not sure if this is widely known, but I thought I'd share it anyway:
There are times when we might want to see (for instance) which command we've received. In such times, I generally opted for:
Nothing wrong with that! However, the Propeller has to do every IF test, even if the command has already been acted upon.
I've now found that it's possible to do:
Notice the case 1 == 1? This allows us to use a "case" structure, which means the Propeller jumps out of the structure as soon as the command has been acted upon (I believe!).
Even if I'm wrong about that, I think it looks "cleaner" anyway!
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Cheers,
Simon
www.norfolkhelicopterclub.com
“Before you criticize someone, you should walk a mile in their shoes. That way when you criticize them, you are a mile away from them and you have their shoes.” - Jack Handey.
Post Edited (simonl) : 5/5/2009 11:44:03 PM GMT
There are times when we might want to see (for instance) which command we've received. In such times, I generally opted for:
if strcomp( string( "cls" ), @cmd[noparse][[/noparse] 0 ] ):
doCmdCLS
done := 1
if strcomp( string( "hello" ), @cmd[noparse][[/noparse] 0 ] ):
doCmdHello
done := 1
if strcomp( string( "toggle" ), @cmd[noparse][[/noparse] 0 ] ):
doCmdToggle( @cmd[noparse][[/noparse] parStart ] )
done := 1
if strcomp( string( "clear" ), @cmd[noparse][[/noparse] 0 ] ):
doCmdClear
done := 1
if strcomp( string( "Flash LED" ), @cmd[noparse][[/noparse] 0 ] ):
FlashLED
done := 1
' -------------------------------------------------
' Catch invalid commands.
' -------------------------------------------------
if( done <> 1 )
ser.str( string( "Invalid command!") )
done := 0
Nothing wrong with that! However, the Propeller has to do every IF test, even if the command has already been acted upon.
I've now found that it's possible to do:
case 1 == 1
strcomp( string( "cls" ), @cmd[noparse][[/noparse] 0 ] ):
doCmdCLS
strcomp( string( "hello" ), @cmd[noparse][[/noparse] 0 ] ):
doCmdHello
strcomp( string( "toggle" ), @cmd[noparse][[/noparse] 0 ] ):
doCmdToggle( @cmd[noparse][[/noparse] parStart ] )
strcomp( string( "clear" ), @cmd[noparse][[/noparse] 0 ] ):
doCmdClear
strcomp( string( "Flash LED" ), @cmd[noparse][[/noparse] 0 ] ):
FlashLED
' -------------------------------------------------
' Catch invalid commands.
' -------------------------------------------------
other:
ser.str( string( "Invalid command!") )
Notice the case 1 == 1? This allows us to use a "case" structure, which means the Propeller jumps out of the structure as soon as the command has been acted upon (I believe!).
Even if I'm wrong about that, I think it looks "cleaner" anyway!
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Cheers,
Simon
www.norfolkhelicopterclub.com
“Before you criticize someone, you should walk a mile in their shoes. That way when you criticize them, you are a mile away from them and you have their shoes.” - Jack Handey.
Post Edited (simonl) : 5/5/2009 11:44:03 PM GMT

Comments
Wow.. Nice find.. Can someone verify this?
OBC
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
New to the Propeller?
Visit the: The Propeller Pages @ Warranty Void.
But you can simply write
case -1
instead of 1==1. Which simply is the same. 1==1 is simply a boolean expresion which will be resolved during runtime (needs runtime of course). True is represented as -1, false as 0. Strcomp also returns a boolean true or false.
But this also ends in a bunch of string-compares if the command is at the end of the list. For many commands I prefere the solution where you create a hash value of the command string and use the case statement to compare numbers. This is much faster and you don't need to store the·text of all the commands. One long/word per command is enough.
Post Edited (MagIO2) : 5/5/2009 9:51:54 PM GMT
I say 60% because (as you'll see if you run my attached codes) IF is slower than CASE some of the time.
The two attached codes are identical except for the CASE and IF structures. They're code for some testing I'm doing with serial comm's, and accept the following commands:
1. cls
2. hello
3. toggle <pinNumber>
4. clear
5. flash
Operation:
* Send to Propeller
* Start PST
* Hit enter
* Type command & hit enter
Here are the counter readings that I got:
Strangley "flash" and "claer" (intentionally wrong!) sometimes give very different readings, and I can't figure-out why!
I'm not sure what to make of this now - LOL.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Cheers,
Simon
www.norfolkhelicopterclub.com
“Before you criticize someone, you should walk a mile in their shoes. That way when you criticize them, you are a mile away from them and you have their shoes.” - Jack Handey.
Post Edited (simonl) : 5/5/2009 10:26:34 PM GMT
I'm not sure I follow what you're saying there! Would you give an example?
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Cheers,
Simon
www.norfolkhelicopterclub.com
“Before you criticize someone, you should walk a mile in their shoes. That way when you criticize them, you are a mile away from them and you have their shoes.” - Jack Handey.
Ultimately, this is a data structures problem that has been studied immensely. Personally, I'd prefer binary trees (O(n log(n)) if I remember right) since they can be expanded easier, and don't take as much memory as hash tables. With this small amount of commands though, neither would be any significant improvement over simply listing them as you have done. It might be worth looking into though if you want lot's of commands (>100 maybe).
if strcomp( string( "cls" ), @cmd[noparse][[/noparse] 0 ] ): doCmdCLS done := 1 [b]elseif[/b] strcomp( string( "hello" ), @cmd[noparse][[/noparse] 0 ] ): doCmdHello done := 1 [b]elseif[/b] strcomp( string( "toggle" ), @cmd[noparse][[/noparse] 0 ] ): doCmdToggle( @cmd[noparse][[/noparse] parStart ] ) done := 1 [i]etc.[/i]You have revealed an interesting aspect of spin. Thanks for that! I never new a case of CASE could be an expression ... of course that means it could be a variable too. CASE implementation is kind of freaky though and actually uses more code bytes than IF ELSEIF ELSE.
Cheers.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
--Steve
Propalyzer: Propeller PC Logic Analyzer
http://forums.parallax.com/showthread.php?p=788230
Just for a laugh (hey, it's 0:23 here in UK!) I re-coded using elseif, and the readings are as follows:
ELSEIF IF DIFF CASE DIFF* cls 92320 -5328 0 hello 4042304 -4048 -144 toggle 17 2026128 -2912 +1296 clear 7392 -1760 -448 flash 2500288 +2493376 +1008 <CR> 1468944 -838 -1168 claer 1470800 +1463632 +432 * Compared with timings using "CASE -1"So, again, some bizarre results! And again, CASE is often quicker, but I still don't understand why.
@SLRM: Thanks for the info
@localroger: Interesting findings. I like your use of RETURN - another option I'd not considered. I'm not sure if what I've shown with this CASE trick is so clever though - LOL
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Cheers,
Simon
www.norfolkhelicopterclub.com
“Before you criticize someone, you should walk a mile in their shoes. That way when you criticize them, you are a mile away from them and you have their shoes.” - Jack Handey.
Strange; I looked at program usage and found:
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Cheers,
Simon
www.norfolkhelicopterclub.com
“Before you criticize someone, you should walk a mile in their shoes. That way when you criticize them, you are a mile away from them and you have their shoes.” - Jack Handey.
Post Edited (simonl) : 5/5/2009 11:48:48 PM GMT
Now I really must get some shut-eye - it's 01:16 aaargh!
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Cheers,
Simon
www.norfolkhelicopterclub.com
“Before you criticize someone, you should walk a mile in their shoes. That way when you criticize them, you are a mile away from them and you have their shoes.” - Jack Handey.
Limited to 16? Uuugh.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
--Steve
Propalyzer: Propeller PC Logic Analyzer
http://forums.parallax.com/showthread.php?p=788230
note the addition to Keyboard.spin.
I don't think execution speed is a an issue for keyboard input, I prefer clarity and utility unless I run out of storage.
!!!Not all the pins are the standard ones!
{{ Hash Code Command Interpreter by Bill Drummond This program is a demonstration of using hash codes to parse keyboard input into commands. Using hash codes makes the case statement easy to use and to my eye code that's easier to follow than 'if else' code, especially since SPIN is not very string friendly. Note that I coded this without needing a DAT statement. Entering a uncoded Command will output "Hashcode 'Unrecognized Command'", you can then use that hash code for adding your new command. The IDE won't let you use the slash to define a constant, but the actual name doesn't matter as long as you assign the hash code for the command you wish to enter. ex: DIR_W for DIR/W [s]The Windows Program HashPrj.exe will output the Constants to a text file that you can cut and paste for the commands you wish to use.[/s] I've included code for 1 or 2 argument strings. Some of the code was borrowed from PropCMD by Michelli Scales ++++++++++++++++added this to Keyboard.spin+++++++++++++++++ PUB LookNext : KeyCode '' LookNext (never waits) '' returns key and leaves it in the buffer(0 if buffer empty) if par_tail <> par_head keycode := par_keys.word[noparse][[/noparse]par_tail] ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ }} CON _clkmode = xtal1 + pll16x _xinfreq = 5_000_000 CLS = $00004813 COLOR = $00484142 COPY = $00048459 COPYSER = $0845E7E2 DEL = $0000489C DIR = $000048E2 DIR_W = $0048E547 ' this hashcode is for DIR/8 the IDE sees DIR div W DUMP = $00049A20 DUMP_S = $049A2343 ' this hashcode is for DUMP/S GETSER = $04BA97A2 HELP = $0004CA10 KEYCODE = $0FAD83C5 MORE = $00052465 MOUNT = $00524A34 SEROUT = $057A74A4 TYPE = $00059E45 TYPE_S = $059E4843 UNMOUNT = $0A324A64 RENAME = $056A2615 'GETPINS = $0BA94E73 bkspKey = $C8 escKey = $CB ctlcKey = $0263 ctlzKey = $027A F1 = $D0 LCDRows = 13 BKSP = $08 LF = $0A CR = $0D CTLZ = $1A ESC = $1B SPC = $20 SDBase = 8 LCDBase = 12 RXPin = 31 TXPin = 30 KbData = 26 KbClock = 27 OBJ lcd : "tv_text" fdx : "FullDuplexSerial" kb : "keyboard" SDCard : "fsrw" VAR byte HashStr[noparse][[/noparse]36] byte Arg1[noparse][[/noparse]14] byte Arg2[noparse][[/noparse]13] byte HasArgs byte Has2Args byte Fnames[noparse][[/noparse]13] byte FBuf[noparse][[/noparse]160] byte Alt[noparse][[/noparse]2] byte Lcount long hc 'Pins byte LCD_Base byte SD_Base byte Kb_Data byte Kb_Clock byte RX_Pin byte TX_Pin PUB start | i SD_Base := 8 ''lcd.str(string("Mounting SD Card.", CR)) SDCard.mount(SD_Base) ''lcd.str(string("Success.", CR)) 'start the tv lcdinal if getpinFile lcd.str(string("Pins Loaded From File",CR)) '' Alternate Cursor Chars Alt[noparse][[/noparse]0] := SPC Alt[noparse][[/noparse]1] := $5F MainLoop PRI MainLoop | ch Lcount := 0 HashStr[noparse][[/noparse]0] := 0 repeat lcd.str(string($B4,$07)) ''Prompt string blink DoCmd(Hash(getCmd)) PRI DoCmd(Cmd) | r ' Parse Commands case Cmd CLS : lcd.out(0) COLOR: if HasArgs GetArg1 lcd.out($C) lcd.out(Arg1[noparse][[/noparse]0]-$30) COPY: CopyCmd COPYSER: CopySerialCmd DEL: if HasArgs GetArg1 r := SDCard.popen(@Arg1,"d") DirCmd DIR: DirCmd DIR_W : DirWideCmd ' enter DIR/8 DUMP : DumpCmd DUMP_S: DumpSerialCmd ' enter DUMP/S GETSER: GetSerCmd 'GETPINS: getpinFile HELP,$3F: HelpCmd '$3f = "?" KEYCODE: KeyCodeCmd MORE: TypeCmd MOUNT: SDCard.Mount(SD_Base) TYPE: TypeCmd TYPE_S,SEROUT: SerialOutCmd RENAME: lcd.str(string(" = RENAME Not Yet coded",CR)) OTHER : lcd.out("$") lcd.Hex(Cmd,8) lcd.str(string(" = Unrecognized Command",CR)) PRI GetPinFile : result | ch,i,j ch := SDCard.popen(String("PINS.TXT"), "r") if ch < 0 LCD_Base := LCDBase RX_Pin := RXPin TX_Pin := TXPin Kb_Data := KbData Kb_Clock := KbClock result := 0 else result := true HashStr[noparse][[/noparse]0] := 0 j := 1 Repeat until StrComp(String("END"),@arg1) i := 0 ch := 0 repeat until ch == Lf ch := SDCard.PGetC if (ch <> $0D)and (ch <> $0A) 'Lcd.Hex(ch,3) HashStr[noparse][[/noparse]i++] := ch HashStr[noparse][[/noparse]i] := 0 GetArg1 GetArg2 'lcd.str(@arg1) 'lcd.str(string(" = ")) 'lcd.hex(numfromarg(@arg2),2) 'Lcd.Out(Cr) if StrComp(@Arg1,String("LCD_Base")) Lcd_Base := numfromarg(@arg2) if StrComp(@Arg1,String("RX_Pin")) RX_Pin := numfromarg(@arg2) if StrComp(@Arg1,String("TX_Pin")) TX_Pin := numfromarg(@arg2) if StrComp(@Arg1,String("Kb_Data")) Kb_Data := numfromarg(@arg2) if StrComp(@Arg1,String("Kb_Clk")) Kb_Clock := numfromarg(@arg2) lcd.start(LCD_Base) 'Start the Serial fdx.start(RX_Pin,TX_Pin,0,9600) ' Rx,Tx, Mode, Baud fdx.str(String("Serial test ")) 'start the keyboard kb.start(Kb_Data, Kb_Clock) lcd.str(string("Hash Code Command Interpeter",CR)) lcd.str(string(" by Bill Drummond",CR)) {Command [noparse][[/noparse]Arg1 Arg2] } PRI GetCmd : Str_Ptr| i,ch i := 0 repeat until ch == CR blink if KB.looknext == F1+2 ''Added this to "keyboard.spin" kb.getkey i := StrSize(@hashStr) lcd.str(@hashStr) blink else ch := UpperCase(kb.getkey) if ch == $C8 'KB for backspace ch := BKSP ' TV_Text wants $08 lcd.out(ch) ' send BS lcd.out(SPC) ' untype prev ch i-- ' backup index lcd.out(ch) ' backover space if ch <> BKSP ' ignore BS char HashStr[noparse][[/noparse]i++] := ch HashStr[noparse][[/noparse]i-1] := 0 'lcd.str(@hashStr) return @HashStr PRI GetArg1 : Str_Ptr | j,i i := 0 j := 0 Has2Args := 0 repeat until (HashStr[noparse][[/noparse]i] == SPC ) i++ i ++ repeat until HashStr[noparse][[/noparse]i] == 0 Arg1[noparse][[/noparse]j] := HashStr[noparse][[/noparse]i] if HashStr[noparse][[/noparse]i] == SPC Has2Args := -1 quit i++ j++ Arg1[noparse][[/noparse]j--] := 0 Return @Arg1 PRI GetArg2 : Str_Ptr | j,i i := 0 j := 0 repeat until HashStr[noparse][[/noparse]i] == SPC i++ i ++ repeat until HashStr[noparse][[/noparse]i] == SPC i++ i ++ repeat until HashStr[noparse][[/noparse]i] == 0 Arg2[noparse][[/noparse]j] := HashStr[noparse][[/noparse]i] i++ j++ Arg2[noparse][[/noparse]j] := 0 Return @Arg2 {Get Hash code for command} PRI Hash(string_ptr):Result | x HasArgs := 0 repeat while (byte[noparse][[/noparse]string_ptr] <> 0 ) if byte[noparse][[/noparse]string_ptr] == SPC HasArgs := -1 quit Result := (Result << 4) + byte[noparse][[/noparse]string_ptr++] x := Result & $F0_00_00_00 if (x <> 0) Result := Result ^ (x >> 24) Result := result & (! x) PRI Blink | i 'Display Blinking Cursor, uses VAR Alt[noparse][[/noparse]2] i := 0 repeat until kb.gotkey lcd.out(Alt[noparse][[/noparse]i++]) waitcnt(10_000_000 + cnt) lcd.out(BKSP) if i == 2 i := 0 lcd.out(SPC) lcd.out(BKSP) PRI CopySerialCmd | r,ch if HasArgs GetArg1 r := SDCard.popen(@Arg1, "w") fdx.str(String(CR,"Upload File Now, Hit esc when done",CR)) fdx.rxflush repeat if fdx.rxcheck ch := fdx.rx if ch <> ESC lcd.out(ch) SDCard.pputc(ch) else r := SDCard.pClose quit fdx.str(String("Xfer Done ",CR)) lcd.str(String("Xfer Done ",CR)) PRI GetSerCmd | ch,k fdx.str(String("Serial Ready ",CR)) fdx.rxflush repeat if fdx.rxcheck ch := fdx.rx if ch <> ESC lcd.out(ch) else quit fdx.str(String("Serial Done ",CR)) lcd.str(String("Serial Done ",CR)) PRI KeyCodeCmd | ch lcd.str(string("Type a key or Esc to exit",cr)) Repeat until ch == $CB blink ch := KB.getkey lcd.out(ch) lcd.str(string(" KEYCODE is $")) lcd.hex(ch,2) lcd.out(cr) PRI CopyCmd | r,w,RCount Rcount := 160 if HasArgs GetArg1 if Has2Args GetArg2 repeat while Rcount == 160 r := SDCard.popen(@Arg1, "r") RCount := SDCard.pread(@Fbuf, 160) SDCard.pClose lcd.dec(Rcount) lcd.out(SPC) w := SDCard.popen(@Arg2, w) r := SDCard.pWrite(@Fbuf, Rcount) lcd.dec(r) SDCard.pClose PRI DirCmd | r SDCard.opendir repeat while 0 == SDCard.nextfile(@Fnames) lcd.str(@Fnames) lcd.out(CR) PRI DirWideCmd | i i := 0 SDCard.opendir repeat while 0 == SDCard.nextfile(@Fnames) lcd.str(@Fnames) case i 0 : lcd.out($0A) lcd.out(14) 1 : lcd.out($0A) lcd.out(27) 2 : lcd.out($0A) lcd.out(1) i := -1 i++ if not (i //= 3) lcd.out(CR) lcd.out(CR) PRI TypeCmd| ch lcd.out(0) lcount := 0 if HasArgs ch := SDCard.popen(GetArg1, "r") if ch < 0 lcd.str(string("File : ")) lcd.str(@arg1) lcd.str(string(" Not Found")) lcd.out(cr) return lcd.out(CR) lcd.out(0) repeat until ch < 0 ch := SDCard.pgetc Display(ch) Lcd.out(BKSP) Lcd.out(SPC) Lcd.out(CR) PRI Display(ch) if ch == cr lmore(0,"t") else if (ch <> $0A) ' because TV_Text uses line feed for cursor position. lcd.out(ch) PRI HelpCmd| ch lcd.out(0) lcount := 0 ch := SDCard.popen(String("HELP.TXT"), "r") lcd.out(CR) repeat until ch < 0 ch := SDCard.pgetc Display(ch) Lcd.out(BKSP) Lcd.out(SPC) lcd.out(CR) PRI SerialoutCmd | Ch if HasArgs ch := SDCard.popen(GetArg1, "r") fdx.tx(CR) repeat until ch < 0 ch := SDCard.pgetc fdx.tx(ch) PRI DumpCmd | ch,c, lc[noparse][[/noparse]15], adx, atend ' From DosCMD.spin ch := SDCard.popen(GetArg1, "r") if ch < 0 lcd.str(string("File : ")) lcd.str(@arg1) lcd.str(string(" Not Found: ")) lcd.out(cr) return adx := 0 atend := false repeat until atend lcd.out(spc) lcd.hex(adx,4) lcd.str(string(":")) c := 0 repeat while c < 8 ch := SDCard.pgetc if ch < 0 lcd.str(string(" ")) atend := true lc[noparse][[/noparse]c] := -1 else lcd.out(spc) lcd.hex(ch,2) lc[noparse][[/noparse]c] := ch c++ c := 0 lcd.out(spc) repeat while c < 8 ch := lc[noparse][[/noparse]c] if ch =< 1 or (ch => bksp and ch =< cr) lcd.out(46) else lcd.out(ch) c++ adx += 8 if not lmore(0,"t") return PRI DumpSerialCmd | ch,c, lc[noparse][[/noparse]15], adx, atend ' From DosCMD.spin ch := SDCard.popen(GetArg1, "r") if ch < 0 lcd.str(string("File : ")) lcd.str(@arg1) lcd.str(string(" Not Found: ")) lcd.out(cr) return fdx.tx(lf) adx := 0 atend := false repeat until atend fdx.tx(spc) fdx.hex(adx,4) fdx.str(string(":")) c := 0 repeat while c < 8 ch := SDCard.pgetc if ch < 0 fdx.str(string(" ")) atend := true lc[noparse][[/noparse]c] := -1 else fdx.tx(spc) fdx.hex(ch,2) lc[noparse][[/noparse]c] := ch c++ c := 0 fdx.tx(spc) repeat while c < 8 ch := lc[noparse][[/noparse]c] if ch =< 1 or (ch => bksp and ch =< cr) fdx.tx(46) else fdx.tx(ch) c++ adx += 8 if not lmore(0,"s") return PRI uppercase(c) : chr if lookdown(c: "a".."z") c -= SPC chr := c PRI numfromarg(s) | nbase, c nbase := 10 result := 0 repeat c := byte[noparse][[/noparse]s] s++ if c == 0 return elseif c == "$" '$=hex nbase := 16 elseif c == "#" nbase := 10 else if c => "a" c := c - constant("a"-10) elseif c => "A" c := c - constant("A"-10) elseif c => "0" c := c - "0" result := result * nbase + c PRI lmore(nocr,d) | c ''borrowed from DosCmd result := true if nocr == 0 if d == "t" lcd.out(CR) if d == "s" fdx.tx(CR) fdx.tx(LF) c := kb.key if c == ctlckey abort lcount++ if lcount > LCDRows - 2 lcd.str(string("-more-")) c := kb.getkey if c == esckey or c == ctlckey lcd.str(string(" (aborted)",CR)) return false repeat 6 lcd.str(string(bksp,spc,bksp)) lcount := 0 {{ TERMS OF USE: MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. }}Post Edited (Bill Drummond) : 5/6/2009 2:06:30 AM GMT
John Abshier
I tried some timing as well, but I changed your code to keep every branch consistent and on the same playing field. Using case -1 does shave 320 clocks off of the end result, but something I noticed in my observations, is that the IF/THEN statements are more consistent if you want some kind of determinism, where as the CASE statements seem to progressively increase in clock time the further down in the chain you get. Also the IF/THEN actually produces shorter code by a few longs than the CASE statements.
So in conclusion, if you want timing to be "more consistent" use the IF/THEN ... If slightly more speed is important and you only have 3 or 4 branches, use the CASE function. Anything beyond 5 branches you don't really gain anything in the way of faster execution speed. It actually starts counting against you.
[b]CON[/b] [b]_clkmode[/b] = [b]xtal1[/b] + [b]pll16x[/b] [b]_xinfreq[/b] = 5_000_000 [b]VAR[/b] [b]word[/b] cmd,done [b]long[/b] counter1,counter2 [b]obj[/b] COMport : "FullDuplexSerial.spin" [b]PUB[/b] TestLoop COMport.start(31, 30, 0, 2400) cmd := [b]string[/b]( "A" ) '<--- This value ranges from "A to "F" done := 0 [b]dira[/b][noparse][[/noparse]­16..23]~~ counter1 := [b]cnt[/b] '---------------------------------------- { [b]if[/b] [b]strcomp[/b]( [b]string[/b]( "A" ), cmd ) '6912 - A done := 1 [b]if[/b] [b]strcomp[/b]( [b]string[/b]( "B" ), cmd ) '6960 - B done := 2 [b]if[/b] [b]strcomp[/b]( [b]string[/b]( "C" ), cmd ) '6960 - C done := 3 [b]if[/b] [b]strcomp[/b]( [b]string[/b]( "D" ), cmd ) '6960 - D done := 4 [b]if[/b] [b]strcomp[/b]( [b]string[/b]( "E" ), cmd ) '6928 - E done := 5 '6432 - F <-- This value falls through all conditions } '---------------------------------------- { [b]case[/b] 1 == 1 [b]strcomp[/b]( [b]string[/b]( "A" ), cmd ): done := 1 '3824 - A [b]strcomp[/b]( [b]string[/b]( "B" ), cmd ): done := 2 '5008 - B [b]strcomp[/b]( [b]string[/b]( "C" ), cmd ): done := 3 '6144 - C [b]strcomp[/b]( [b]string[/b]( "D" ), cmd ): done := 4 '7280 - D [b]strcomp[/b]( [b]string[/b]( "E" ), cmd ): done := 5 '8384 - E '7888 - F <-- This value falls through all conditions } '---------------------------------------- '{ [b]case[/b] -1 [b]strcomp[/b]( [b]string[/b]( "A" ), cmd ): done := 1 '3504 - A [b]strcomp[/b]( [b]string[/b]( "B" ), cmd ): done := 2 '4688 - B [b]strcomp[/b]( [b]string[/b]( "C" ), cmd ): done := 3 '5824 - C [b]strcomp[/b]( [b]string[/b]( "D" ), cmd ): done := 4 '6960 - D [b]strcomp[/b]( [b]string[/b]( "E" ), cmd ): done := 5 '8064 - E '7568 - F <-- This value falls through all conditions '} '---------------------------------------- [b]outa[/b][noparse][[/noparse]­16..23] := |<done counter2 := [b]cnt[/b] COMport.dec(counter2-counter1) [b]repeat[/b]▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Beau Schwabe
IC Layout Engineer
Parallax, Inc.
Post Edited (Beau Schwabe (Parallax)) : 5/6/2009 8:52:16 PM GMT
This is true. A CASE statement is a serial construct that progressively compares each statement as it works its way down the chain and jumps out as soon as the condition is met (somewhat like a S/A A/D converter staircase). Your series of IF statement run every comparison every time and the only difference is which condition code is run. In that case the first instance (done :=1) is going to be faster than the others as it requires one less byte of spin code than the others. (Pushing 1 on the stack is faster than the others due to the way the compiler/interpreter handles that literal)
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
"VOOM"?!? Mate, this bird wouldn't "voom" if you put four million volts through it! 'E's bleedin' demised!
Is there a modern computer language that doesn't have some form of a case statement?
Who would want one?
If execution speed was an issue, is SPIN a logical choice?
Are there programmers who spend more time debugging their code than writing it?
LONG LIVE CLAIRTY, waste the computers time not mine.
Probably most. Especially if you count testing as debugging, after all you have to find the bugs first.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
Just like to mention a little bug in your code
The assignment is not putting the string into the byte-buffer. It's putting the long sized pointer to string "A" into the cmd buffer. This bug is 'fixed' by another bug:
As you pass the content of cmd to the stringcompare. Usually if you want to compare something with a buffer you'd write
strcomp( string("xy"), @cmd )
@ all
I like the hashing version because it simply saves memory. Of course this comes along with an added runtime for creating the hash value. But I don't see a reason for code that deals with user input to be as fast as possible. And the hashing function could be optimized as well. If you simply·XOR the 2 first with the 2 last characters resulting in a word size hash value of the command-string this might be sufficient to distinguish·all the commands you use. You could build the hash value while you enter the command - that's where you have plenty of time.
Say you have a complex controller with 30 commands with an average of 5 characters. You need 30 * 6 bytes (one additional byte for string terminator) = 180 bytes for storing the commands vs 60 bytes for hash-values. And of course it makes a difference if you use a strcomp in if or case instead of a constant. There you have additional savings.
·
That's kind of strange - if I'm reading your output right - as it looks like CASE is therefore more memory hungry, but that contradicts what I found in the PropTool!
I was in the middle of writing the following summary when I saw your post - I think it still holds?!
So to summarise:
* A "case" structure takes less memory
* Use "case -1" instead of "case 1==1"
* If you're after more deterministic timing, use a series of IF statements
* An ElseIf structure is faster than straight IFs
* The further down the "Case" and "ElseIf" structure that a matching condition is found, the longer the structure takes to process. This isn't the case in an "If" structure.
* The timing differences are extremely small, so your choice of structure really depends on your coding "style"
The up-shot is therefore: none of this really matters (!), but I've had fun exploring it, and learned some stuff along the way
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Cheers,
Simon
www.norfolkhelicopterclub.com
“Before you criticize someone, you should walk a mile in their shoes. That way when you criticize them, you are a mile away from them and you have their shoes.” - Jack Handey.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
lonesock
Piranha are people too.