Параллелим процесс на PHP
Давно уже наткнулся на одну статью, автор которой приводит пример распараллеливания работы php-скриптов через сокеты, но некогда было ее прочитать. Ссылку на эту статью можете увидеть в конце поста. Вот, наконец, добрался до нее и после прочтения возникло дикое желание распараллелить всем известный пример вычисления числа PI на MPI. У меня двуядерная машинка, поэтому эффект от распараллеливания должен проявиться.
В итоге, переработав пример из статьи про распараллеливание, получилось два скрипта – test.php и pi.php. Первый скрипт является главным и управляет работой второго. Второй скрипт отвечает за вычисление числа PI, т.е. именно он запускается параллельно.
test.php
<? // функция запуска параллельной задачи function StartTask($server, $uri, $post) { $fp = fsockopen($server, 80); // формируем переменные для передачи скрипту методом POST $_post = Array(); if (is_array($post)) { foreach ($post as $name => $value) { $_post[] = $name.'='.urlencode($value); } } $post = implode('&', $_post); stream_set_blocking($fp, false); stream_set_timeout($fp, 86400); // вызываем скрипт, передавая ему необходимые переменные fwrite($fp, "POST /$uri HTTP/1.1\r\nHost: $server \r\n". "Content-Type: application/x-www-form-urlencoded\r\n". "Content-Length: ".strlen($post)."\r\n". "Connection: close\r\n\r\n$post" ); return $fp; } // эта функция берет вывод параллельного скрипта и возвращает его function GetTaskOutput(&$fp) { if ($fp === false) { return false; } if (feof($fp)) { fclose($fp); $fp = false; return false; } return fread($fp, 512); } // функция для вычисления времени (чтобы замерить время выполнения кода) function GetMTime() { $mtime = microtime(); $mtime = explode(' ', $mtime); $mtime = $mtime[1] + $mtime[0]; return $mtime; } set_time_limit(0); // это обязательно должно быть нулем, иначе настройки php.ini могут не позволить скрипту работать долго // Запускаем сначала для одного потока 5 экспериментов, потом - для двух потоков. И посмотрим, какое будет время. $n = 40000000; // кол-во интервалов $PI25DT = 3.141592653589793238462643; // точное значение числа PI for ($size = 1; $size <= 2; $size++) { // кол-во процессов всего $minTime = 0; for ($k = 0; $k < 5; $k++) { $time1 = GetMTime(); // засекаем время $fp = Array(); for ($i = 0; $i < $size; $i++) { // стартуем скрипты $fp[$i] = StartTask('test','/pi.php', Array('rank' => $i, 'size' => $size, 'n' => $n)); } $mypi = 0; while (true) { $out = Array(); $break = true; for ($i = 0; $i < $size; $i++) { $out[$i] = GetTaskOutput($fp[$i]); if ($out[$i] !== false) { $break = false; } } if ($break) { break; } // выделяем строку результата из вывода скрипта. Почему так? Попробуйте без этого и будет понятно for ($i = 0; $i < $size; $i++) { if (preg_match('!<result>(.*)?</result>!is', $out[$i], $matches) > 0) { $mypi += $matches[1]; } } } $time2 = GetMTime(); if ($time2 - $time1 < $minTime || $k == 0) { $minTime = $time2 - $time1; } } echo '<pre>'; echo 'Size: '.$size.'<br />'; // выводим кол-во скриптов, параллельно работавших echo 'N: '.$n.'<br />'; echo 'Exact PI = '.number_format($PI25DT, 16).'<br />'; echo 'My PI = '.number_format($mypi, 16).'<br />'; // выводим вычисленное значение числа PI echo 'Time, sec: '.$minTime; // минимальное время работы в течение всех экспериментов echo '</pre>'; flush(); @ob_flush(); } ?>
pi.php
<? set_time_limit(0); $rank = $_REQUEST['rank']; // номер процесса $size = $_REQUEST['size']; // кол-во процессов всего $n = $_REQUEST['n']; // кол-во интервалов $h = 1.0 / $n; $sum = 0.0; for ($i = $rank + 1; $i <= $n; $i += $size) { $x = $h * ($i - 0.5); $sum += (4.0 / (1.0 + $x * $x)); } $mypi = $h * $sum; echo '<result>'.$mypi.'</result>'; ?>
Я не буду говорить о методах вычисления числа PI в этом посте, т.к. это тема отдельного разговора. Я просто посмотрел на реализацию параллельного алгоритма вычисления PI в примерах библиотеки MPI и сделал то же самое, но на PHP.
Да, тут в примере я провожу сначала 5 экспериментов для одного процесса, потом те же 5 экспериментов, но для двух параллельных процессов. Т.е. в сумме 10 экспериментов – 2 итерации по 5 итераций в каждой.
Тут конечно самое интересное – а действительно ли будет выигрыш от распараллеливания? Я решил проверить, и вот что у меня получилось:
Size: 1
N: 40000000
Exact PI = 3.1415926535897931
My PI = 3.1415926535888001
Time, sec: 22.361926078796
Size: 2
N: 40000000
Exact PI = 3.1415926535897931
My PI = 3.1415926535901999
Time, sec: 15.371038913727
Первый результат – для 40 миллионов интервалов и одного процесса. Второй результат – то же самое, но уже распараллелено на два процесса. Как видим, погрешность во втором случае чуть меньше, чем в первом. Но самое интересное – выигрыш есть, и он оказался 30-ти процентным. Неплохой результат однако В идеале конечно должно быть 50%, но такого не бывает никогда, т.к. очень много посторонних факторов влияет на процессы и процессоры.
Почему я брал минимальное время после проведения каждых 5 экспериментов? Почему не среднее? Дело в том, что так правильнее Минимальное время показывает, что на такой итерации с таким временем процессор был меньше загружен всяким мусором (работающими на фоне программами и т.п.). А если бы мы вычисляли среднее время – мы бы брали в расчет и мусор, который нескромно влияет на результат.
Я, конечно, мог бы делать не 5 итераций, а, скажем, 50, но тогда пришлось бы ждать более получаса, чтобы процессы отработали Если кому интересно – можете и на сотне итераций пробовать :))
Чем больше итераций, тем точнее результат.
Напоследок скажу следующее: не делайте такое распараллеливание, если задача очень простая и не требует много временных ресурсов, т.к. наоборот останетесь в проигрыше – временные издержки на само распараллеливание будут большие.
Вычисление PI – это пример, первый пришедший в голову, и сразу пошедший в реализацию На самом деле можно много чего придумать. Приведенный выше код можно подточить под собственные нужды.
А вот и сама ссылка на первоисточник: [ссылка]
спс, интересно.
Я считаю надо все-таки среднее время брать, поскольку мы же не знаем, насколько влияние “мусора” отличается в паралельном и непаралельном случаях (для минимальных результатов), а разница во влияниях “мусора” может быть довольно большой (проверено на деле:)).
А так мы усредняем это влияние… довольно спорный вопрос:)
О вреде многозадачности применительно к людям
[ссылка]
Спасибо, adword! Очень интересная и правильная статья! Советую всем прочитать.
Рекомендую к прочтению все труды Джоэл Спольски!
[ссылка]
Неплохо, согласен.
Но тоже считаю, что за результат надо брать среднее время выполнения, ибо:
1) если считать загрузку ЦП фоновыми программами чем то вроде некого шума, смезывающего итоговую картину, то надо наоборот стремиться уровнять его значение для обоих случаев за счет увеличения числа итераций и нахождения среднего арифметического, или же пытаться исключить вовсе (что в нашем случае почти нереально);
2) есть значительный риск получить крайне неточные результаты в столь малом количестве итераций (если в конкретный момент времени мусора в ЦП окажется меньше, чем обычно);
Хотелось бы увидеть еще что то о многопоточности в php, в частности о мульти кУРЛ)
Интересная и полезная статья.
Интересно… Спасибо)
Спасибо.Сам не догадался о такой организации мультипоточности
А почему нельзя использовать банальные pthreads?)
Иногда хочется чего-нибудь своего)
Действительно, интересный код, только когда к таким вопросам обращаешься, удивляешься, и как я мог этого не знать…
Все ведь элементарно. Слабый базовый фундамент, некоторых логика спасает.
Ну дали бы хоть скопировать код, а то вытягивать его из исходного кода html-страницы это не дело!!!!