от Andrey(20-12-2002)

рейтинг (3)   [ добре ]  [ зле ]

Printer Friendly Вариант за отпечатване

file_get_contents()

Поне до момента на излизане на 4.3.0 са ми познати 3 начина за извличане съдържанието на файл от диска или въобще на файл, когато се използва потоци.
Пример 1
$content = implode('', file($fname));
Коментар :
Това е един от най-често използваните методи. Може да го срещнете в много скриптове. Достатъчно бърз. Може да участва в израз.
Недостатъци :
В повечето случаи този код ще работи, но има изключения. До версия 4.3.0 функцията file() не е binary-safe. Т.е. за текстови файлове няма да има проблеми. Проблемите възникват във момента, в който решите да отваряте файл, който съдържа символа на '\0'. Начин за избягване : вж. Пример 2

Пример 2
$fd = fopen($fname, 'r'); // за уиндоус трябва да е 'rb'
 $content = fread($fd, filesize($fname));
 fclose($fd);
 $content = implode('', file($fname));
Коментар :
Това е един от по-малко използван метод. Избягва проблема на предишният метод за версии < 4.3.0 . Скоростта му е някъде около тази на предишният метод, но мисля малко по-бавен.
Недостатъци :
Може би единственият му недостатък е, че се пише на 3 реда код и не може да бъде част от израз, както е при предишният метод.

Пример 3
ob_start();
 readfile($fname);
 $content = ob_get_contents();
 ob_end_clean();
 $content = implode('', file($fname));
Коментар :
Това е един от малко използван метод. Рядкост е да го срещнете в скрипт. Използва се техниката на буфериране на изхода, която не е много популярна. Определено най-бързият от всички показани до момента методи, но за сметка на това се използват повече ресурси (главно памет). Недостатъци :
Както и Пример 2, не може да участва в израз. Не във всички случаи е недостатък, но ако паметта ви е кът, тогава не Ви го препоръчвам.

Пример 4 : Новата функция
$content = file_get_contents();
Коментар :
Тази функция се явява еволюционна, защото все пак не е хубаво за скриптов език, който се използва широко за извличане съдържанието на файл да се ползват накуп най-малко 2 функции. Това ще бъде вече най-бързият метод за четене при това е binary-safe. Като вътрешност функцията много наподобява readfile(), но връща резултата вместо да го изкарва на изхода.

version_compare() и дебъгване

Функцията се използва са сравнение на два низа, които съдържат версии по PHP стандарта.
Пример 5
<?php
 $Debug_level = 2|4|8|16|32|1024;
 
 if (($Debug_level & 1024) && version_compare(PHP_VERSION , "4.3.0-dev",'<')) {
         $Debug_level -= 1024;
 }
 define('DEBUG_MODE', $Debug_level);
 error_reporting(E_ALL);
 
 ...
 ...
 (DEBUG_MODE & 1024) && log_printf("[Elapsed in %s::%s = %2.5fs]\n",
 __CLASS__, __FUNCTION__, $timer->elapsed());
 ?>
Този пример показва как може да защитите код от изпълнение на някой версии на PHP. В случая на версии, които са преди 4.3.0-dev. Тази защита се налага защото предефинираните константи __CLASS__ и __FUNCTION__ се появяват през Април 2002г. във 4.3.0-dev.
За част oт читателите половината код може да е неразбираем. Особено последния ред. Използва се побитово сравнение (аритметичен OR). Това е техника широко използвана oт програмистите на C. Дава възможност само с една операция да се провери дали е трябва да се изпише дадена дебъг информация.
Пример 6
<?php
 (DEBUG_MODE & 5) && printf("Entering parsing and saving mode at : ".microtime()."\n");
 ?>
В този случай се проверява дали дебъг нивото е 1 или 4 printf() се изпълнява само когато е изпълнено да е "вдигнато" ниво и/или ниво 4. В случая ниво 1 означава всички съобщения, а 4 - информация за времето за изпълнение.
Плюс на този метод е че се събира на 1 ред.
Пример 7
<?php
 if ((DEBUG_MODE & 4) || (DEBUG_MODE & 1)) {
         printf("Entering parsing and saving mode at : ".microtime()."\n");
 }
 ?>
