Page 1 of 1
Faster than "Select...Case" (ON n GOSUB alternati
Posted: Sat Nov 19, 2005 4:39 am
by Randy Walker
[RE RE-EDITED FOR PUREBASIC VERSION 4.0 - 3/23/06]
Copy, paste, test and examine. It's pretty straight forward. Use as you wish.
Posted: Sat Nov 19, 2005 6:29 am
by vanleth
Hmm could be a debugger issue. Maybe the debugger does a ekstra safe check when meeting a Select statement.
Posted: Sat Nov 19, 2005 7:23 am
by Randy Walker
[RE RE-EDITED FOR PUREBASIC VERSION 4.0 - 3/23/06]
Based on your speculation, I extended the code and replaced debug using MessageRequestor() instead. The net results are the same because there is no list of if/then comparisons when using the CallFunctionFast() alternative. This is my point. The debugger has noting to do with the difference in speed. It is purely because I replaced the list of conditionnals with direct jumps according to value in c.w
Code: Select all
;/ YOUR FAMILIAR, STANDARD, EVERY DAY "SELECT / CASE" SAMPLE CODE
; Going to test duration on "Select...Case Filtering" loop.
capturedTime.l = ElapsedMilliseconds()
For l.l = 1 To 100000 ; These two loops only here to demonstrate concept.
For operation.w = 1 To 52 ; Using 52 operations here is arbitrary.
Select operation ; <<These "operation.l values" could also be random!!
Case 1 ; and same would apply to alternative method below.
dummy.l = dummy.l & 5
Case 2 ; Notice how ...
dummy.l = dummy.l & 5 ;The
Case 3
dummy.l = dummy.l & 5 ;more
Case 4
dummy.l = dummy.l & 5 ;Cases
Case 5
dummy.l = dummy.l & 5 ;there
Case 6
dummy.l = dummy.l & 5 ;are
Case 7
dummy.l = dummy.l & 5 ;the
Case 8
dummy.l = dummy.l & 5 ;less
Case 9
dummy.l = dummy.l & 5 ;efficient
Case 10
dummy.l = dummy.l & 5 ;Select
Case 11
dummy.l = dummy.l & 5 ;Case
Case 12
dummy.l = dummy.l & 5
Case 13
dummy.l = dummy.l & 5 ; Select/Case is a "filtering system"
Case 14 ; that must fail numerous tests before
dummy.l = dummy.l & 5 ; encountering matched values. Only
Case 15 ; after numurous failures, your code
dummy.l = dummy.l & 5 ; can then begin processing.
Case 16
dummy.l = dummy.l & 5
Case 17
dummy.l = dummy.l & 5 ; The alternative "direct jump" method
Case 18 ; always points to the exact fucntion
dummy.l = dummy.l & 5 ; without wasting any time "filtering".
Case 19
dummy.l = dummy.l & 5
Case 20
dummy.l = dummy.l & 5 ; For sample purposes, the functions
Case 21 ; used in each "Case value" have been
dummy.l = dummy.l & 5 ; kept identical (dummy=dummy & 5) to
Case 22 ; standardize comparisons.
dummy.l = dummy.l & 5
Case 23
dummy.l = dummy.l & 5
Case 24
dummy.l = dummy.l & 5
Case 25
dummy.l = dummy.l & 5
Case 26
dummy.l = dummy.l & 5
Case 27
dummy.l = dummy.l & 5
Case 28
dummy.l = dummy.l & 5
Case 29
dummy.l = dummy.l & 5
Case 30
dummy.l = dummy.l & 5
Case 31
dummy.l = dummy.l & 5
Case 32
dummy.l = dummy.l & 5
Case 33
dummy.l = dummy.l & 5
Case 34
dummy.l = dummy.l & 5
Case 35
dummy.l = dummy.l & 5
Case 36
dummy.l = dummy.l & 5
Case 37
dummy.l = dummy.l & 5
Case 38
dummy.l = dummy.l & 5
Case 39
dummy.l = dummy.l & 5
Case 40
dummy.l = dummy.l & 5
Case 41
dummy.l = dummy.l & 5
Case 42
dummy.l = dummy.l & 5
Case 43
dummy.l = dummy.l & 5
Case 44
dummy.l = dummy.l & 5
Case 45
dummy.l = dummy.l & 5
Case 46
dummy.l = dummy.l & 5
Case 47
dummy.l = dummy.l & 5
Case 48
dummy.l = dummy.l & 5
Case 49
dummy.l = dummy.l & 5
Case 50
dummy.l = dummy.l & 5
Case 51
dummy.l = dummy.l & 5
Case 52
dummy.l = dummy.l & 5
EndSelect
Next operation
Next l
MessageRequester("Select...Case Elaped Time",StrQ(ElapsedMilliseconds() - capturedTime.l))
;
;/ NOW THE ALTERNATIVE TO "SELECT / CASE FILTERING" AS SEEN ABOVE
;/ Initialize procedures that will correlate to "Case values"
; Array used to index what will be our "value dependant" procedure calls
Global Dim p.l(52)
Procedure test_a() ; CallFunctionFast() will be used later to call
dummy.l = dummy.l & 5 ; these procedures directly by numeric value.
EndProcedure ; This section equates 1 to 1 with the code above.
Procedure test_b()
dummy.l = dummy.l & 5 ; Procedures seen here are layed out and numbered
EndProcedure ; sequencially to match "Select/Case" equivilants
Procedure test_c() ; used in sample code above.
dummy.l = dummy.l & 5
EndProcedure
Procedure test_d()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_e()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_f()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_g()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_h()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_i()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_j()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_k()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_l()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_m()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_n()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_o()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_p()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_q()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_r()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_s()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_t()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_u()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_v()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_w()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_x()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_y()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_z()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_aa()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_ab()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_ac()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_ad()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_ae()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_af()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_ag()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_ah()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_ai()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_aj()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_ak()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_al()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_am()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_an()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_ao()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_ap()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_aq()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_ar()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_as()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_at()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_au()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_av()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_aw()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_ax()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_ay()
dummy.l = dummy.l & 5
EndProcedure
Procedure test_az()
dummy.l = dummy.l & 5
EndProcedure
;/ ; Initialize array for code demonstration below
p(1) = @test_a() ; Equivelent syntax under GFA_Basic for this operation
p(2) = @test_b() ; would be as follows:
p(3) = @test_c() ; ON operation GOSUB proc1, proc2, proc3, proc4, ...
p(4) = @test_d() ;
p(5) = @test_e() ; Could just as well be:
p(6) = @test_f() ; ON operation GOSUB left(), right(), bendover(), ...
p(7) = @test_g() ; ON operation GOSUB test_a(), test_b(), test_c(), ...
p(8) = @test_h() ;
p(9) = @test_i() ; The idea being procedures are called by corresponding
p(10) = @test_j() ; position following the term "Gosub". (1, 2, 3, ...)
p(11) = @test_k() ;
p(12) = @test_l() ; A comma delimnited list is not available in PureBasic
p(13) = @test_m() ; so instead we need to create our own index. That's
p(14) = @test_n() ; why we set up the procedures above. Now we index.
p(15) = @test_o() ;
p(16) = @test_p() ; The "@"procedure does the magic here by allowing each
p(17) = @test_q() ; procedure to be assigned a "Case value" according to
p(18) = @test_r() ; the procedure address in memory.
p(19) = @test_s()
p(20) = @test_t()
p(21) = @test_u()
p(22) = @test_v()
p(23) = @test_w()
p(24) = @test_x()
p(25) = @test_y()
p(26) = @test_z()
p(27) = @test_aa()
p(28) = @test_ab()
p(29) = @test_ac()
p(30) = @test_ad()
p(31) = @test_ae()
p(32) = @test_af()
p(33) = @test_ag()
p(34) = @test_ah()
p(35) = @test_ai()
p(36) = @test_aj()
p(37) = @test_ak()
p(38) = @test_al()
p(39) = @test_am()
p(40) = @test_an()
p(41) = @test_ao()
p(42) = @test_ap()
p(43) = @test_aq()
p(44) = @test_ar()
p(45) = @test_as()
p(46) = @test_at()
p(47) = @test_au()
p(48) = @test_av()
p(49) = @test_aw()
p(50) = @test_ax()
p(51) = @test_ay()
p(52) = @test_az()
; Test duration on "CallFunctionFast()" loop.
capturedTime.l = ElapsedMilliseconds()
For l.l = 1 To 100000 ; These two loops only to demonstrate concept.
For operation.w = 1 To 52
*a = p(operation) ; Any value inside p() "means" call operation directly!!!
CallFunctionFast(*a) ; << JUST DO IT -- NO FILTERING REQUIRED !!!
Next operation
Next l
MessageRequester("CallFunctionFast() Elapsed Time",StrQ(ElapsedMilliseconds() - capturedTime.l))
Posted: Sat Nov 19, 2005 8:08 am
by Randy Walker
Sorry I'm only a novice at programming so this is probably not the best code to demonstrate. Appearntly Windows has some mutlitask thing going on that may give false results on occasion. If you run the code a number of times you should see on the average, about 1 half the processing time when you hit the CallFunctionFast() section. Also, I am running on a P3 @ 600 Mhz so you may need to increase the loop values to see a difference.
Posted: Sat Nov 19, 2005 10:45 am
by blueznl
it may be faster but i consider it horrible coding, personal opinion etc

Posted: Sat Nov 19, 2005 11:42 am
by Randy Walker
blueznl wrote:it may be faster but i consider it horrible coding, personal opinion etc

Exactly why I submitted it as a requested function. As the comment noted in the code above. GFA accomplished this with a very simple command that made Select/Case look complicated. The code above only emulates the functionality in GFA and therefore, as you point out, is nothing close to ideal. Didn't you use GFA?
Posted: Mon Nov 21, 2005 5:24 pm
by Hades
It really isn't that horrible.
For example, if you have to render different objects and can't do it with the same procedure, it's a bit like object oriented coding.
You could just write CallFunctionFast(Object\Render) instead of a long select / case list.
Much nicer

Posted: Mon Nov 21, 2005 6:37 pm
by Randy Walker
Hades wrote: It really isn't that horrible ... it's a bit like object oriented coding.
Thanks Hades. (Uhh ... whatever object oriented coding is

)
I think the horible part is in my sample presentation. It is not the best example in terms demonstrating possible applications. I tried to keep the code as inert as possible to demonstrate the timing advantage. In actuality, when placed side by side, the code is even shorter than the Select/Case alternative.
Here the Select/Case alternative occupies 58 lines of code:
Code: Select all
For l.w = 1 To 10000
For c.w = 1 To 26
Select c.w
Case 1
test_a() ;The
Case 2
test_b() ;more
Case 3
test_c() ;Cases
Case 4
test_d() ;the
Case 5
test_e() ;less
Case 6
test_f() ;efficient
Case 7
test_g() ;Select
Case 8
test_h() ;Case
Case 9
test_i()
Case 10
test_j()
Case 11
test_k()
Case 12
test_l()
Case 13
test_m()
Case 14
test_n()
Case 15
test_o()
Case 16
test_p()
Case 17
test_q()
Case 18
test_r()
Case 19
test_s()
Case 20
test_t()
Case 21
test_u()
Case 22
test_v()
Case 23
test_w()
Case 24
test_x()
Case 25
test_y()
Case 26
test_z()
EndSelect
Next c
Next l
And the shorter, faster alternative occupies only 33 lines of code:
Code: Select all
Dim p.l(26) ; Array used to index case dependant procedure calls using CallFunctionFast()
p(1) = @test_a() ; Equivelent syntax under GFA_Basic for this operation
p(2) = @test_b() ; would be as follows:
p(3) = @test_c() ; ON n GOSUB proc1, proc2, proc3, proc4, etc...
p(4) = @test_d() ;
p(5) = @test_e() ; The value "n" above points to comma delimnited list
p(6) = @test_f() ; following the ON n GOSUB command so that direct
p(7) = @test_g() ; indexing by value is possible. List items can be any
p(8) = @test_h() ; order. There is no "conditional" testing so processing
p(9) = @test_i() ; time is less than the "Select/Case"
p(10) = @test_j()
p(11) = @test_k()
p(12) = @test_l()
p(13) = @test_m()
p(14) = @test_n()
p(15) = @test_o()
p(16) = @test_p()
p(17) = @test_q()
p(18) = @test_r()
p(19) = @test_s()
p(20) = @test_t()
p(21) = @test_u()
p(22) = @test_v()
p(23) = @test_w()
p(24) = @test_x()
p(25) = @test_y()
p(26) = @test_z()
For l.w = 1 To 10000
For c.w = 1 To 26
*a = p(c) ; c.w is the index value for procedure call
CallFunctionFast(*a)
Next c
Next l
Better yet -- If we can get Fred to poke the procedure addresses into the comma delimited list below during compile, we could reduce all the above to only a single line, and the need for the index array is emilinated completely.
Code: Select all
On c.w GoProc test_a(), test_b(), test_c(), test_d() ..., test_x(), test_y(), test_z()
Of course he may prefer diferent syntax/command structure, but ... Wouldn't that be sweet??!!
Posted: Mon Nov 21, 2005 9:15 pm
by blueznl
ah okay, now i see what you mean, yeah, in certain situations it may help indeed, yes
(and yeah, i was heavily into gfabasic indeed

)
on n goproc ... or on n call ... would be fine i guess...
Posted: Mon Nov 21, 2005 9:44 pm
by Randy Walker
blueznl wrote: ... now i see what you mean, yeah, in certain situations it may help indeed, yes ...
Thank you blueznl. I've come to respect your talents and contributions here rather highly so you were really making me question myself on this.
Now I just hope that Fred can justify the advantage and find a way to implement some equivelent command for us. Select/Case is powerfull and has it's place, but in some instances it can be a bit primitive.
Posted: Mon Nov 21, 2005 9:59 pm
by blueznl
blushing
reason why i saw the light is stuff like (totally wrong example

)
Code: Select all
on direction call goleft(), goright(), goup(), godown()