Параллелим процесс на PHP
Давно уже наткнулся на одну статью, автор которой приводит пример распараллеливания работы php-скриптов через сокеты, но некогда было ее прочитать. Ссылку на эту статью можете увидеть в конце поста. Вот, наконец, добрался до нее и после прочтения возникло дикое желание распараллелить всем известный пример вычисления числа PI на . У меня двуядерная машинка, поэтому эффект от распараллеливания должен проявиться.
В итоге, переработав пример из статьи про распараллеливание, получилось два скрипта – 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 – это пример, первый пришедший в голову, и сразу пошедший в реализацию
На самом деле можно много чего придумать. Приведенный выше код можно подточить под собственные нужды.
А вот и сама ссылка на первоисточник:
Март 19th, 2009
спс, интересно.
Я считаю надо все-таки среднее время брать, поскольку мы же не знаем, насколько влияние “мусора” отличается в паралельном и непаралельном случаях (для минимальных результатов), а разница во влияниях “мусора” может быть довольно большой (проверено на деле:)).
А так мы усредняем это влияние… довольно спорный вопрос:)
Март 19th, 2009
О вреде многозадачности применительно к людям
Март 19th, 2009
Спасибо, adword! Очень интересная и правильная статья! Советую всем прочитать.
Март 19th, 2009
Рекомендую к прочтению все труды Джоэл Спольски!
Март 21st, 2009
Неплохо, согласен.
Но тоже считаю, что за результат надо брать среднее время выполнения, ибо:
1) если считать загрузку ЦП фоновыми программами чем то вроде некого шума, смезывающего итоговую картину, то надо наоборот стремиться уровнять его значение для обоих случаев за счет увеличения числа итераций и нахождения среднего арифметического, или же пытаться исключить вовсе (что в нашем случае почти нереально);
2) есть значительный риск получить крайне неточные результаты в столь малом количестве итераций (если в конкретный момент времени мусора в ЦП окажется меньше, чем обычно);
Март 25th, 2009
Хотелось бы увидеть еще что то о многопоточности в php, в частности о мульти кУРЛ)
Март 27th, 2009
Интересная и полезная статья.
Март 29th, 2009
Интересно… Спасибо)
Апрель 17th, 2009
Спасибо.Сам не догадался о такой организации мультипоточности
Июнь 2nd, 2009
А почему нельзя использовать банальные pthreads?)
Июнь 2nd, 2009
Иногда хочется чего-нибудь своего)