Както се вижда еквивалентен if оператор заема най-малко 2 реда. Разбира се може да се запише на 1 ред, но този запис ще е против всякаква конвенция за записване на този оператор. Разбира се мога да бъде обвинен, че метода който ползвам е неразбираем и объркващ, дори понякога загрозяващ кода, но определено пести редове видим код в редактора. А ако като свикнеш с него не ти прави впечатление. Подобно да вечният диспут къде да се пише отварящата лява скоба.

array_shift($ar)

Тази функция връща първият елемент от масива $ar като го премахва от там и пренарежда масива. Ако търсите производителност и не се нуждаете от това елементите ви в един масив да са с индекси започващи от нула, то не използвайте тази функция. За разлика от Perl и Python, тук PHP e бавен. Силно намалената производителност е следствие от факта, че всички елементи на масива трябва да бъдат обходени и техните индекси да бъдат преномерирани от 0. Почти никакво е забавянето от прехеширането.
Едно възможно решение само веднъж да си подредите масива по индекси (последователни), да правите unset() на първият елемент от масива, като използвате помощна променлива.
Пример 7
<?php
 $ar = array(1,1,2,3,5,8,13,21,34,55);
 $first_elem_idx = 0;
 $elem = $ar[$firs_elem_idx]; // 1
 unset($ar[$firs_elem_idx++]);
 
 $elem = $ar[$firs_elem_idx]; // 1
 unset($ar[$firs_elem_idx++]);
 
 $elem = $ar[$firs_elem_idx]; // 2
 unset($ar[$firs_elem_idx++]);
 ?>

do..while

do..while е цикъл с постусловие. Той се използва доста по-рядко от while. Дори доста често се правят трикове преди while за да се емулира на практика цикъла с постусловие. Тук обаче ще спомена една възможност, на тази конструкция, която намирам за много удобна :
Пример 8
<?php
 do {
         if ($i < 5) {
                 print "i is not big enough";
                 break;
         }
         $i *= $factor;
         if ($i < $minimum_limit) {
                 break;
         }
         print "i is ok";
         
         ...process i...
 } while(0);
 ?>
Примера е взет от документацията на PHP. Този цикъл се изпълнява само веднъж, всъщност това е псевдоцикъл, защото се използва само защото в него са достъпни break и continue;. Всъщност continue; не се използва защото ефекта е еквивалентен на break - излизане от блока. Може да се оприличи на изключенията познати на някой от C++, Java, Object Pascal. Разликата е, че обработката при изключителната ситуация в повечето случаи се прави в тялото на цикъла преди изпълнението на прехода.

array_map()

Освен обикновенното си поведение, тази функция има нещо интересно скрито в нея :
Пример 9
<?php
 $a = array(1, 2, 3, 4, 5);
 $b = array("one", "two", "three", "four", "five");
 $c = array("uno", "dos", "tres", "cuatro", "cinco");
 
 $d = array_map(null, $a, $b, $c);
 print_r($d);
 /*
 Изхода е :
 Array
 (
 [0] => Array
 (
 [0] => 1
 [1] => one
 [2] => uno
 )
 
 [1] => Array
 (
 [0] => 2
 [1] => two
 [2] => dos
 )
 
 [2] => Array
 (
 [0] => 3
 [1] => three
 [2] => tres
 )
 
 [3] => Array
 (
 [0] => 4
 [1] => four
 [2] => cuatro
 )
 
 [4] => Array
 (
 [0] => 5
 [1] => five
 [2] => cinco
 )
 )
 */
 ?>
Както се вижда, ако първият параметър е NULL тази функция смесва елементите на подадените и като параметри масиви в този, който се връща. Първият параметър се използва за предаване на име на "callback" функция.

Малко думи за производителност

