Можете ли вы выяснить эту проблему с PHP? - PullRequest
14 голосов
/ 22 января 2011

Может кто-нибудь сказать мне, почему, когда я запустил скрипт с указанным ниже содержимым и затем остановил его через 5 секунд, мне нужно разделить прошедшее время на 2, чтобы получить правильное время выполнения скрипта?*

ignore_user_abort(true); set_time_limit(0); 

$begin_time = microtime(true);

$elapsed_time = 0;

while(!connection_aborted()) {
    echo ' ';
    flush();
    usleep(1000000);
}

$elapsed_time = microtime(true) - $begin_time;

$timer_seconds = $elapsed_time; //10 seconds

$timer_seconds = $elapsed_time / 2; //5 seconds


/*I am writing to a DB - but you can use this to test */
$fp = fopen('times.txt', 'w');
fwrite($fp, 'Time Elapsed: '.$timer_seconds);
fclose($fp);

Не стесняйтесь пробовать код, потому что он озадачил меня, почему $elapsed_time нужно разделить на два.Может быть, я что-то неправильно понял?

Спасибо всем за помощь

Обновление

Я обновил код, так что каждый может попробовать это, и он запишет в текстовый файл.для просмотра вывода.

Ответы [ 6 ]

13 голосов
/ 26 января 2011

Эксперимент:

Значительные изменения по сравнению с исходным кодом:

1) Использование implicit_flush и все буферы очищаются перед выполнением чего-либо.
2) Вместо выводавсего лишь пробел, код выводит номер итерации и 1023 байта других данных, чтобы сообщить браузеру, что у нас достаточно вывода для отображения.Обычный известный прием.
3) Наряду с экономией времени в выходном текстовом файле он также сохраняет общее количество итераций, которые выполнял код.

Используемый код:

<?php
// Tricks to allow instant output
@ini_set('implicit_flush', 1);
for ($i = 0; $i < ob_get_level(); $i++)
    ob_end_flush();
ob_implicit_flush(1);

//Your Code starts here
ignore_user_abort(true);
set_time_limit(0); 

$begin_time = microtime(true);
$elapsed_time = 0;

while(!connection_aborted())
{
    //this I changed, so that a looooong string is outputted
    echo $i++.str_repeat(' ',1020).'<br/>';
    flush();
    usleep(1000000);
}

$elapsed_time = microtime(true) - $begin_time;
$timer_seconds = $elapsed_time; //10 seconds

//Writes to file the number of ITERATIONS too along with time
$fp = fopen('4765107.txt', 'w');
fwrite($fp, 'Time Elapsed: '.$timer_seconds);
fwrite($fp, "\nIterations: ".$i);
fclose($fp);
?>

Демонстрация в реальном времени:


Что я получил:

1) Когда код запускается в течение 10 итераций и нажата кнопка STOP в браузеревыходной файл показывает 13 итераций с ~ 13,01 секундами.

2) Когда код выполняется в течение 20 итераций и нажата кнопка STOP в браузере, выходной файл показывает 23 итерации с ~ 23,01 секундами.


Выводы и выводы:

1) Сценарий фактически НЕ останавливается при нажатии кнопки СТОП, а через 2-4 секунды нажатия.Итак, есть больше итераций, чем в браузере.

2) Количество итераций одинаково, как количество секунд, необходимое для выполнения, как показано в выходном файле.

Поэтому, ошибки нет и, видимо, нет ошибок, это просто время задержки между нажатием кнопки STOP и остановкой скрипта.


Примечания:

1) Сервер: Linux VPS.
2) Проверенные клиенты: Firefox и Chrome.
3) Поскольку выполнение сценария заканчивается через 2-4 секунды после нажатия кнопки STOP, вывод занимает около 3-4 секунд.файл для обновления для текущего теста.

9 голосов
/ 29 января 2011

Резюме: (этот пост стал эпическим, когда я тестировал различные возможности)

PHP обычно использует две итерации цикла для обнаружения разъединения или для выдачи выходных данных.Эта задержка может исходить от программного обеспечения веб-сервера, хост-компьютера, клиентского компьютера и клиентского браузера, но тогда она должна варьироваться в зависимости от времени ожидания на одну итерацию.Скорее всего, задержка происходит из-за внутреннего выполнения или процесса вывода PHP (возможно, из небольшого внутреннего буфера или процесса обработки прерываний).

Epic Post:

ПодсчетВаше время выполнения из [Обновить] или URL-подтверждения не совсем точная отправная точка - сначала может потребоваться любое количество шагов, которое может увеличить задержку:

  1. Требуется поиск DNS (с использованием TCP)
  2. Установлено TCP-соединение с сервером
  3. Веб-сервер создает поток или дочерний элемент
  4. Веб-сервер решает, как обрабатывать запрос
  5. PHP может потребоваться запустить
  6. PHP может потребоваться преобразовать ваш источник в код операции

