on 30-Oct-2008 08:58
Ever wondered whether to use switch or if/else? How about the effects of regular expressions? Or even what the overhead of variables are? We've come up with a list of the top 10 optimization techniques you can follow to write a healthier happier iRule.
#1 - Use a Profile First
This one seems obvious enough but we continually get questions on the forums about iRules that are already built into the product directly. iRules does add extra processing overhead and will be slower than executing the same features in native code. Granted, they won't be that slow, but any little bit you can save will help you in the long run. So, if you aren't doing custom conditional testing, let the profiles do the work. The following common features implemented in iRules can be handled by native profiles:
Even if you do need to do some conditional testing, several of the profiles such as the stream profile will allow you to enable/disable and control the expression directly from the iRule.
#2 - A Little Planning Goes A Long Way
iRules are fun and it is really easy to find yourself jumping right in and coding without giving proper thought to what you are trying to accomplish. Just like any software project, it will pay off in the long run if you come up with some sort of feature specification and try to determine the best way to implement it. Common questions you can ask yourself include:
By taking a few minutes up front, you will save yourself a bunch of rewrites later on.
#3 - Understand Control Statements
Common control statements include if/elseif/else, switch, and the iRule matchclass/findclass commands with datagroups. The problem is that most of these do the same thing but, depending on their usage, their performance can vary quite a bit. Here are some general rules of thumb when considering which control statement to use:
Now, these are not set in stone but just general rules of thumb so use your best judgement. There will always be exceptions to the rule and it is best if you know the cost/benefits to make the right decision for you.
#4 - Regex is EVIL!
Regex's are cool and all, but are CPU hogs and should be considered pure evil. In most cases there are better alternatives.
Bad
when HTTP_REQUEST { if { [regex {^/myPortal} [HTTP::uri] } { regsub {/myPortal} [HTTP::uri] "/UserPortal" newUri HTTP::uri $newUri }
Good
when HTTP_REQUEST { if { [HTTP::uri] starts_with "/myPortal" } { set newUri [string map {myPortal UserPortal [HTTP::uri]] HTTP::uri $newUri }
But sometimes they are a necessary evil as illustrated in the CreditCardScrubber iRule. In the cases where it would take dozens or more commands to recreate the functionality in a regular expression, then a regex might be your best option.
when HTTP_RESPONSE_DATA { # Find ALL the possible credit card numbers in one pass set card_indices [regexp -all -inline -indices \ {(?:3[4|7]\d{2})(?:[ ,-]?(?:\d{5}(?:\d{1})?)){2}|(?:4\d{3})(?:[ ,-]?(?:\d{4})){3}|(?:5[1-5]\d{2})(?:[ ,-]?(?:\d{4})){3}|(?:6011)(?:[ ,-]?(?:\d{4})){3}} \ [HTTP::payload]] }
#5 - Don't Use Variables
The use of variables does incur a small amount of overhead that in some cases can be eliminated. Consider the following example where the value of the Host header and the URI are stored in variables and later are compared and logged. These take up additional memory for each connection.
when HTTP_REQUEST { set host [HTTP::host] set uri [HTTP::uri] if { $host equals "bob.com" } { log "Host = $host; URI = $uri" pool http_pool1 } }
Here is the exact same functionality as above but without the overhead of the variables.
when HTTP_REQUEST { if { [HTTP::host] equals "bob.com" } { log "Host = [HTTP::host]; URI = [HTTP::uri]" pool http_pool1 } }
You may ask yourself: "Self, who cares about a spare 30 bytes or so?" Well, for a single connection, it's likely no big deal but you need to think in terms of 100's of 1000's of connections per second. That's where even the smallest amount of overhead you can save will add up. Removing those variables could gain you a couple thousand connections per second on your application.
And never fear, there is no more overhead in using the builtin commands like [HTTP::host] over a varable reference as both are just direct pointers to the data in memory.
#6 - Use Variables
Now that I've told you not to use variables, here's the case where using variables makes sense. When you need to do repetitive costly evaluations on a value and can avoid it by storing that evaluation in a variable, then by all means do so.
Bad - Too many memory allocations
when HTTP_REQUEST { if { [string tolower [HTTP::uri]] starts_with "/img" } { pool imagePool } elseif { ([string tolower [HTTP::uri]] ends_with ".gif") || ([string tolower [HTTP::uri]] ends_with ".jpg") } { pool imagePool } }
Bad - The length of a variable name can actually consume more overhead due to the way TCL stores variables in lookup tables.
when HTTP_REQUEST { set theUriThatIAmMatchingInThisiRule [string tolower [HTTP::uri]] if { $theUriThatIAmMatchingInThisiRule starts_with "/img" } { pool imagePool } elseif { ($theUriThatIAmMatchingInThisiRule ends_with ".gif") || ($theUriThatIAmMatchingInThisiRule ends_with ".jpg") } { pool imagePool } }
Good - Keep your variables names to a handful of characters.
when HTTP_REQUEST { set uri [string tolower [HTTP::uri]] if { $uri starts_with "/img" } { pool imagePool } elseif { ($uri ends_with ".gif") || ($uri ends_with ".jpg") } { pool imagePool } }
#7 - Understand Polymorphism
TCL is polymorphic in that variables can "morph" back and forth from data type to data type depending on how it is used. This makes loosly-typed languages like TCL easier to work with since you don't need to declare variables as a certain type such as integer or string but it does come at a cost. You can minimize those costs by using the correct operators on your data types.
If you aren't careful, things may not be what they seem as illustrated below
set x 5 if { $x == 5 } { } # evaluates to true if { $x eq 5 } { } # evaluates to true if { $x == 05 } { } # evaluates to true if { $x eq 05 } { } # evaluates to false
#8 - Timing
The timing iRule command is a great tool to help you eek the best performance out of your iRule. CPU cycles are stored on a per event level and you can turn "timing on" for any and all of your events. These CPU metrics are stored and can be retrieved by the bigpipe CLI, the administrative GUI, or the iControl API. The iRule Editor can take these numbers and convert them into the following statistics
Just make sure you turn those timing values off when you are done optimizing your iRule as that will have overhead as well!
#9 - Use The Community
DevCentral is a great resource whether you are just starting your first iRule or a seasoned veteran looking for advice on something you may have overlooked. The forums and wikis can be a life saver when trying to squeeze every bit of performance out of your iRule.
#10 - Learn about the newest features
Read the release documentation for new BIG-IP releases as we are constantly adding new functionalty to make your iRules faster. For instance, look for the upcoming "class" command to make accessing your data groups lightning fast.
Conclusion
Take these 10 tips with you when you travel down the road of iRule development and you will likley have a faster iRule to show for it. As always, if you have any tips that you've found to be helpful when optimizing your iRules, please pass them along and we'll add them to this list.