attached patch provides a better way to handle assignment of the odd/even class for tables. the old way would give incorrect display results if you happened to build your $rows array with numeric keys.

i'm also pretty sure this is more efficient code, but have not run tests to verify that.

credit goes to chx for the array flipping idea. :)

CommentFileSizeAuthor
odd_even.patch926 byteshunmonk
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

hunmonk’s picture

Benchmarking results -- the proposed solution appears to be the fastest among the three i tested:

 
$flip = array('even' => 'odd', 'odd' => 'even');
$class = 'even';
 
$time_start = microtime(true);
for ($number = 0; $number < 200000; $number++) {
  $class = ($number % 2 == 1) ? 'even': 'odd';
}
$time_end = microtime(true);
$old_time = $time_end - $time_start;
 
$time_start = microtime(true);
for ($number = 0; $number < 200000; $number++) {
  $class = $flip[$class];
}
$time_end = microtime(true);
$array_flip_time = $time_end - $time_start;
 
$time_start = microtime(true);
for ($number = 0; $number < 200000; $number++) {
  $class = $class == 'even' ? 'odd' : 'even';
}
$time_end = microtime(true);
$tri_flip_time = $time_end - $time_start;
 
echo "Old time is $old_time seconds\n";
echo "Array flip time is $array_flip_time seconds\n";
echo "Trinary flip time is $tri_flip_time seconds\n";

Results:

Old time is 2.94821381569 seconds
Array flip time is 2.82611393929 seconds
Trinary flip time is 3.10265183449 seconds

Old time is 2.9622759819 seconds
Array flip time is 2.9366338253 seconds
Trinary flip time is 3.28688883781 seconds

Old time is 3.1440680027 seconds
Array flip time is 2.91075491905 seconds
Trinary flip time is 3.05684518814 seconds

Old time is 3.09293603897 seconds
Array flip time is 2.87835907936 seconds
Trinary flip time is 3.03077507019 seconds

Old time is 3.16652011871 seconds
Array flip time is 2.86847901344 seconds
Trinary flip time is 3.02843999863 seconds

chx’s picture

Status: Needs review » Reviewed & tested by the community

Based on Chad's numbers I say this is good to go but the core committers need to decide whether the code is easy enough to understand. I suspected it'll be fast because there is no branching in there this way.

Dries’s picture

Status: Reviewed & tested by the community » Fixed

Committed to CVS HEAD.

drumm’s picture

Committed to 5.

douggreen’s picture

That's nifty and impressive. And I think that these results are accurate. But I'd like to add two points:

  • the biggest improvement isn't from the arrays lookup, but from the mod operator. Changing the code to $number & 1 also gives a significant boost.
  • using microtime for benchmarking isn't very accurate. I refreshed my page a half dozen times and on at least one occasion, the "And time" was fastest.

Modified sample test with "And" time:

<?php
$time_start = microtime(true);
for ($number = 0; $number < 200000; $number++) {
  $class = ($number % 2 == 1) ? 'even': 'odd';
}
$time_end = microtime(true);
$old_time = $time_end - $time_start;

$time_start = microtime(true);
$flip = array('even' => 'odd', 'odd' => 'even');
$class = 'even';
for ($number = 0; $number < 200000; $number++) {
  $class = $flip[$class];
}
$time_end = microtime(true);
$array_flip_time = $time_end - $time_start;

$time_start = microtime(true);
for ($number = 0; $number < 200000; $number++) {
  $class = ($number & 1) ? 'odd' : 'even';
}
$time_end = microtime(true);
$and_time = $time_end - $time_start;

$time_start = microtime(true);
for ($number = 0; $number < 200000; $number++) {
  $class = $class == 'even' ? 'odd' : 'even';
}
$time_end = microtime(true);
$tri_flip_time = $time_end - $time_start;

echo "Old time is $old_time seconds\n";
echo "Array flip time is $array_flip_time seconds\n";
echo "And time is $and_time seconds\n";
echo "Trinary flip time is $tri_flip_time seconds\n";
?>

Results:

Old time is 0.25097584724426 seconds
Array flip time is 0.13768005371094 seconds
And time is 0.15497612953186 seconds
Trinary flip time is 0.21387219429016 seconds