Поэтому вместо измерения [Обновить] -> [Стоп] время и сравнивая его с числом, записанным PHP, я измерил отображаемый вывод до записанный вывод - что уменьшает измерение задержки в основном только в пределах PHP (хотя сервер / браузер будет действоватьг).Измененный скрипт не может убежать (он завершается после фиксированного числа итераций), очищает буферизацию php.ini по умолчанию и сообщает счетчик итераций на экране и в файле синхронизации.Я запускал скрипт с различными $sleep периодами, чтобы увидеть эффекты.Окончательный сценарий:

<?php
date_default_timezone_set('America/Los_Angeles'); //Used by ob apparently
ignore_user_abort(true); //Don't terminate script because user leaves
set_time_limit(0); //Allow runaway script !danger !danger
while (@ob_end_flush()) {}; //By default set on/4K in php.ini

$start=microtime(true);

$n=1000;
$i=0;
$sleep=100000;// 0.1s
while(!connection_aborted() && $i<$n) {
    echo "\n[".++$i."]";flush();
    usleep($sleep);
}

$end=microtime(true);

file_put_contents("timing.txt",
    "#\$sleep=".($sleep/1000000).
      "s\n/ s=$start / e=$end ($i) / d=".($end-$start)."\n",
    FILE_APPEND);
?>

Результаты: (несколько запусков объединены, запуск в Firefox)

# On-screen $i / start utime / end utime (current $i) / delta utime

#$sleep=1s
2 / s=1296251342.5729 / e=1296251346.5721 (4) / d=3.999242067337
3 / s=1296251352.9094 / e=1296251357.91 (5) / d=5.000559091568
#$sleep=0.1s
11 / s=1296251157.982 / e=1296251159.2896 (13) / d=1.3075668811798
8 / s=1296251167.5659 / e=1296251168.5709 (10) / d=1.0050280094147
16 / s=1296251190.0493 / e=1296251191.8599 (18) / d=1.810576915741
4 / s=1296251202.7471 / e=1296251203.3505 (6) / d=0.60339689254761
16 / s=1296251724.5782 / e=1296251726.3882 (18) / d=1.8099851608276
#$sleep=0.01s
42 / s=1296251233.0498 / e=1296251233.5217 (44) / d=0.47195816040039
62 / s=1296251260.4463 / e=1296251261.1336 (64) / d=0.68735003471375
150 / s=1296251279.2656 / e=1296251280.901 (152) / d=1.6353850364685
379 / s=1296252444.7587 / e=1296252449.0108 (394) / d=4.2521529197693
#$sleep=0.001s
337 / s=1296251293.4823 / e=1296251294.1515 (341) / d=0.66925406455994
207 / s=1296251313.7312 / e=1296251314.1445 (211) / d=0.41328597068787
792 / s=1296251324.5233 / e=1296251326.0915 (795) / d=1.5682451725006

(Opera не отображает числа во время, но отображаетпоследние цифры, которые примерно совпадают)
(Chrome ничего не отображает во время / после теста)
(Safari ничего не показывает во время / после теста)
(IE ничего не отображает во время /после теста)

Первое число в каждой строке указывает число, отображаемое на экране после нажатия [стоп] (запись вручную).

Несколько точек:

  • Ваша точка останова квантуется до ближайшего $sleep периода (1/10 с в приведенном выше скрипте), потому что скрипт проверяет только в начале каждой во время итерации, есть некоторые различия, потому что метод usleep не идеалензадержка.
  • Браузер и сервер, который вы используете, имеют значение.Примечания на странице руководства flush «могут не иметь возможности переопределить схему буферизации вашего веб-сервера и не влияют на буферизацию на стороне клиента в браузере».Затем более подробно расскажем о проблемах как сервера, так и клиента.[ Сервер : WinXPsp3 / Apache 2.2.17 / PHP 5.3.3 Клиент : WinXPsp3 / FireFox 3.6.13]

Анализ:

Во всех случаях, кроме задержки 0,001 с, мы наблюдаем задержку в 2 итерации между [остановкой] и перехватом PHP (или отчетами Firefox или Apache).С задержкой 0,001 с она немного меняется, в среднем ~ 4 итерации или 0,004 с - это, вероятно, приближается к порогу скорости обнаружения.

Когда время задержки составляет 0,1 с или выше, мы видимвремя выполнения близко совпадает $sleep * {записанные итерации}.

