Я подписан на PHP internal mailing list, посему читаю его ежедневно и постоянно, даже принимаю участие в обсуждениях новых фитч и оптимизаций. И переодически натыкаюсь на весьма интересные вещи, а иногда на действительно что-то почти сенсационное. Такое случилось вчера, о чём я напишу ниже. Давайте использовать этот топик для обсуждения листа рассылки PHP - новые фитчи, любопытные баги и оптимизации - вообщем всё что представляет интерес для нас, как для конечных пользователей языка. Итак, о птичках - всё началось вот с этого сообщения - http://news.php.net/php.internals/38961 . Оно расположено на этой странице: http://news.php.net/php.internals/start/38942 Дальше смотрите сделедующие страницы по "Re: lstat call on each directory level" Для тех, кто английский знает хреново или незнает вообще опишу кратко что по чём, остальным советую читать оригинал. И так, суть в том, что PHP на каждую parent директорию файла делает системный вызов lstat. Т.е. если у вас файл лежит в /home/username/htdocs/myproject/web/somefile.txt, то при просмотре действия fopen через strace вы увидите примерно следующее Код (Text): lstat('/home') lstat('/home/username') lstat('/home/username/htdocs') lstat('/home/username/htdocs/myproject') lstat('/home/username/htdocs/myproject/web') А как известно (а для некоторых это откровение) - системные вызовы очень дорого обходятся и съедают довольно много ресурсов, что сказывается на скорости работы. и не спасает даже stat_cache в php.ini - он конечно даёт ускорение, но как пишет автор, оно не работает в полной мере и всёравно довольно много системных вызовов всёравно делается. Между прочим при include/require(_once) это тоже делается. Т.е. если у вас много инклюдов - вы попали. Он провёл небольшой тест, вынес свой сайт из папки (где-то из вложености 4-5 уровня) в корень фаиловой системы (/) и получил примерно ~33% ускорение, т.е. это просто ахтунг какое ускорение. Вообщем советую прочитать что там пишут, так как я не описывал подробности и нюансы (а они есть при определённых условиях). Для использующих open_basedir это не актуально, так как в таком случае такая операция обязательна. Т.е. они ничего не могут с этим сделать. Так же есть патчи, позволяющие исправить эти промашки, смотрите лист рассылки. Позже постараюсь выложить более подробный материал здесь и на хабре (если меня не опередят)
Вообще то не совсем, если stat включен, что бы отслеживать изменения фаилов, то он это делает тоже. Т.е. тут факт в том, что это внутренний механизм PHP и кешеры тут даже не причём. Темболее это распространяется на все фаилы, что бы вы не открывали локально через fopen или другую функцию (те же file_get_contents и file_put_contents)
Если много include и работы с фаилами и не используются абсолютные пути с неправильным или содержательным include_path - то да, издержки довольно велики. В моём случае из-за неправильного include_path + неправильный инклюдинг фаилов приводил к 10 lstat с кучей ошибок File not found (в strace конечно) на файл, файлов инклюдилось 6. В большими include_path всё может быть ещё гораздо хуже. Мой вывод - это актуально. Вот тот самый strace http://pastebin.com/m37704b6a Это уже правленный вариант require относительно текущей директории скрипта без нормального include_path, т.е. он ищет криво только bittorrent.php. А раньше искал ВСЕ фаилы так, как в strace'e - т.е. кол-во lstat исчислялось как минимум 3-мя десяткам, что есть плохо. ab в этот раз выжал больше 90 req/sec с главной при -n 1000 -c 150, впринципе раньше было чуть меньше 70-ти, так что ускорение есть.
Ага, а теперь вспомним функцию 440hz (Или Zend_Framework_Loader) для автолоада классов, где сканируется пяток директорий, множим на число классов, множим на вложенность, получаем количество lstat ... чувствую, можно будет охуеть.
Новое поведение встроенного обработчика ошибок. Теперь если запретить вывод ошибок на экран, то мы получим 500 статус в ответе сервера. Это нововведение PHP 5.2.4. На днях я столкнулся с такой проблемой: PHP: <?php ini_set('display_errors', 0); eval('aa|bb/cc'); print 'Hello, world!'; После выполнения eval мы видим ожидаемый результат, тоесть строку "Hello, world!", но в заголовках статус 500. Разработчики PHP посчитали это нормальным поведением и поставили статус Bogus на моём репорте. Как вы считаете, нормальное ли это поведение?
Не может быть всё Ок Т.к. это документированное поведение. Код (Text): HTTP/1.x 500 Internal Server Error Вот что получает браузер. При этом остальной код выполняется нормально и результат выводится в браузер.
Вот подтверждение: Код (Text): /* Bail out if we can't recover */ switch (type) { case E_CORE_ERROR: if(!module_initialized) { /* bad error in module startup - no way we can live with this */ exit(-2); } /* no break - intentionally */ case E_ERROR: case E_RECOVERABLE_ERROR: case E_PARSE: case E_COMPILE_ERROR: case E_USER_ERROR: EG(exit_status) = 255; if (module_initialized) { if (!PG(display_errors) && !SG(headers_sent) && SG(sapi_headers).http_response_code == 200 ) { sapi_header_line ctr = {0}; ctr.line = "HTTP/1.0 500 Internal Server Error"; ctr.line_len = strlen(ctr.line); sapi_header_op(SAPI_HEADER_REPLACE, &ctr TSRMLS_CC); } /* the parser would return 1 (failure), we can bail out nicely */ if (type != E_PARSE) { /* restore memory limit */ zend_set_memory_limit(PG(memory_limit)); efree(buffer); zend_objects_store_mark_destructed(&EG(objects_store) TSRMLS_CC); zend_bailout(); return; } } break; }
Более того. Проанализировав этот код, можно написать во такой вот: PHP: <?php print 123; ob_flush(); ini_set('display_errors', 0); eval('aa|bb/cc'); и получить статус 200 или вот такой: PHP: <?php print 123; ini_set('display_errors', 0); eval('aa|bb/cc'); и получить статус 500.
Непонятно только почему это если отображение ошибки включено, то 200... как-будто от этого ошибка исчезает, хех
Когда ошибка в основном коде, то это хорошо, что статус 500. Но когда ошибка в eval и код выполняется дальше, это нифига не логично как-то.
Фига все логично. eval - это что, не часть кода? не часть твоей логики? А если ты подразумеваешь, что там может быть ошибка, то озаботься сам об обработке
А, ну в принципе понятно почему 200 выдают при включенном выводе ошибок. Явно не потому, что так правильно, а просто что бы вывод не заменялся на 500-ю страницу где-нить попути.
С моей точки зрения, Сергей прав. Каким образом страница отображает браузеру что-то, если на самом деле сервер вернул 500-ую ошибку? Нелогично. Дело не в обработке.
Если сервер вернул 500-ю ошибку, это значит _ошибка_ и отображать нужно сообщение об ошибке. А не криво работающий сайт, как это делают многие (вернее просто не делают ничего).
С eval-ом соглашусь... ну тут скорее фигня в том, что у eval-а вообще проблема отлавливать ошибки так что.. не юзайте eval, это очень плохо ) А правильная логика ложна быть такая (она такая и есть) throw - 500 try { throw } catch {} - 200
теперь так будем всегда писать: PHP: <?php if (!eval('aa|bb/cc') && !headers_sent()) { if (PHP_SAPI != 'cgi') { header('HTTP/1.0 200 Ok'); } else { header('Status: 200 Ok'); } print 'Ошибка в eval'; }