To show that microtime isn't a good benchmarking tool, I refreshed my page a half dozen times and on at least one occasion, the "And time" was fastest.

Old time is 0.28778481483459 seconds
Array flip time is 0.17358088493347 seconds
And time is 0.15392303466797 seconds
Trinary flip time is 0.2486789226532 seconds
hunmonk’s picture

@douggreen: just so it's clear, the and operator carries the same problem as the mod -- it's not a key agnostic way to perform the flipping, which was half of the reason for this change.

vhmauery’s picture

to make microtime a better tool, you might consider upping the number of iterations by say, 100x or so.

I think this patch may have been committed for the wrong reason. I just did several runs with loops of 20,000,000 rather than 200,000, which makes a huge difference when trying to measure such a small difference. This is a pretty standard representation after several runs of what I got:

Old time (%) is 35.83558 seconds
Old time (&) is 35.139227 seconds
Array with & time is 35.979259 seconds
Array flip time is 34.806519 seconds
Trinary flip time is 35.986957 seconds

I added a new test that uses a numerically indexed array and a bit-wise and. It doesn't seem to make much difference either. But the point of this is that it doesn't really matter. We are talking on the order of a max difference of 5.90219e-08 seconds per flip -- 59 nano seconds. In other words, the time required for a process context switch is orders of magnitude higher. After several other runs, I was noticing differences between the fastest and slowest of around 80 nano seconds.

What all this says to me is that it doesn't matter which one you use. Just use the one that is the easiest to read and maintain.

Note that I only have php4 available right now for my testing, so I had to modify the test so it looks like:

<?php
function timediff($t1, $t2) {
	$sec = $t2['sec'] - $t1['sec'];
	$sec += (($t2['usec'] - $t1['usec'])/1000000.0);
	return $sec;
}
$flip = array('even' => 'odd', 'odd' => 'even');
$class = 'even';

$time_start = gettimeofday();
for ($number = 0; $number < 20000000; $number++) {
  $class = ($number % 2 == 1) ? 'even': 'odd';
}
$time_end = gettimeofday();
$old_time = timediff($time_start, $time_end);

$time_start = gettimeofday();
for ($number = 0; $number < 20000000; $number++) {
  $class = ($number & 1 == 0) ? 'even': 'odd';
}
$time_end = gettimeofday();
$old_time2 = timediff($time_start, $time_end);

$classes = array('even', 'odd');
$time_start = gettimeofday();
for ($number = 0; $number < 20000000; $number++) {
  $class = $classes[$number & 1];
}
$time_end = gettimeofday();
$old_time3 = timediff($time_start, $time_end);

$time_start = gettimeofday();
for ($number = 0; $number < 20000000; $number++) {
  $class = $flip[$class];
}
$time_end = gettimeofday();
$array_flip_time = timediff($time_start, $time_end);

$time_start = gettimeofday();
for ($number = 0; $number < 20000000; $number++) {
  $class = $class == 'even' ? 'odd' : 'even';
}
$time_end = gettimeofday();
$tri_flip_time = timediff($time_start, $time_end);

echo "Old time (%) is $old_time seconds\n";
echo "Old time (&) is $old_time2 seconds\n";
echo "Array with & time is $old_time3 seconds\n";
echo "Array flip time is $array_flip_time seconds\n";
echo "Trinary flip time is $tri_flip_time seconds\n";
?>
hunmonk’s picture

@vhmauery: again, performance was only one element of this patch. using either the modulus or & operator results in a situation where the even/odd class assignments are incorrectly assigned, if the $rows array was built using explicitly defined keys.

the array flip or the trinary operator are the only two correct implementations -- the others should _not_ be considered.

douggreen’s picture

@hunmonk, I get it now. As you probably know, chx sent email to the dev list pointing to this trick in regards to array efficiency, which is what got me thinking about performance.

@vhmauery, one final comment, my test results, even for 200,000,000, are quite different than yours, probably due to the difference between php4 and php5. On php5, the % changed to & accounts would of given a good speed boost. But hats off to hunmonk whose array solution, not only is the right functional solution, it's elegant, and it's faster!

hunmonk’s picture

credit to chx for the array solution -- i was merely the vehicle... :)

Anonymous’s picture

Status: Fixed » Closed (fixed)