Я пытаюсь сделать очень простую, но многочисленную итерационную задачу. Я выбираю 7 случайных серийных номеров из массива из 324000 серийных номеров и помещаю их в другой массив, а затем выполняю поиск в этом массиве, чтобы выяснить, находится ли в нем конкретное число, выполняю другой сценарий и записываю, сколько раз искомое число находится в массив.
Это идет довольно быстро в одном потоке. Но когда я помещаю его в pthreads, даже один запущенный pthread работает в 100 раз медленнее, чем один поток. Рабочие не делятся никакими ресурсами (то есть получают всю информацию из своих собственных папок и записывают информацию в свои собственные папки). Fwrite узкие места — не проблема. Проблема с массивами, которые я отмечу ниже. Я сталкиваюсь с проблемой строки кэша, где массивы, хотя у них есть отдельные переменные, все еще разделяют ту же самую строку кэша? Вздох … очень признателен за вашу помощь в выяснении, почему массивы замедляют его работу.
<?php
class WorkerThreads extends Thread
{
private $workerId;
private $linesId;
private $linesId2;
private $c2_result;
private $traceId;
public function __construct($id,$newlines,$newlines2,$xxtrace)
{
$this->workerId = $id;
$this->linesId = (array) $newlines;
$this->linesId2 = (array) $newlines2;
$this->traceId = $xxtrace;
$this->c2_result= (array) array();
}
public function run()
{
for($h=0; $h<90; $h++) {
$fp42=fopen("/folder/".$this->workerId."/count.txt","w");
for($master=0; $master <200; $master++) {
// *******PROBLEM IS IN THE <3000 loop -very slow***********
$b=0;
for($a=0; $a<3000; $a++) {
$zex=0;
while($zex != 1) {
$this->c2_result[0]=$this->linesId[rand(0,324631)];
$this->c2_result[1]=$this->linesId[rand(0,324631)];
$this->c2_result[2]=$this->linesId[rand(0,324631)];
$this->c2_result[3]=$this->linesId[rand(0,324631)];
$this->c2_result[4]=$this->linesId[rand(0,324631)];
$this->c2_result[5]=$this->linesId[rand(0,324631)];
$this->c2_result[6]=$this->linesId[rand(0,324631)];
if(count(array_flip($this->c2_result)) != count($this->c2_result)) { //echo "duplicates\n";
$zex=0;
} else { //echo "no duplicates\n";
$zex=1;
//exit;
}
}
// *********PROBLEM here too !in_array statement, slowing down******
if(!in_array($this->linesId2[$this->traceId],$this->c2_result)) {
//fwrite($fp4,"nothere\n");
$b++;
}
}
fwrite($fp42,$b."\n");
}
fclose($fp42);
$mainfile3="/folder/".$this->workerId."/count_pthread.php";
$command="php $mainfile3 $this->workerId";
exec($command);
}
}
}
$xxTrack=0;
$lines = range(0, 324631);
for($x=0; $x<56; $x++) {
$workers = [];
// Initialize and start the threads
foreach (range(0, 8) as $i) {
$workers[$i] = new WorkerThreads($i,$lines,$lines2,$xxTrack);
$workers[$i]->start();
$xxTrack++;
}
// Let the threads come back
foreach (range(0, 8) as $i) {
$workers[$i]->join();
}
unset($workers);
}
ОБНОВЛЕННЫЙ КОД
Мне удалось ускорить исходный код в 6 раз с помощью предложений @tpunt. Самое главное, что я узнал, это то, что код замедляется вызовами rand (). Если бы я мог избавиться от этого, то скорость была бы в 100 раз быстрее. array_rand, mt_rand () и shuffle () еще медленнее. Вот новый код:
class WorkerThreads extends Thread
{
private $workerId;
private $c2_result;
private $traceId;
private $myArray;
private $myArray2;
public function __construct($id,$xxtrace)
{
$this->workerId = $id;
$this->traceId = $xxtrace;
$c2_result=array();
}
public function run()
{
////////////////////THE WORK TO BE DONE/////////////////////////
$lines = file("/fold/considers.txt",FILE_IGNORE_NEW_LINES);
$lines2= file("/fold/considers.txt",FILE_IGNORE_NEW_LINES);
shuffle($lines2);
$fp42=fopen("/fold/".$this->workerId."/count.txt","w");
for($h=0; $h<90; $h++) {
fseek($fp42, 0);
for($master=0; $master <200; $master++) {
$b=0;
for($a=0; $a<3000; $a++) {
$zex=0;
$myArray = [];
$myArray[rand(0,324631)] = true;
$myArray[rand(0,324631)] = true;
$myArray[rand(0,324631)] = true;
$myArray[rand(0,324631)] = true;
$myArray[rand(0,324631)] = true;
$myArray[rand(0,324631)] = true;
$myArray[rand(0,324631)] = true;
while (count($myArray) !== 7) {
$myArray[rand(0,324631)] = true;
}
if (!isset($myArray[$lines2[$this->traceId]])) {
$b++;
}
}
fwrite($fp42,$b."\n");
}
$mainfile3="/newfolder/".$this->workerId."/pthread.php";
$command="php $mainfile3 $this->workerId";
exec($command);
}//END OF H LOOP
fclose($fp42);
}
}
$xxTrack=0;
$p = new Pool(5);for($b=0; $b<56; $b++) {
$tasks[$b]= new WorkerThreads($b,$xxTrack);
$xxTrack++;
}
// Add tasks to pool queue
foreach ($tasks as $task) {
$p->submit($task);
}
// shutdown will wait for current queue to be completed
$p->shutdown();
Ваш код просто невероятно неэффективен. Есть также ряд проблем с этим — я кратко остановился на некоторых из этих вещей ниже.
Во-первых, вы раскручиваете более 500 потоков (9 * 56 = 504). Это будет очень медленно, потому что многопоточность в PHP требует архитектуры без общего доступа. Это означает, что новый экземпляр интерпретатора PHP необходимо будет создавать для каждого создаваемого вами потока, где все классы, интерфейсы, признаки, функции и т. Д. Необходимо будет скопировать в новый экземпляр интерпретатора.
Возможно, еще важнее то, что ваши 3 вложенные for
петли выполняют 54 миллиона итерации (90 * 200 * 3000). Умножьте это на 504 создаваемых потока, и вы скоро увидите, почему все становится вялым. Вместо этого используйте пул потоков (см. Pthreads ‘ Pool
класс) с более скромным количеством потоков (попробуйте 8 и переходите оттуда) и сократите количество итераций, выполняемых для каждого потока.
Во-вторых, вы открываете файл 90 раз за поток (итого 90 * 504 = 45360). Вам нужен только один обработчик файлов на поток.
В-третьих, использование реальных массивов PHP внутри Threaded
объекты делают их только для чтения. Так что в отношении $this->c2_result
свойство, код внутри вашего вложенного while
петля не должна даже работать. Не говоря уже о том, что следующая проверка не ищет дубликаты:
if(count(array_flip($this->c2_result)) != count($this->c2_result))
Если вы избегаете кастинга $this->c2_result
свойство массива (поэтому делает его Volatile
объект), тогда следующий код может вместо этого заменить while
цикл:
$keys = array_rand($this->linesId, 7);
for ($i = 0; $i < 7; ++$i) {
$this->c2_result[$this->linesId[$keys[$i]]] = true;
}
Установив значения в качестве ключей в $this->c2_result
мы можем удалить последующее in_array
вызов функции для поиска через $this->c2_result
, Это делается путем использования массива PHP в качестве хеш-таблицы, где время поиска ключа является постоянным временем (O (1)), а не линейным временем, требуемым при поиске значений (с in_array
). Это позволяет нам заменить следующую медленную проверку:
if(!in_array($this->linesId2[$this->traceId],$this->c2_result))
со следующей быстрой проверкой:
if (!isset($this->c2_result[$this->linesId2[$this->traceId]]))
Но с учетом сказанного вы, кажется, не используете $this->c2_result
недвижимость в другом месте. Таким образом (при условии, что у вас нет преднамеренно отредактированного кода, который его использует), вы можете полностью удалить его и просто заменить цикл while при проверке после него следующим:
$found = false;
foreach (array_rand($this->linesId, 7) as $key) {
if ($this->linesId[$key] === $this->linesId2[$this->traceId]) {
$found = true;
break;
}
}
if (!$found) {
++$b;
}
Помимо вышесказанного, вы также можете посмотреть на хранение данных, которые вы собираете в памяти (как некоторые свойства на Threaded
объект), чтобы предотвратить дорогие записи на диск. Результаты могут быть агрегированы в конце, до закрытия пула.
Обновление на основе вашего обновления
Вы сказали, что rand
функция вызывает значительное замедление. Хотя это может быть частью проблемы, я считаю, что это на самом деле весь код внутри вашего третьего вложенного for
петля. Код внутри есть очень горячий код, потому что он выполняется 54 миллиона раз. Выше я предложил заменить следующий код:
$zex=0;
while($zex != 1) {
$c2_result[0]=$lines[rand(0,324631)];
$c2_result[1]=$lines[rand(0,324631)];
$c2_result[2]=$lines[rand(0,324631)];
$c2_result[3]=$lines[rand(0,324631)];
$c2_result[4]=$lines[rand(0,324631)];
$c2_result[5]=$lines[rand(0,324631)];
$c2_result[6]=$lines[rand(0,324631)];
$myArray = (array) $c2_result;
$myArray2 = (array) $c2_result;
$myArray=array_flip($myArray);
if(count($myArray) != count($c2_result)) {//echo "duplicates\n";
$zex=0;
} else {//echo "no duplicates\n";
$zex=1;
//exit;
}
}
if(!in_array($lines2[$this->traceId],$myArray2)) {
$b++;
}
с сочетанием array_rand
а также foreach
, После некоторых первоначальных испытаний выясняется, что array_rand
на самом деле превосходно медленный. Но мое решение хэш-таблицы, чтобы заменить in_array
вызов по-прежнему остается в силе. Используя массив PHP как хеш-таблицу (в основном, сохраняя значения в виде ключей), мы получаем постоянную производительность поиска по времени (O (1)), в отличие от линейного поиска по времени (O (n)).
Попробуйте заменить приведенный выше код следующим:
$myArray = [];
$myArray[rand(0,324631)] = true;
$myArray[rand(0,324631)] = true;
$myArray[rand(0,324631)] = true;
$myArray[rand(0,324631)] = true;
$myArray[rand(0,324631)] = true;
$myArray[rand(0,324631)] = true;
$myArray[rand(0,324631)] = true;
while (count($myArray) !== 7) {
$myArray[rand(0,324631)] = true;
}
if (!isset($myArray[$lines2[$this->traceId]])) {
$b++;
}
Для меня это привело к ускорению на 120%.
Что касается дальнейшей производительности, вы можете (как уже упоминалось выше, снова) сохранить результаты в памяти (как простое свойство) и выполнить запись всех результатов в конце run
метод.
Кроме того, сборщик мусора для pthreads не является детерминированным. Поэтому его не следует использовать для извлечения данных. Вместо этого Threaded
объект должен быть введен в рабочий поток, где данные, которые будут собраны, должны быть сохранены в этот объект. Наконец, вы должны закрыть бассейн после сборщик мусора (который, опять же, не должен использоваться в вашем случае).
несмотря на то, что неясно, что такое ваш код и что такое $ newlines и $ newlines2, так что я просто догадываюсь здесь …
что-то вроде этого ?
Идея состоит в том, чтобы избежать как можно большего количества fopen и fwrite в вашем цикле.
1 — открыть его только один раз в конструкции.
2 — соединить свою цепь в петле.
3 — написать это только один раз после цикла.
class WorkerThreads extends Thread {
private $workerId;
private $linesId;
private $linesId2;
private $c2_result;
private $traceId;
private $fp42;
private $mainfile3;
public function __construct($id, $newlines, $newlines2, $xxtrace) {
$this->workerId = $id;
$this->linesId = (array) $newlines;
$this->linesId2 = (array) $newlines2;
$this->traceId = $xxtrace;
$this->c2_result = array();
$this->fp42 = fopen("/folder/" . $id . "/count.txt", "w");
$this->mainfile3 = "/folder/" . $id . "/count_pthread.php";
}
public function run() {
for ($h = 0; $h < 90; $h++) {
$globalf42='';
for ($master = 0; $master < 200; $master++) {//<200
$b = 0;
for ($a = 0; $a < 3000; $a++) {
$zex = 0;
if ($zex != 1) {
for ($ii = 0; $ii < 6; $ii++) {
$this->c2_result[$ii] = $this->linesId[rand(0, 324631)];
}
$zex = (count(array_flip($this->c2_result)) != count($this->c2_result)) ? 0 : 1;
}
if (!in_array($this->linesId2[$this->traceId], $this->c2_result)) {
$b++;
}
}
$globalf42 .= $b . "\n";
}
fwrite($this->fp42, $globalf42);
fclose($this->fp42);
$command = "php $this->mainfile3 $this->workerId";
exec($command);
}
}
}