PHP Pthread多线程代码比单线程慢

时间:2017-05-05 17:07:01

标签: php arrays multithreading pthreads

我正在尝试做一个非常简单但很多的迭代任务。我从324000个序列号的数组中选择7个随机序列号并将它们放在另一个数组中,然后搜索该数组以查看其中是否有特定数字,执行另一个脚本并写出查找数字的次数。阵列。

单线程的速度相当快。但是当我把它放在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);
}

更新代码

在@tpunt建议的帮助下,我能够将原始代码加速6倍。最重要的是,我学到的是调用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();

2 个答案:

答案 0 :(得分:1)

你的代码非常低效。它也存在许多问题 - 我已经对下面的一些内容进行了快速细分。

首先,你正在旋转超过500个线程(9 * 56 = 504)。这将非常缓慢,因为PHP中的线程需要一个无共享架构。这意味着需要为您创建的每个线程创建一个新的PHP解释器实例,其中所有类,接口,特征,函数等都需要复制到新的解释器实例。

或许更重要的是,您的3个嵌套for循环正在执行 5400万次迭代(90 * 200 * 3000)。通过创建的504个线程将其乘以,您很快就可以看到为什么事情变得迟缓。相反,使用线程池(请参阅pthreads&#39; Pool类),使用更少量的线程(尝试8,然后从那里开始),并减少每个线程执行的迭代。

其次,每个线程打开一个文件90次(所以总共90 * 504 = 45360)。每个线程只需要一个文件处理程序。

第三,利用Threaded对象内部的实际PHP数组使它们成为只读。因此,对于$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循环中的所有代码。其中的代码是非常热门代码,因为它执行了5400万次。我在上面建议您替换以下代码:

$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_randforeach。经过一些初步测试,结果发现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对象注入到工作线程中,其中要收集的数据应保存到此对象中。最后,您应该在垃圾收集之后关闭池(同样,不应该在您的情况下使用)。

答案 1 :(得分:0)

尽管你的代码还不清楚以及$ 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);
    }
}

}