20 апр. 2011 г.

The PHP Benchmark



READ LOOP: foreach()    vs.    while(list()=each())
What is the best way to loop a hash array?
Given is a Hash array with 100 elements, 24byte key and 10k data per entry
I've chosen the large data amount to try out what happens if I reference the data with the &-ref-operator (to avoid copying). But to my surprise the loops are never faster! In tests 5 and 6 are even 10x - 30x slower !! The larger the data entrys are the slower the tests 5 and 6 get! Copying seams always faster then using the &-ref-operator.
Way ???
Let me know at bs_php@users.sourceforge.net
+  100 %1: foreach($aHash as $val);Total time:    0[ms]
+ 1241 %2: while(list(,$val) = each($aHash));Total time:    1[ms]
+ 4798 %3: foreach($aHash as $key=>$val);Total time:    5[ms]
+ 2563 %4: while(list($key,$val)= each($aHash));Total time:    3[ms]
+ 2066 %5: foreach($aHash as $key=>$val) $tmp[] = &$aHash[$key];Total time:    2[ms]
+ 2099 %6: while(list($key) = each($aHash)) $tmp[]=&$aHash[$key];Total time:    2[ms]
+  612 %7: Get key-/ value-array: foreach($aHash as $key[]=>$val[]);Total time:    1[ms]
+  381 %8: Get key-/ value-array: array_keys() / array_values()Total time:    0[ms]
+  554 %9: STRANGE: This is the fasetest code when using the the &-ref-operator (to avoid copying)
$key = array_keys($aHash);
$size = sizeOf($key);
for ($i=0; $i<$size; $i++) $tmp[] = &$aHash[$key[$i]];
Total time:    1[ms]
Conclusion:
It must have something to do with PHP4 variable ref-count So you can safely use foreach and only use the &-ref-operator when realy needed OR (according to the link above) when passing objects to functions. (Thanx to Wayne for his help)


Test:
MODIFY LOOP: foreach()    vs.    while(list()=each())
While the above test only reads and copies the data the question arised what would happen if I modify each value of the hash above.
Again I an unexpected result. Even if I reduce the data size to 100 byte p. e. it ends up that Nr.3 is 1.5 - 2x faster.
+  372 %1: foreach($aHash as $key=>$val) $aHash[$key] .= "a";Total time:    2[ms]
+  174 %2: while(list($key) = each($aHash)) $aHash[$key] .= "a";Total time:    1[ms]
+  100 %3: STRANGE: This is the fasetest code :
$key = array_keys($aHash);
$size = sizeOf($key);
for ($i=0; $i<$size; $i++) $aHash[$key[$i]] .= "a";
Total time:    1[ms]
Conclusion:
Use foreach unless the hash is lage AND has lage data elements. In that case use variation Nr.3 .


Test:
For-loop test
Is it worth the effort to calculate the length of the loop in advance?
E.g. "for ($i=0; $i<$size; $i++)" instead of "for ($i=0; $i<sizeOf($x); $i++)"
+  100 %1: With pre calcTotal time:    1[ms]
+ 1537 %2: Without pre calcTotal time:   11[ms]
Conclusion:
The test above speeks for it self. Always calculate the length of the loop in advance!


Test:
Using the &-ref-operator as so called "alias"
Is a good idea to use the &-ref-operator to substitute (or alias) a complex mutidim-array? . Call 1'000x
E.g. $person = &$aHach["country"]["zip"]["streat"]["number"]["name"]
+  107 %1: NO Aliasing. Using: $aSingleDimArray[$i]Total time:    1[ms]
+  100 %2: Aliasing. Using: $alias = &$aSingleDimArray[$i]Total time:    1[ms]
+  178 %3: NO Aliasing. Using: $aMultiDimArray[$i]["aaaaa"]["aaaaaaaaaa"]Total time:    1[ms]
+  122 %4: Aliasing. Using: $alias = &$aMultiDimArray[$i]["aaaaa"]["aaaaaaaaaa"]Total time:    1[ms]
+  268 %5: NO Aliasing. Using: veryMultiDimArray[$i]["a"]["aa"]["aaa"]["aaaa"]["aaaaa"]Total time:    2[ms]
+  152 %6: Aliasing. Using: $alias = &$veryMultiDimArray[$i]["a"]["aa"]["aaa"]["aaaa"]["aaaaa"]Total time:    1[ms]
Conclusion:
It seams to be ok to use aliases. It also makes the code more readabel. But I was expecting to get a lager performance gain; especially with very multdimetional arrays.