За всеки, който е решил да тръгне по пътя на ООП в PHP e ясно, че кода му ще бъде по-бавен. Все пак ако толкова силно желаете или се нуждаете от всяка милисекунда то тогава има какво да направите по въпроса
Пример 10
<?php
 class CObject {
         var $property;
         
         function CObject() {
                 $this->property = 0;
         }
 }// class CObject
 
 class CLongLoop extends CObject {
         function CLongLoop() {
                 parent::CObject();
         }
         
         function do_long_loop_slow($how) {
                 for ($i=0; $i < $how; $i++) {
                         $this->property++;
                 }
         }
         
         function do_long_loop_fast($how) {
                 $this_property = &$this->property;
                 for ($i=0; $i < $how; $i++) {
                         $this_property++;
                 }
         }
 }// class CLongLoop
 
 $long_loop_obj = new CLongLoop();
 echo microtime()."<br>\n";
 $long_loop_obj->do_long_loop_slow(100000);
 echo microtime()."<br>\n";
 $long_loop_obj->do_long_loop_fast(100000);
 echo microtime()."<br>\n";
 
 echo microtime()."<br>\n";
 $long_loop_obj->do_long_loop_slow(100);
 echo microtime()."<br>\n";
 $long_loop_obj->do_long_loop_fast(100);
 echo microtime()."<br>\n";
 
 /*
 Изход на машина : Pentium 3 Celeron 700MHz, 256 RAM
 
 При 100 000 итерации
 
 0.00246600 1040316935
 0.32363900 1040316935
 0.54043900 1040316935
 
 -=-=-=
 Първи начин : ~ 0.32с
 Втори начин : ~ 0.22с
 
 
 При 100 итерации :
 0.38567500 1040317294
 0.38614000 1040317294
 0.38642200 1040317294
 Първи начин : ~ 0.00047с
 Втори начин : ~ 0.00028с
 
 */
 ?>
Както се вижда вторият начин е с около 30% по-бърз. Все пак трябва да имате в предвид, че цикъла се извърта 100 000 пъти. Това не е никак малко. За цикли с порядъци по-малко на брой итерации забавянето е незначително. Разбира се, ако в цикъла се използват повече "нелокални" променливи - член-променливи на класа скоростта пада пропорционално на техният брой. Защо се получава такава разлика? Четете в следващата точка.

Навътре в начина на работа на Zend Engine

Разликата се получава от спецификата на интерпретация на PHP скриптовете. Ако не използвате "кеш" за код, то ZendEngine
1)Зарежда скрипта от диска
2)Прави анализ
2.1)Синтактичен
2.2)Семантичен, като едновременно с него се генерира междинен код
3)Изпълнява генерираният междинният код
Междинният компилираният междинен код е това, което популярните продукти като ZendCache и PHPAccelerator кешират. В известна степен междинният код може да се и се оптимизира от тези продукти. Така изпълнението на скриптове може да се ускори значително.
Все пак нека да кажа нещо повече за междинният код. Това е асемблера на PHP. Инструкциите са подобни на тези в асемблерите за различни процесори, но съобразени със спецификата на "виртуалната машина". Опкодовете са над 110 във ZE1 и около 140 във ZE2. Когато едно присвояване се превръща в междинен код то се замества с няколко опкода. Зарежда се адреса на променливата, на която се присвоява стойност. Търси се по име(низ) в хеша на текущо активната функция (била тя и main() ). Този адрес се зарежда във временна променлива. След това ако на променливата се присвоява променлива се зарежда нейният адрес, и първата променлива става референция (да не се бърка с указател) към втората. Ако се присвоява израз, то се извлича резултата от временната променлива в която преди това е записан. Всичко това може да изглежда малко сложно, но на практика не е така.
Тази теория помага да се обяснени "Пример 10". Когато използват $this->property , се зарежда адреса на $this от хеша на известните променливи в текущият метод, след това в хеша съдържащ член-променливите на класа се извършва търсене по низ, за да се намери адрес на $this->property. Както се вижда разликата, е че при член променливи се извършва едно търсене повече. Все пак $this e една от първите декларирани променливи във всеки метод и търсенето е по-бързо.
Ако продължим с разсъжденията на база на горната теория може да се обясни защо са възможно това :
Пример 11
<?php
 $foo = 'bar';
 $bar = 'ok we got it';
 echo $$foo; // ok we got it
 echo $foo(); // In bar()
 
 function bar() {
         return "In bar()\n";
 }
 ?>
Съдържанието на $foo се използва при търсенето в хеша с променливите и се изписва съдържанието на $bar. При функцията е аналогично, търсенето става в хеша с декларираните функции. Примера може да се разшири дори и за създаване на инстанция на клас

Литература:

  1. Документацията на PHP
  2. Различни материали от Интернет
  3. Dev форумите на PHP
  4. Статия на Derick Rethans за това как работи ZE


<< Често задавани въпроси за Squid (част 2) | Как да накареме конзолата да щади очите ни. >>