One of the most simple and straightforward methods to check whether
the code is able to complete a task within a reasonable time is
benchmarking. For the purpose, Interchange offers
the benchmark
tag which is found in the
eg/usertag/
directory of
the Interchange tarball distribution.
The benchmark
reference page contains all the relevant
installation and usage notes.
Interchange has powerful capabilities (such as searching) that allow you to produce lists of items for use in category lists, product lists, indexes, and other navigation tools or data reports.
These are a two-edged sword, though. Lists of hundreds or thousands of entries can be returned, and techniques that work well displaying only a few items may slow to a crawl when a large list is returned.
In general, when you are displaying only one item (such as on a
flypage) or a small list (such as shopping cart contents),
you can be pretty carefree in your use of ITL tags.
When there are thousands of items, though, you cannot; each
ITL tag requires parsing and argument building, and all
complex tests or embedded Perl blocks cause the
Safe
module to evaluate code.
The Safe
module is pretty fast considering
what it does, but it can only generate a few thousand instances per
second even on a fast system. And the ITL tag
parser can likewise only parse thousands of tags per CPU second.
What to do? You want to provide complex conditional tests but you don't want your system to slow to a crawl. Luckily, there are techniques which can speed up complex lists by orders of magnitude.
[
constructs
are the fastest way to retrieve loop data. Let's say we want to
find all our products (search in all PREFIX
-tag]ProductFiles
databases)
and display descriptions of all the products found:
[loop prefix=foo search="ra=yes"] [foo-data products description] [foo-field description] [data products description [foo-code]] [data table=products column=description key="[foo-code]"] [/loop]
The loop tags are interpreted by means of fast regular expression scans of the loop container text, and fetch an entire row of data in one query.
The [data]
ITL tag interpretation is
delayed until after the loop is finished, whereby the ITL tag
parser must find the tag, build a parameter list, and then fetch the
data with a separate query.
If there are repeated references to the same field in the loop, the speedup can be 10x or more.
The mv_return_fields
variable (otherwise known as the
"rf
" parameter in one-click terminology) defines
a comma-separated list of fields you want returned from a search.
This, in effect, kind of pre-fetches the data you want to use within
a loop.
Once the records are returned, the fields can be accessed using the
[
syntax.
The fields can also be referenced using PREFIX
-param field
][
,
where the PREFIX
-pos N
]N
represents the ordinal position
(starting from 0
) in the field list.
That said, the following are equivalent in effect but the second variant is much, much faster:
<pre> Benchmark loop-field list: [benchmark start=1] <!-- [loop search="ra=yes/st=db"] [loop-code] price: [loop-field price] [/loop] --> TIME: [benchmark] Benchmark loop-param list: [benchmark start=1] <!-- [loop search="ra=yes/st=db/rf=sku,price"] [loop-code] price: [loop-param price] [/loop] --> TIME: [benchmark] </pre>
[
can be used for row counting and display.
PREFIX
-alternate N
]
A common need when building tables is to conditionally close the table row or data containers. I see a lot of code that manually inserts new rows every three columns:
[loop search="ra=yes"] [calc] return '<tr>' if [loop-increment] == 1; return[/calc] [calc] return '' if [loop-increment] % 3; return '</tr>' [/calc] [/loop]
Much faster, by a few orders of magnitude than the above, is:
[loop search="ra=yes"] [loop-change 1][condition]1[/condition]<tr>[/loop-change 1] [loop-alternate 3]</tr>[/loop-alternate] [/loop]
If you think you need to close the final row by checking the final count, look at this complete example done the right way:
[loop search="ra=yes"] [on-match] <table> <tr> [/on-match] [list] <td>[loop-code]</td> [loop-alternate 3]</tr><tr>[/loop-alternate] [/list] [on-match] </tr> </table> [/on-match] [no-match] No match, sorry. [/no-match] [/loop]
The above is a hundred times faster than anything you can build with
multiple [calc]
tags.
Using [
, you
can execute the same code as with PREFIX
-calc][calc]
, but with two benefits:
you will not trigger ITL parsing, and the code will be
executed during the loop instead of
after it.
The [
object
has complete access to all normal embedded Perl objects like
PREFIX
-calc]$Values
, $Carts
,
$Tag
, and such. If you want to access data tables
from within the loop (such as products or
pricing), just call the following
above the loop:
[perl tables="products pricing" ]
For repetitive routines, you can achieve a considerable savings in CPU by pre-compiling your embedded Perl code. The precompilation can occur either once at catalog configuration time, or once at time of list execution.
When you compile routines at the time of the list execution
(using [item-sub
), only one
NAME
] ... CODE ...
[/item-sub]Safe
evaluation will be done, and every
time the [loop-exec
is called, it will be a direct call to the routine. This can be
10 times or more faster than separate NAME
][calc]
calls, or 5 times
faster than separate
[
calls. Here's
an example:
PREFIX
-calc
[benchmark start=1] loop-calc: <!-- [loop search="st=db/fi=country/ra=yes/ml=1000"] [loop-calc] my $code = q{[loop-code]}; return "code '$code' reversed is " . reverse($code); [/loop-calc] [/loop] --> [benchmark] <p> [benchmark start=1] loop-sub and loop-exec: <!-- [loop search="st=db/fi=country/ra=yes/ml=1000"] [loop-sub country_compare] my $code = shift; return "code '$code' reversed is " . reverse($code); [/loop-sub] [loop-exec country_compare][loop-code][/loop-exec] [/loop] --> [benchmark]
You can run [query arrayref=
, which saves the
results of the search/query in a Perl reference. It is then
available in
KEYNAME
sql="... SQL ...
"]$Tmp->{KEYNAME}
.
This is the fastest possible method to display a list. Observe:
[set waiting_for]os28004[/set] [benchmark start=1] Query plus embedded Perl <!-- [query arrayref=myref sql="select sku,price,description from products" ] [perl] # Get the query results, has multiple fields my $ary = $Tmp->{myref}; my $out = ''; foreach $line (@$ary) { my ($sku, $price, $desc) = @$line; if($sku eq $Scratch->{waiting_for}) { $out .= "We were waiting for this one!!!!\n"; } $out .= "sku: $sku price: $price description: $desc\n"; } return $out; [/perl] --> TIME: [benchmark] <p> [benchmark start=1] Just query <!-- [query list=1 sql="select sku,price,description from products"] [if scratch waiting_for eq '[sql-code]'] We were waiting for this one!!!! [/if] sku: [sql-code] price: [sql-param price] desc: [sql-param description] [/query] --> TIME: [benchmark]
Consider these two snippets:
[if scratch KEY] ... do something ... [/if]
and:
[if scratch KEY == '1'] ... do something ... [/if]
The first variant does not require Perl evaluation. It simply checks
to see if the value is blank or 0
, and assumes
TRUE if it is anything but.
Of course, this requires your code to return blank or value
0
for FALSE results (instead of say,
"No
" or " "), but then we can talk about a
20-35% speed-up.
Here's a sample program to time the results:
Overhead: [benchmark start=1] <!-- [loop search="ra=yes"] [set cert][loop-field gift_cert][/set] [/loop] --> [benchmark] <p> "if scratch cert": [benchmark start=1] <!-- [loop search="ra=yes"] [set cert][loop-field gift_cert][/set] [loop-code] [if scratch cert] YES [else] NO [/else][/if] [loop-code] [if scratch cert] YES [else] NO [/else][/if] [loop-code] [if scratch cert] YES [else] NO [/else][/if] [loop-code] [if scratch cert] YES [else] NO [/else][/if] [loop-code] [if scratch cert] YES [else] NO [/else][/if] [/loop] --> [benchmark] <p> "if scratch cert == 1": [benchmark start=1] <!-- [loop search="ra=yes"] [set cert][loop-field gift_cert][/set] [loop-code] [if scratch cert == 1] YES [else] NO [/else][/if] [loop-code] [if scratch cert == 1] YES [else] NO [/else][/if] [loop-code] [if scratch cert == 1] YES [else] NO [/else][/if] [loop-code] [if scratch cert == 1] YES [else] NO [/else][/if] [loop-code] [if scratch cert == 1] YES [else] NO [/else][/if] [/loop] --> [benchmark] <p> [page optimization/ar01s01]Run again</a>
Avoid interpolate=1
and
reparse=1
whenever possible. A separate tag parser
must be spawned every time you do this. Many times people use this
without needing it.
Avoid saving large values to scratch space, as these will be
written to the users session. If you need them only for the current
page, use [tmpn]
and [tmp]
instead of [set]
and [seti]
(temporary variables are automatically deleted at the end of current
page processing - before the user's session is saved).
You can also retrieve values using [scratchd
to return the contents and delete them from the session at the same
time.
VARIABLE_NAME
]