Когда время задержки ниже 0,1 с, мы видим, что задержки выполнения превышают время ожидания.Вероятно, это связано со стоимостью проверки клиентского соединения, увеличением $i, выводом текста и очисткой буфера за итерацию.Расхождение между временем выполнения и $i*$sleep является довольно линейным, предполагая, что для выполнения этих задач требуется ~ 0,001 с / итерация (при сне в 0,01 с это 0,0008, в то время как в режиме сна 0,001 с получается 0,0010 - вероятно, в результате увеличения MALLOC / выход).

6 голосов
/ 22 января 2011

Вы полагаетесь на connection_aborted(), чтобы быть true в тот момент, когда вы нажали кнопку «Стоп» в своем браузере, но не показали никаких доказательств того, что вы убедились, что это так.На самом деле это не так.

Вы забыли, как в сети происходит проверка «соединение прервано».Приложение (в данном случае php) не знает, что произошло, пока не попытается записать в канал.

Первый комментарий к документации для connection_abort() говорит: «В порядкечтобы обнаружить разъединение внутри скрипта, нам нужно очистить буфер (только когда сервер попытается отправить содержимое буфера, он увидит, что соединение разорвано). "

Так что я не верючто вы могли бы надежно использовать connection_abort() таким образом.

Будьте уверены, microtime() работает правильно.

4 голосов
/ 22 января 2011

connection_aborted() может обнаруживать отключение только при отправке буфера.Но flush() не обязательно отправляет буфер.Таким образом, цикл продолжает повторяться, пока буфер не будет заполнен и действительно очищен.

Для получения более подробной информации см. Справочные страницы названных функций.

0 голосов
/ 01 февраля 2011

Это не столько «проблема», сколько «по замыслу»

Это функция работы http.

При отправке веб-страницы клиенту (браузеру) сервер «ДОЛЖЕН» отправить заголовок длины содержимого. Теперь он не может знать длину содержимого, пока не получит все из сценария.

Таким образом, серверы будут буферизовать вывод скрипта до его завершения.

Это - то, где капризы входят. В зависимости от сервера и даже различных версий того же самого сервера, он может или не может проверить, отключился ли клиент на регулярной основе, и если это произойдет, эти проверки могут быть разными временные интервалы. которая может даже меняться в зависимости от того, насколько занят сервер.

PHP не контролирует соединение с клиентом, он может только спросить сервер, если соединение все еще существует. Сервер может или не может сказать это правду. Таким образом, сценарий продолжает выполняться (в то время, когда сервер может не знать).

Так почему же Микуши работал после добавления ob_end_flush () при запуске сценария?

Ну, это потому, что он включил другую функцию http, называемую передачей чанка. Это позволяет отправлять данные порциями, каждая со специальной версией заголовка длины содержимого (фактически это не говорит об этом, просто отправляет следующую длину порции)

Попробуйте скрипт Микуши с Wireshark, и вы увидите кодировку, пример показан здесь

HTTP/1.1 200 OK
Date: Tue, 01 Feb 2011 11:52:35 GMT
Server: Apache/2.2.3 (CentOS)
X-Powered-By: PHP/5.1.6
Connection: close
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8


7 <== this is the content-length 
<pre>0

2 <== this is the content-length 
1

2 ditto ...
2

2
3

2
4

2
5

Так что это означает, что невозможно (да, Томалак, вы меня поняли :)) знать, когда сервер прервет соединение, и, следовательно, возвращать true для connection_aborted (), пока вы не протестируете реальный задействованный сервер. Потому что каждый по-своему. Даже веб-браузер может делать некоторые вещи, которые задерживают фактическое закрытие, что может еще больше осложнить проблему.

DC

0 голосов
/ 26 января 2011

Использование вашего скрипта из коробки не работает должным образом в Ubuntu, используя Chrome для доступа к странице. Цикл просто продолжается, пришлось перезапустить Apache. С другой стороны, добавление ob_end_flush () вверху решает эту проблему, плюс таймер на самом деле правильный.

ob_end_flush();
ignore_user_abort(true); 
set_time_limit(0);

$begin_time = microtime(true);

$elapsed_time = 0;

while(!connection_aborted()) {
    echo ' ';
    flush();
    usleep(1000000);
    error_log("looping");
}

$elapsed_time = microtime(true) - $begin_time;

$timer_seconds = $elapsed_time; 
error_log(var_export($timer_seconds, true));


$timer_seconds = $elapsed_time / 2; 
error_log(var_export($timer_seconds, true));

Если вы запустите это и посмотрите журнал ошибок, вы увидите, что $ elpased_time является правильным при первом запуске, не нужно делить и на. По тому, почему ваш код ведет себя так, я не знаю, так как он даже не работает на моей машине.

...