За последние 24 часа нас посетили 72283 программиста и 1653 робота. Сейчас ищут 909 программистов ...

То, чего мы не знаем о PHP, но знать полезно и нужно!

Тема в разделе "Прочие вопросы по PHP", создана пользователем Psih, 16 июл 2008.

  1. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    Я подписан на 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):
    1.  
    2. lstat('/home')
    3. lstat('/home/username')
    4. lstat('/home/username/htdocs')
    5. lstat('/home/username/htdocs/myproject')
    6. lstat('/home/username/htdocs/myproject/web')
    А как известно (а для некоторых это откровение) - системные вызовы очень дорого обходятся и съедают довольно много ресурсов, что сказывается на скорости работы.
    и не спасает даже stat_cache в php.ini - он конечно даёт ускорение, но как пишет автор, оно не работает в полной мере и всёравно довольно много системных вызовов всёравно делается.
    Между прочим при include/require(_once) это тоже делается. Т.е. если у вас много инклюдов - вы попали.

    Он провёл небольшой тест, вынес свой сайт из папки (где-то из вложености 4-5 уровня) в корень фаиловой системы (/) и получил примерно ~33% ускорение, т.е. это просто ахтунг какое ускорение.

    Вообщем советую прочитать что там пишут, так как я не описывал подробности и нюансы (а они есть при определённых условиях).

    Для использующих open_basedir это не актуально, так как в таком случае такая операция обязательна. Т.е. они ничего не могут с этим сделать.
    Так же есть патчи, позволяющие исправить эти промашки, смотрите лист рассылки.

    Позже постараюсь выложить более подробный материал здесь и на хабре (если меня не опередят)
     
  2. Sergey89

    Sergey89 Активный пользователь

    С нами с:
    4 янв 2007
    Сообщения:
    4.796
    Симпатии:
    0
    Помоему тот же eAccelerator избавит от этой проблемы.
     
  3. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    Вообще то не совсем, если stat включен, что бы отслеживать изменения фаилов, то он это делает тоже. Т.е. тут факт в том, что это внутренний механизм PHP и кешеры тут даже не причём. Темболее это распространяется на все фаилы, что бы вы не открывали локально через fopen или другую функцию (те же file_get_contents и file_put_contents)
     
  4. vasa_c

    vasa_c Активный пользователь

    С нами с:
    22 мар 2006
    Сообщения:
    1.760
    Симпатии:
    0
    Адрес:
    гор.Ленинград
    33% ускорения ваще?
     
  5. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    Если много 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-ти, так что ускорение есть.
     
  6. Anonymous

    Anonymous Guest

    Ага, а теперь вспомним функцию 440hz (Или Zend_Framework_Loader) для автолоада классов, где сканируется пяток директорий, множим на число классов, множим на вложенность, получаем количество lstat ... чувствую, можно будет охуеть.
     
  7. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    Горбунов Олег
    Можешь проверить и сравнить что к чему :)
     
  8. Sergey89

    Sergey89 Активный пользователь

    С нами с:
    4 янв 2007
    Сообщения:
    4.796
    Симпатии:
    0
    Новое поведение встроенного обработчика ошибок. Теперь если запретить вывод ошибок на экран, то мы получим 500 статус в ответе сервера. Это нововведение PHP 5.2.4.

    На днях я столкнулся с такой проблемой:
    PHP:
    1. <?php
    2. ini_set('display_errors', 0);
    3. eval('aa|bb/cc');
    4. print 'Hello, world!';
    После выполнения eval мы видим ожидаемый результат, тоесть строку "Hello, world!", но в заголовках статус 500. Разработчики PHP посчитали это нормальным поведением и поставили статус Bogus на моём репорте. Как вы считаете, нормальное ли это поведение?
     
  9. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    Sergey89
    У меня Display errors 0 и PHP 5.2.6 с lighttpd 1.5 - всё ок.
     
  10. Sergey89

    Sergey89 Активный пользователь

    С нами с:
    4 янв 2007
    Сообщения:
    4.796
    Симпатии:
    0
    Не может быть всё Ок :) Т.к. это документированное поведение.

    Код (Text):
    1. HTTP/1.x 500 Internal Server Error
    Вот что получает браузер. При этом остальной код выполняется нормально и результат выводится в браузер.
     
  11. Sergey89

    Sergey89 Активный пользователь

    С нами с:
    4 янв 2007
    Сообщения:
    4.796
    Симпатии:
    0
    Вот какую логику преследуют разработчики.
     
  12. Sergey89

    Sergey89 Активный пользователь

    С нами с:
    4 янв 2007
    Сообщения:
    4.796
    Симпатии:
    0
    Вот подтверждение:
    Код (Text):
    1.     /* Bail out if we can't recover */
    2.     switch (type) {
    3.         case E_CORE_ERROR:
    4.             if(!module_initialized) {
    5.                 /* bad error in module startup - no way we can live with this */
    6.                 exit(-2);
    7.             }
    8.         /* no break - intentionally */
    9.         case E_ERROR:
    10.         case E_RECOVERABLE_ERROR:
    11.         case E_PARSE:
    12.         case E_COMPILE_ERROR:
    13.         case E_USER_ERROR:
    14.             EG(exit_status) = 255;
    15.             if (module_initialized) {
    16.                 if (!PG(display_errors) &&
    17.                     !SG(headers_sent) &&
    18.                     SG(sapi_headers).http_response_code == 200
    19.                 ) {
    20.                     sapi_header_line ctr = {0};
    21.  
    22.                     ctr.line = "HTTP/1.0 500 Internal Server Error";
    23.                     ctr.line_len = strlen(ctr.line);
    24.                     sapi_header_op(SAPI_HEADER_REPLACE, &ctr TSRMLS_CC);
    25.                 }
    26.                 /* the parser would return 1 (failure), we can bail out nicely */
    27.                 if (type != E_PARSE) {
    28.                     /* restore memory limit */
    29.                     zend_set_memory_limit(PG(memory_limit));
    30.                     efree(buffer);
    31.                     zend_objects_store_mark_destructed(&EG(objects_store) TSRMLS_CC);
    32.                     zend_bailout();
    33.                     return;
    34.                 }
    35.             }
    36.             break;
    37.     }
     
  13. Sergey89

    Sergey89 Активный пользователь

    С нами с:
    4 янв 2007
    Сообщения:
    4.796
    Симпатии:
    0
    Более того. Проанализировав этот код, можно написать во такой вот:
    PHP:
    1.  
    2. <?php
    3. print 123;
    4. ini_set('display_errors', 0);
    5. eval('aa|bb/cc');
    и получить статус 200 или вот такой:
    PHP:
    1.  
    2. <?php
    3. print 123;
    4. ini_set('display_errors', 0);
    5. eval('aa|bb/cc');
    и получить статус 500.
     
  14. MiksIr

    MiksIr Активный пользователь

    С нами с:
    29 ноя 2006
    Сообщения:
    2.339
    Симпатии:
    44
    Угу, _наконец_ они это сделали ;) хох ;)
     
  15. MiksIr

    MiksIr Активный пользователь

    С нами с:
    29 ноя 2006
    Сообщения:
    2.339
    Симпатии:
    44
    Непонятно только почему это если отображение ошибки включено, то 200... как-будто от этого ошибка исчезает, хех
     
  16. Sergey89

    Sergey89 Активный пользователь

    С нами с:
    4 янв 2007
    Сообщения:
    4.796
    Симпатии:
    0
    Когда ошибка в основном коде, то это хорошо, что статус 500. Но когда ошибка в eval и код выполняется дальше, это нифига не логично как-то.
     
  17. MiksIr

    MiksIr Активный пользователь

    С нами с:
    29 ноя 2006
    Сообщения:
    2.339
    Симпатии:
    44
    Фига все логично. eval - это что, не часть кода? не часть твоей логики? А если ты подразумеваешь, что там может быть ошибка, то озаботься сам об обработке
     
  18. MiksIr

    MiksIr Активный пользователь

    С нами с:
    29 ноя 2006
    Сообщения:
    2.339
    Симпатии:
    44
    А, ну в принципе понятно почему 200 выдают при включенном выводе ошибок. Явно не потому, что так правильно, а просто что бы вывод не заменялся на 500-ю страницу где-нить попути.
     
  19. Sergey89

    Sergey89 Активный пользователь

    С нами с:
    4 янв 2007
    Сообщения:
    4.796
    Симпатии:
    0
    А хуле этот eval не останавливает работу скрипта? Если он часть логики страницы.
     
  20. Elkaz

    Elkaz Старожил
    Команда форума Модератор

    С нами с:
    26 июн 2006
    Сообщения:
    3.373
    Симпатии:
    0
    Адрес:
    Баку, Азербайджан
    С моей точки зрения, Сергей прав. Каким образом страница отображает браузеру что-то, если на самом деле сервер вернул 500-ую ошибку? Нелогично. Дело не в обработке.
     
  21. MiksIr

    MiksIr Активный пользователь

    С нами с:
    29 ноя 2006
    Сообщения:
    2.339
    Симпатии:
    44
    Хотя может ты и прав...
     
  22. MiksIr

    MiksIr Активный пользователь

    С нами с:
    29 ноя 2006
    Сообщения:
    2.339
    Симпатии:
    44
    Если сервер вернул 500-ю ошибку, это значит _ошибка_ и отображать нужно сообщение об ошибке. А не криво работающий сайт, как это делают многие (вернее просто не делают ничего).
     
  23. Anonymous

    Anonymous Guest

    Это то понятно. Непонятно, зачем выдавать 500, это то как раз и мешает сделать:
     
  24. MiksIr

    MiksIr Активный пользователь

    С нами с:
    29 ноя 2006
    Сообщения:
    2.339
    Симпатии:
    44
    С eval-ом соглашусь... ну тут скорее фигня в том, что у eval-а вообще проблема отлавливать ошибки ;) так что.. не юзайте eval, это очень плохо ;))
    А правильная логика ложна быть такая (она такая и есть)
    throw - 500
    try { throw } catch {} - 200
     
  25. Sergey89

    Sergey89 Активный пользователь

    С нами с:
    4 янв 2007
    Сообщения:
    4.796
    Симпатии:
    0
    теперь так будем всегда писать:

    PHP:
    1. <?php
    2. if (!eval('aa|bb/cc') && !headers_sent()) {
    3.     if (PHP_SAPI != 'cgi') {
    4.         header('HTTP/1.0 200 Ok');
    5.     } else {
    6.         header('Status: 200 Ok');
    7.     }
    8.  
    9.     print 'Ошибка в eval';
    10. }