I'm always fascinated by the creative outlets people come up with when toying with iRules.  My favorite is still Joe's FTP Hunt the Wumpus, which he blogged about a while back (the code is here in the wiki).  The latest is a great entry from Lori this morning, giving you, the community, a handy dice roller.  After a careful read, a few thoughts came to mind, in no particular order.

  • Wow, what a great way to collide two completely independent planets in the geek solar system.
  • Role playing games take too much time, I just want to load up the console and shoot stuff.  Lots of stuff.  Don't mess with me.
  • Sweet!  Another great iRule that has no earthly business value whatsoever but is fun nonetheless.
  • Umm, there sure are a lot of variables in that there iRule:
Lori's iRule

when HTTP_REQUEST timing on {   set uri [HTTP::uri]   if {$uri starts_with "/roll"} {      set v1 [URI::query [HTTP::uri] "num"]      set v2 [URI::query [HTTP::uri] "d"]      set total 0      for {set i 0} { $i < $v1} {incr i} {         set dieroll [expr {int(rand()*$v2) + 1}]         set result1 [expr {round($dieroll)}]         set total [expr {$total + $result1}]      }      HTTP::respond 200 content "$v1 d $v2 = $total"   }}  

So I see at least five variables I can pull out of there.  After all, the general guidance is reduce, reduce, reduce when it comes to variables.  Here's my update:

Jason's *Optimized* iRule

when HTTP_REQUEST timing on {   if { [HTTP::uri] starts_with "/roll"} {      set total 0      for { set i 0 } { $i < [URI::query [HTTP::uri] "num"] } { incr i } {         set total [expr {$total + [expr {round([expr {int(rand()*[URI::query [HTTP::uri] "d"]) + 1}])}]}]      }      HTTP::respond 200 content "[URI::query [HTTP::uri] "num"] d [URI::query [HTTP::uri] "d"] = $total"   }}

Certainly not as readable, but it's going to smoke Lori's in a head to head test...ah, but to quote my good Autumn Saturday morning buddy Lee Corso, "Not so fast my friend!"  I enabled timing (shown above, timing on, in the rules on the EVENT line) and hit ctrl-F5 for a while to a local virtual on my test box with Lori's recommended 3d6 (http://myvip.com/roll?num=3&d=6).  Results weren't looking good for the home team.  Lori's results are first, followed by mine:

RULE dieroll
+-> HTTP_REQUEST   493 total   0 fail   0 abort
    |     Cycles (min, avg, max) = (0, 120259, 159600)

RULE dieroll
+-> HTTP_REQUEST   497 total   0 fail   0 abort
    |     Cycles (min, avg, max) = (0, 134771, 176760)

 

You can see that although I eliminated a bunch of variables, the average cycles necessary to execute the iRule climbed over 12%!  That's huge.  Hmm, me thinks URI::query is the culprit here.  Calling it to set up two variables, versus calling it repeatedly in a for loop I think is the problem here.  So let's re-optimize, still eliminating some variables, but keeping the URI::query out of the for loop:

Jason's Re-Optimized iRule

when HTTP_REQUEST timing on {   if { [HTTP::uri] starts_with "/roll"} {      set total 0      set numd "[URI::query [HTTP::uri] "num"] [URI::query [HTTP::uri] "d"]"      for { set i 0 } { $i < [lindex $numd 0] } { incr i } {         set total [expr {$total + [expr {round([expr {int(rand()*[lindex $numd 1]) + 1}])}]}]      }      HTTP::respond 200 content "[lindex $numd 0] d [lindex $numd 1] = $total"   }}

So instead of calling URI::query twice for separate variables, I set both values in a list, using lindex to pull out the particulars.  Did it matter?  Here are the results from the final version:

RULE dieroll
+-> HTTP_REQUEST   503 total   0 fail   0 abort
    |     Cycles (min, avg, max) = (0, 118033, 160752)

OK, so the savings here are minimal, 1.8% less cycles than the original, but any savings are good, right?  That many more cycles for the BIG-IP to tackle other jobs. 

*** UPDATE ***

Not sure why I didn't do this before I posted, but I thought I'd try eliminating the lindex and just set both of the variables outside the for loop since that's a one-time thing:

Jason's Re-Re-Optimized iRule

when HTTP_REQUEST timing on {   if { [HTTP::uri] starts_with "/roll"} {      set total 0      set num [URI::query [HTTP::uri] "num"]      set d  [URI::query [HTTP::uri] "d"]        for { set i 0 } { $i < $num } { incr i } {         set total [expr {$total + [expr {round([expr {int(rand()*$d) + 1}])}]}]      }      HTTP::respond 200 content "$num d $d = $total"   }}

 

So taking another look at the results, now we're cooking:

RULE dieroll
+-> HTTP_REQUEST   502 total   0 fail   0 abort
    |     Cycles (min, avg, max) = (0, 110229, 153768)

Significant savings here, cutting 8.3% of the consumed cycles.  Looking back at the exercise, the critical savings were found in the for loop, eliminating the variables there proved the greatest optimization.  So, class, what have we learned today?  Whereas the optimization techniques in general are solid, and should be utilized, it's important to realize that a) some reductions comes at a higher cost and b) testing is always a good thing. 

 

Follow me on Twitter Follow me on LinkedIn Follow me on Facebook Add to Technorati Favorites