Tech Tips on DevCentral
   
You are here: Tutorials > Tech Tips

Current Articles | Search | Syndication

Comparing iRule Control Statements

by Joe - 3246 views Article Rating

A ways back, we put up an article titled “Ten Steps to iRules Optimization” in which we illustrated a few simple steps to making your iRules faster.  Item #3 in that article was “Understanding Control Statements”.  I decided to put the findings to the test and build myself an iRule that performs built-in timing diagnostics for the various control statements and hopefully shed some more light on what command to choose in your situation of choice.

The Commands

The control statements that we seen used primarily in iRules are the following:

  • switch – The switch command allows you to compare a value to a list of matching values and execute a script when a match is found.

    switch {
      “val1” { # do something }
      “val2” { # do something else }
      …
    }
  • switch –glob – An extenstion to the switch command that allows for wildcard matching
    switch –glob {
      “val1” { # do something }
      “val2” { # do something else }
      …
    }

  • if/elseif – A control statement that allows you to match arbitrary values together and execute a script when a match is found.

    if { $val equals “foo” } { # do something }
    elseif { $val equals “bar” } # do something else }

  • matchclass – Perform comparisons with the contents of a datagroup or TCL list.

    set match [matchclass “val” equals $::list];
  • class match – A datagroup-only command to perform extended comparisons on group values (LTM v10.0 and above).

    set match [class match “val” equals listname]

The Variables

Since the execution of a command is very very fast and the lowest resolution timer we can get is in milliseconds, we are required to execute the commands many times sequentially to get a meaningful number.  Thus we need to allow for multiple iterations of the operations for each of the tests.

The total number of comparisons we are doing also is a factor in how the command performs.  For a single comparison (or a data group with one value), the commands will likely be equivalent and it will be a matter of choice.  So, we will allow for multiple listsize values to determine how many comparisons will occur.

Where the comparison occurs in the logic flow is also important.  For a 1000 if/elseif command structure, comparing on the first match will be much faster than the last match.  For this reason, I’ve used a modulus over the total listsize to generate the tests on.  For a list size of 100, we will test item 1, 19, 39, 59, and 79 which should give a good sampling across various locations in the lists where a match occurs. 

Initialization

In my attempt to test as many possible combinations as possible, as well as the pain of hard-coding a 10000 line long “if/elfeif” statement, I’ve taken some shortcuts (as illustrated in the various test snippets).  In this code below, we check for query string parameters and then set default values if they are not specified.  A comparison list is then generated with the matchlist variable.

   1: #--------------------------------------------------------------------------
   2: # read in parameters
   3: #--------------------------------------------------------------------------
   4: set listsize    [URI::query [HTTP::uri] "ls"];
   5: set iterations  [URI::query [HTTP::uri] "i"];
   6: set graphwidth  [URI::query [HTTP::uri] "gw"];
   7: set graphheight [URI::query [HTTP::uri] "gh"];
   8: set ymax        [URI::query [HTTP::uri] "ym"];
   9:  
  10: #--------------------------------------------------------------------------
  11: # set defaults
  12: #--------------------------------------------------------------------------
  13: if { ("" == $iterations) || ($iterations > 10000) } { set iterations 500; }
  14: if { "" == $listsize } { set listsize 5000; }
  15: if { "" == $graphwidth } { set graphwidth 300; }
  16: if { "" == $graphheight } { set graphheight 200; }
  17: if { "" == $ymax } { set ymax 500; }
  18:  
  19: set modulus [expr $listsize / 5];
  20: set autosize 0;
  21:  
  22: #--------------------------------------------------------------------------
  23: # build lookup list
  24: #--------------------------------------------------------------------------
  25: set matchlist "0";
  26: for {set i 1} {$i < $listsize} {incr i} {
  27:   lappend matchlist "$i";
  28: }
  29:  
  30: set luri [string tolower [HTTP::path]]

The Main iRule Logic

The iRule has two main components.  The first checks for the main page request of /calccommands.  When that is given it will generate an HTML page that embeds all the report graphs.  The graph images are passed back into the second switch condition where the specific test is performed and the a redirect is given for the Google Bar chart.

   1: switch -glob $luri {
   2:   
   3:   "/calccommands" {
   4:     #----------------------------------------------------------------------
   5:     # check for existence of class file.  If it doesn't exist
   6:     # print out a nice error message.  Otherwise, generate a page of
   7:     # embedded graphs that route back to this iRule for processing
   8:     #----------------------------------------------------------------------
   9:   }
  10:   "/calccommands/*" {
  11:     #----------------------------------------------------------------------
  12:     # Time various commands (switch, switch -glob, if/elseif, matchclass, 
  13:     # class match) and generate redirect to a Google Bar Chart
  14:     #----------------------------------------------------------------------
  15:   }
  16: }

Defining the Graphs

The first request in to “/calccommands” will first test for the existence of the class file for the test.  A class must be defined named “calc_nnn” where nnn is the listsize.  Values in the list should be 0 to nnn-1.  I used a simple perl script to generate class files of size 100, 1000, 5000, and 10000.

The catch command is used to test for the existence of the class file.  If it does not exist, then an error message is presented to the user.

If the class file exists, then a HTML page is generated with 5 charts, breaking up the list index we are testing into between 1 and listsize.  When the HTML response is build, it is sent back to the client with the HTTP::respond command.

   1: "/calccommands" {
   2:   
   3:   #----------------------------------------------------------------------
   4:   # check for existence of class file.  If it doesn't exist
   5:   # print out a nice error message.  Otherwise, generate a page of
   6:   # embedded graphs that route back to this iRule for processing
   7:   #----------------------------------------------------------------------
   8:   
   9:   if { [catch { class match "1" equals calc_$listsize } ] } {
  10:     
  11:     
  12:     # error
  13:     set content "<html><center>BIG-IP Version $static::tcl_platform(tmmVersion)"
  14:     append content "<h1><font color='red'>ERROR: class file 'calc_$listsize' not found</font></h1>";
  15:     append content "</html>";
  16:     
  17:   } else {
  18:     
  19:     # Build the html and send requests back in for the graphs...
  20:     set content "<html><center>BIG-IP Version $static::tcl_platform(tmmVersion)"
  21:     append content "<p>List Size: ${listsize}<p><hr size=3 width='75%'><p>"
  22:     
  23:     set c 0;
  24:     foreach item $matchlist {
  25:       set mod [expr $c % $modulus];
  26:       if { $mod == 0 } {
  27:         append content "<img src='$luri/$item"
  28:         append content "?ls=${listsize}&i=${iterations}&gw=${graphwidth}&gh=${graphheight}&ym=${ymax}'"
  29:         append content " width='${graphwidth}' height='${graphheight}' alt='Performance'>";
  30:       }
  31:       incr c;
  32:     }
  33:     append content "</center></html>";
  34:   }
  35:   HTTP::respond 200 content $content;
  36: }

The Tests

switch

As I mentioned above, I cheated a bit with the generation of the test code.  I found it too tedious to build a 10000 line iRule to test a 10000 switch statement, so I made use of the TCL eval command that allows you to execute a code block you define in a variable.  The expression variable holds the section of code I am to execute for the given test.

For the switch command, the first thing I do is take down the time before they start.  Then a loop occurs for iterations times.  This allows the clock counters to go high enough to build a useful report.  Inside this look, I created a switch statement looking for the specified index item we are looking for in the list.  Making the list content be strings of numbers made this very easy to do.  The foreach item in the generated matchlist a switch comparison was added to the expression.  Finally closing braces were added as was a final time.

Then the switch expression was passed to the eval command which processed the code.

Finally the duration was calculated by taking a difference in the two times and the labels and values variables were appended to with the results of the test.

   1: "/calccommands/*" {
   2:   
   3:   #----------------------------------------------------------------------
   4:   # Time various commands (switch, switch -glob, if/elseif, matchclass, 
   5:   # class match) and generate redirect to a Google Bar Chart
   6:   #----------------------------------------------------------------------
   7:   
   8:   set item [getfield $luri "/" 3]
   9:   set labels "|"
  10:   set values ""
  11:   
  12:   #----------------------------------------------------------------------
  13:   # Switch
  14:   #----------------------------------------------------------------------
  15:   
  16:   set expression "set t1 \[clock clicks -milliseconds\]; \n"
  17:   append expression "for { set y 0 } { \$y < $iterations } { incr y } { "
  18:   append expression "switch $item {"
  19:   foreach i $matchlist {
  20:     append expression "\"$i\" { } ";
  21:   }
  22:   append expression " } "
  23:   append expression " } \n"
  24:   append expression "set t2 \[clock clicks -milliseconds\]";
  25:   
  26:   eval $expression;
  27:   
  28:   set duration [expr {$t2 - $t1}]
  29:   if { [expr {$duration < 0}] } { log local0. "NEGATIVE TIME ($item, matchclass: $t1 -> $t2"; }
  30:   append labels "s|";
  31:   if { $values ne "" } { append values ","; }
  32:   append values "$duration";
  33:   
  34:   if { $autosize && ($duration > $ymax) } { set ymax $duration }

switch –glob

Just for kicks, I wanted to see what adding “-glob” to the switch command would do.  I didn’t make use of any wildcards in the comparisons.  In fact, they were identical to the previous test.  The only difference is the inclusion of the wildcard matching functionality.

   1: #----------------------------------------------------------------------
   2: # Switch -glob
   3: #----------------------------------------------------------------------
   4:  
   5: set expression "set t1 \[clock clicks -milliseconds\]; \n"
   6: append expression "for { set y 0 } { \$y < $iterations } { incr y } { "
   7: append expression "switch -glob $item {"
   8: foreach i $matchlist {
   9:   append expression "\"$i\" { } ";
  10: }
  11: append expression " } "
  12: append expression " } \n"
  13: append expression "set t2 \[clock clicks -milliseconds\]";
  14:  
  15: eval $expression;
  16:  
  17: set duration [expr {$t2 - $t1}]
  18: if { [expr {$duration < 0}] } { log local0. "NEGATIVE TIME ($item, matchclass: $t1 -> $t2"; }
  19: append labels "s-g|";
  20: if { $values ne "" } { append values ","; }
  21: append values "$duration";
  22:  
  23: if { $autosize && ($duration > $ymax) } { set ymax $duration }

If/elseif

The if/elseif test was very similar to the switch command above.  Timings were taken, but the only difference was the formation of the control statement.  In this case, the first line used ‘if { $item eq \”$i\” } {}’  Subsequent entries prepended “else” to make the rest of the lines ‘elseif { $item eq \”$i\”} {}’.  The evaluation of the expression was the same and the graph values were stored.

   1: #----------------------------------------------------------------------
   2: # If/Elseif
   3: #----------------------------------------------------------------------
   4: set z 0;
   5: set y 0;
   6:  
   7: set expression "set t1 \[clock clicks -milliseconds\]; \n"
   8: append expression "for { set y 0 } { \$y < $iterations } { incr y } { "
   9: foreach i $matchlist {
  10:   if { $z > 0 } { append expression "else"; }
  11:   append expression "if { $item eq \"$i\" } { } ";
  12:   incr z;
  13: }
  14: append expression " } \n";
  15: append expression "set t2 \[clock clicks -milliseconds\]";
  16:  
  17: eval $expression;
  18:  
  19: set duration [expr {$t2 - $t1}]
  20: if { [expr {$duration < 0}] } { log local0. "NEGATIVE TIME ($item, matchclass: $t1 -> $t2"; }
  21: append labels "If|";
  22: if { $values ne "" } { append values ","; }
  23: append values "$duration";
  24:  
  25: if { $autosize && ($duration > $ymax) } { set ymax $duration }

matchclass

My first attempt at this iRule, was to use matchclass against the generated matchlist variable.  The results weren’t that good and we realized the matchclass’s benefits come when working with native classes, not TCL lists.  I decided to keep this code the same, working on the auto-generated matchlist.  The next test will illustrate the power of using native data groups (classes).

   1: #----------------------------------------------------------------------
   2: # Matchclass on list
   3: #----------------------------------------------------------------------
   4:  
   5: set expression "set t1 \[clock clicks -milliseconds\]; \n"
   6: append expression "for { set y 0 } { \$y < $iterations } { incr y } { "
   7: append expression "if { \[matchclass $item equals \$matchlist \] } { }"
   8: append expression " } \n";
   9: append expression "set t2 \[clock clicks -milliseconds\]";
  10:  
  11: eval $expression;
  12:  
  13: set duration [expr {$t2 - $t1}]
  14: if { [expr {$duration < 0}] } { log local0. "NEGATIVE TIME ($item, matchclass: $t1 -> $t2"; }
  15: append labels "mc|";
  16: if { $values ne "" } { append values ","; }
  17: append values "$duration";
  18:  
  19: if { $autosize && ($duration > $ymax) } { set ymax $duration }

class match

In BIG-IP, version 10.0, we introduced the new “class” command that gives high-performance searches into data groups.  I decided to include the pre-configured classes for this test.  Data groups named “calc_nnn” must exist where nnn equls the listsize and it must contain that many elements for the test to be valid.

   1: #----------------------------------------------------------------------
   2: # class match (with class)
   3: #----------------------------------------------------------------------
   4:  
   5: set expression "set t1 \[clock clicks -milliseconds\]; \n"
   6: append expression "for { set y 0 } { \$y < $iterations } { incr y } { "
   7: append expression "if { \[class match $item equals calc_$listsize \] } { }"
   8: append expression " } \n";
   9: append expression "set t2 \[clock clicks -milliseconds\]";
  10:  
  11: log local0. $expression;
  12:  
  13: eval $expression;
  14:  
  15: set duration [expr {$t2 - $t1}]
  16: if { [expr {$duration < 0}] } { log local0. "NEGATIVE TIME ($item, matchclass: $t1 -> $t2"; }
  17: append labels "c|";
  18: if { $values ne "" } { append values ","; }
  19: append values "$duration";
  20:  
  21: if { $autosize && ($duration > $ymax) } { set ymax $duration }

Chart Generation

Once all of the tests have been run and the labels and values variables have all the data for the reports.  The image is served up with a simple HTTP::redirect to the appropriate google chart server.  I’ve made optimizations here to use the 0-9 prefix chart servers so that the browser could render the images quicker.

   1: #----------------------------------------------------------------------
   2: # build redirect for the google chart and issue a redirect
   3: #----------------------------------------------------------------------
   4:  
   5: set mod [expr $item % 10]
   6: set newuri "http://${mod}.chart.apis.google.com/chart?chxl=0:${labels}&chxr=1,0,${ymax}&chxt=x,y"
   7: append newuri "&chbh=a&chs=${graphwidth}x${graphheight}&cht=bvg&chco=A2C180&chds=0,${ymax}&chd=t:${values}"
   8: append newuri "&chdl=(in+ms)&chtt=Perf+(${iterations}-${item}/${listsize})&chg=0,2&chm=D,0000FF,0,0,3,1"
   9:  
  10: HTTP::redirect $newuri;

The Results

Several runs of the tests are shown below.  In the first, the tests are run on a list of size 100 for 10000 iterations for each test.  As you see for the first element in the matchlist, the if/elseif command is the winner, slightly edging out switch and then matchclass and class.  But when we start searching deeper into the list for comparisons, the if/elseif takes longer and longer depending on how far down in the list you are checking.  The other commands seem to grow in a linear fashion with the only exception being the class command.

CommandTimingList100

http://virtualserver/calccommands?ls=100&i=10000&ym=100

Next we move on to a slightly larger list with 1000 elements.  For the first entry, we see that if if/elseif command takes the longest.  The others are fairly equal.  Again as we move deeper into the list, the other commands grow somewhat linearly in their time with the exception of class which again stays consistent regardless of where in the list it looks.

 

CommandTimingList1000

http://virtualserver/calccommands?ls=1000&i=1000&ym=100

Finally, on a 5000 sized list, If is the clear loser regardless of where you are matching.  switch and matchclass (on a list) are somewhat on par with eachother, but again the class command takes the lead.

CommandTimingList5000

http://virtualserver/calccommands?ls=5000&i=500&ym=100

Observations

Let’s take the first few bullets in our optimization article and see if they matched our observations here.

  • Always think: "switch", "data group", and then "if/elseif" in that order.
    If you think in this order, then in most cases you will be better off.
  • Use switch for < 100 comparisons.
    Switches are fastest for fewer than 100 comparisons but if you are making changes to the list frequently, then a data group might be more manageable.
  • Use data groups for > 100 comparisons.
    Not only will your iRule be easier to read by externalizing the comparisons, but it will be easier to maintain and update.
  • Order your if/elseif's with most frequent hits at the top.
    If/elseif's must perform the full comparison for each if and elseif.  If you can move the most frequently hit match to the top, you'll have much less processing overhead.  If you don't know which hits are the highest, think about creating a Statistics profile to dynamically store your hit counts and then modify you iRule accordingly.

I think this list should be changed to “always use class match” but that’s not the best option for usability in some places. In situations where you are working with smaller lists of data, managing the comparisons inline will be more practical than having them in lots of little class files.    Aside from that, I think based on the graphs above, all of the techniques are on target.

Get The Code

You can view the entire iRule for this article in the iRules CodeShare under CommandPerformance.

Related Articles on DevCentral



Rate This Article:

COMMENTS

posted @ Wednesday, January 12, 2011 4:03 PM by Chris Miller   

This was unbelievably awesome! Reading the best practices is one thing but actually seeing the data is another. Thanks for going through all the effort to do this! Even though my "lists" are always smaller than 10 items, I'm a big fan of datagroups and class match just to keep my iRules cleaner.

Again, thanks a ton! -Chris Miller

posted @ Wednesday, January 12, 2011 4:30 PM by Jason Rahm   

Wow, Joe. Way to raise the bar, sir!

posted @ Wednesday, January 12, 2011 8:31 PM by Joe   

Thanks Chris and Jason. I think I'll need to write another article just on the lessons learned when trying to build self profiling iRules. One example was including the "clock click" commands within the eval statement. At first I had them outside, but the data was all wonky as the overhead on eval parsing the expression for the very large lists threw things off. Hat tip to Colin for recommending moving them inside the eval.

I also found another fun issue with certain cases having "clock clicks" shifting the clock backwards between calls. I'm still trying to figure that one out...

Anyway, it was a fun article and I'll hat tip Jason on the idea of the Google Charts. They may look familiar to his previous article...

-Joe

posted @ Thursday, January 13, 2011 6:29 AM by naladar   

That was an excellent article. I have made it a habit of trying to use the Switch command when building iRules where I can, but I will definitely have to look at what class match can do for me now.

Thanks Joe!

-Nathan

posted @ Thursday, January 13, 2011 10:27 AM by Colin Walker   

Pretty wicked man. I tip my hat to thee!

#Colin

posted @ Friday, December 30, 2011 6:02 AM by DevCentral TV  

Only registered users may post comments.
  

TechTips by Category

Filter by:
ARX Import Restrictions for NetApp Volumes by jmccarron (30 Views)
Two-Factor Authentication With Google Authenticator And APM by watkins (297 Views)
Controlling a Pool Members Ratio and Priority Group with iControl by Joe (243 Views)
F5 ARX WAN Optimization with BIG-IP WAN Optimization Manager (WOM) by mfabiano (284 Views)
iRules Concepts: Tcl, The How and Why by Colin (957 Views)
Populating Tables With CSV Data Via Sideband Connections by watkins (958 Views)
Introduction to iStats Part 1: Overview by Colin (1069 Views)
Google reCAPTCHA Verification With Sideband Connections by watkins (1430 Views)
Transparent Web Application Bot Protection by Joe (1265 Views)
v11: iRules Data Group Updates by citizen_elah (2927 Views)
HTTP Request Cloning via iRules, Part 1 by Colin (1342 Views)
Managing Ramcache Entries with Pycontrol by citizen_elah (909 Views)
iRules Concepts: Connection States and Command Suspension by Colin (1727 Views)
v11.1: DNS Blackhole with iRules by citizen_elah (2089 Views)
CodeShare Refresh: HTTP Session Limit by Colin (1400 Views)
Page 1 of 4First   Previous   [1]  2  3  4  Next   Last   
  

Most Viewed Tech Tips

Mitigating Slow HTTP Post DDoS Attacks With iRules by watkins
(6045 Views) Published on Friday, November 05, 2010
APM Session Invalidation Using ASM by Colin
(6030 Views) Published on Monday, October 17, 2011
Web Application Login Integration with APM by Colin
(5800 Views) Published on Monday, April 18, 2011
iRules Data Group Formatting Rules by citizen_elah
(5490 Views) Published on Tuesday, March 29, 2011
Multiple Certs, One VIP: TLS Server Name Indication via iRules by Colin
(5422 Views) Published on Tuesday, April 05, 2011
One Time Passwords via an SMS Gateway with BIG-IP Access Policy Manager by citizen_elah
(5386 Views) Published on Tuesday, February 08, 2011
BIG-IP APM–Customized Logon Page by citizen_elah
(5104 Views) Published on Tuesday, June 21, 2011
SSL Profiles Part 3: Certificate Chain Implementation by citizen_elah
(5050 Views) Published on Wednesday, December 01, 2010
SSL Profiles: Part 1 by citizen_elah
(4747 Views) Published on Wednesday, November 17, 2010
v11: RDP Access via BIG-IP APM–Part 3 by citizen_elah
(4715 Views) Published on Tuesday, October 04, 2011
Automating Web App Deployments with Opscode Chef and iControl by watkins
(4565 Views) Published on Friday, July 08, 2011
  

 Top Contributors

Techgeeeg
fatmcgav
mikand
MarkM
Chris Phillips
genseek
K-Dubb
adiezma
Ankush Narang
Beinhard
  

93,050 Members in 191 Countries and Growing!

Join DevCentral Today!

About DevCentral

F5 DevCentral is your source for the best technical documentation, discussion forums, blogs, media and more related to application delivery networking.

So dive in, meet your peers, and get familiar with DevCentral. We hope it makes your job easier and helps you get more from your F5 investment. If new to DevCentral, check out the Getting Started section. And if you have any problems, or think something could be easier to use, let us know.

Got It !

We've received your comment and transmitted it directly to DevCentral HQ.

Thanks for taking time to let us know what's on your mind. At DevCentral | Community Matters!

Get In Touch With Us

Have questions, suggestions or just want to get something off your chest?

Use our handy form below to Direct Connect with DevCentral Mission Control.

Send Us Feedback      or