Test:
$obj = new SomeClass()    vs.    $obj =& new SomeClass() using the =&-ref-operator
Is a good idea to use the =&-ref-operator when creating a new object? Call 1'000x
+  100 %1: $obj = new SomeClass()Total time:    1[ms]
+  114 %2: $obj =& new SomeClass()Total time:    1[ms]
+  249 %3: $obj =& $someClass->f();Total time:    2[ms]
+  137 %4: $obj = $someClass->f();Total time:    1[ms]
Conclusion:
There seams to be no difference in performance.


Test:
double (")    vs.    single (') quotes
Is a there a difference in using double (") and single (') quotes for strings. Call 1'000x
+  113 %1: single (') quotes. Just an empty string: $tmp[] = '';Total time:    0[ms]
+  103 %2: double (") quotes. Just an empty string: $tmp[] = "";Total time:    0[ms]
+  100 %3: single (') quotes. 20 bytes Text : $tmp[] = 'aaaaaaaaaaaaaaaaaaaa';Total time:    0[ms]
+  102 %4: double (") quotes. 20 bytes Text : $tmp[] = "aaaaaaaaaaaaaaaaaaaa";Total time:    0[ms]
+  104 %5: single (') quotes. 20 bytes Text and 3x a $ : $tmp[] = 'aa $ aaaa $ aaaa $ a';Total time:    0[ms]
+  100 %6: double (") quotes. 20 bytes Text and 3x a $ : $tmp[] = "aa $ aaaa $ aaaa $ a";Total time:    0[ms]
+  104 %7: double (") quotes. 20 bytes Text and 3x a \$ : $tmp[] = "aa \$ aaaa \$ aaaa \$ a";Total time:    0[ms]
Conclusion:
Single and double quoted strings behave almost the same with one exception: Don't use the a lonely ($) in double quoted string unless you want to reference a PHP-var; or use (\$).


Test:
isSet()    vs.    empty()    vs.    is_array()
What is the performance of isSet() and empty(). Call 2'000x
+  127 %1: isSet() with var that was setTotal time:    0[ms]
+  119 %2: empty() with var that was setTotal time:    0[ms]
+  106 %3: isSet() with var that was *not* setTotal time:    0[ms]
+  102 %4: empty() with var that was *not* setTotal time:    0[ms]
+  119 %5: isSet() with array-var that was setTotal time:    0[ms]
+  104 %6: empty() with array-var that was setTotal time:    0[ms]
+  100 %7: isSet() with array-var that was *not* setTotal time:    0[ms]
+  104 %8: empty() with array-var that was *not* setTotal time:    0[ms]
+  177 %9: is_array() of an arrayTotal time:    1[ms]
+  176 %10: is_array() of a stringTotal time:    1[ms]
+  446 %11: is_array() of a non set valueTotal time:    2[ms]
+  124 %12: isSet() AND is_array() of a non set valueTotal time:    0[ms]
Conclusion:
isSet() and empty() are identical. Interesting that a is_array() on a unset val is 3x slower. So alway check if val is set at all befor using type-checking. E.g. if (isSet($foo) AND is_array($foo))


Test:
switch/case    vs.    if/elseif
Is a there a difference between switch and if elseif. Call 1'000x
+  129 %1: if and elseif (using ==)Total time:    0[ms]
+  100 %2: if and elseif (using ===)Total time:    0[ms]
+  128 %3: caseTotal time:    0[ms]
Conclusion:
Using a switch/case or if/elseif is almost the same. Note that the test is unsing === and is slitly faster then using ==.




http://www.php.lt/benchmark/phpbench.php
http://www.phpbench.com

17 апр. 2011 г.

MySQL, удаление дубликатов

Существует множество универсальных решений данной задачи, но в MySQL всё решается ещё проще:

ALTER IGNORE TABLE my_table ADD UNIQUE INDEX(a, b);

12 апр. 2011 г.

Что нового в PHP 5.3

Итак, посмототрим – готовы ли вы к PHP-5.3.

  • Появились новые ключевые слова: namespace , goto , Closure.
  • Функции natsort(), natcasesort(), usort(), uasort(), uksort(), array_flip() и array_unique() больше не поддерживают объекты в качестве аргументов. Если вам нужно работать с объектами – вам придется перевести их в массивы.
  • Вывод var_dump теперь включает приватные члены.
  • session_start возвращает false, если старт сессии потерпел неудачу.

  • clearstatcache не очищает realpath cache по умолчанию
  • call_user_func() теперь передает $this, даже если вызывающий – есть родительский класс
  • opendir(), scandir() и dir() теперь использую умолчательный контекст, если контекст не передан им в качестве аргумента

  • Добавлена нативная поддержка функций asinh(), acosh(), atanh(), log1p() и expm1().
  • Stream wrappers теперь могут быть использованы как include_path()
  • Магические методы get(), set(), isset(), unset() и call() теперь всегда должны быть public и больше не могут быть static

    *
    toString() больше не может принимать аргументы

  • Введен новый магический метод callStatic()

    * Новый параметр previous в Exception::
    construct()
  • SplObjectStorage теперь имеет поддержку ArrayAccess. Это так же позволяет хранить ассоциативную информацию с объектами в SplObjectStorage.
  • ereg-функции вызывают E_DEPRECATED , пользуйтесь PCRE
  • Следующие ini-директивы вызывают E_DEPRECATED при старте
  • – define_syslog_variables
  • – register_globals

  • – register_long_arrays
  • – safe_mode
  • – magic_quotes_gpc
  • – magic_quotes_runtime
  • – magic_quotes_sybase
  • is_a() – больше не является DEPRECATED (устаревшей)

5 апр. 2011 г.

SQL_CALC_FOUND_ROWS и с постраничный вывод информации

Любой web-программист сталкивался с постраничным выводом информации. Например, в таблице лежит 1000 новостей и нужно выводить их по 10 штук на странице, а внизу вывести листалку страниц.

Программист пишет 2 запроса:
1) SELECT * FROM news WHERE activity=1 LIMIT 0, 10 // выборка записей
2) SELECT COUNT(*) FROM news WHERE activity=1// узнаем кол-во всех новостей
Второй запрос нужен для вывода листалки. Однако в MySQL есть достаточно удобная возможность объединения этих двух запросов в один.

В запросе SELECT перед списком столбцов необходимо указать опцию SQL_CALC_FOUND_ROWS. Вот начало описания синтаксиса конструкции SELECT.
SELECT
[ALL | DISTINCT | DISTINCTROW ]
[HIGH_PRIORITY]
[STRAIGHT_JOIN]
[SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT]
[SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS]
select_expr, …

Таким образом, выполняя запрос SELECT SQL_CALC_FOUND_ROWS MySQL подсчитает полное число строк, подходящих под условие запроса без LIMIT, и сохранить это число в памяти. Естественно, имеет смысл запрос SELECT SQL_CALC_FOUND_ROWS только при использовании ограничения (LIMIT). Сразу после выполнения запроса на выборку для получения количества записей нужно выполнить еще один SELECT-запрос: SELECT FOUND_ROWS ();. В результате MySQL вернет одну строку с одним полем, в котором и будет храниться число строк.
Пример запросов:
1) SELECT SQL_CALC_FOUND_ROWS * FROM news WHERE activity=1 LIMIT 10
2) SELECT FOUND_ROWS();
Первый запрос вернет (выведет) 10 строк таблицы news, для которых выполняется условие activity=1. Второй вызов команды SELECT возвратит количество строк, которые возвратила бы первая команда SELECT, если бы она была написана без выражения LIMIT. Хотя при использовании команды SELECT SQL_CALC_FOUND_ROWS, MySQL должен пересчитать все строки в наборе результатов, этот способ все равно быстрее, чем без LIMIT, так как не требуется посылать результат клиенту.
Пример запросов из PHP:
$result = mysql_query("SELECT SQL_CALC_FOUND_ROWS * FROM news LIMIT  10");
while ($row = mysql_fetch_assoc($result)) {
    var_dump($row);
}
$num_rows = mysql_num_rows($result);
echo "$num_rows Rows";
В результате выполнения кода PHP выведет 10 строк из таблицы news , а затем целочисленное значение количества строк, соответствующих запросу (без учета LIMIT).

4 апр. 2011 г.

Singleton - шаблон проектирования

=== Пример на PHP 5 ===

<?php

class Singleton {



  protected static $instance; // object instance


  /**

   * Защищаем от создания через new Singleton

   *

   * @return Singleton

   */

  private function __construct() { /* ... */ }


  /**

   * Защищаем от создания через колонирование

   *

   * @return Singleton

   */

  private function __clone() { /* ... */ }


  /**

   * Возвращает единственный экземпляр класса

   *

   * @return Singleton

   */

  public static function getInstance() {

    if ( is_null(self::$instance) ) {

      self::$instance = new self;

    }

    return self::$instance;

  }


  public function doAction() { /* ... */ }


}


//usage

Singleton::getInstance()->doAction();

?>


* This source code was highlighted with Source Code Highlighter.