我开始学习并行编程,我想将单线程程序与多线程程序进行比较。
我需要制作一个非常简单的算法,在一分钟内计算出最大数量的可能素数,并向我显示最后计算的素数及其在素数中的位置。
例如,素数23将显示为数字23及其位置9,因为它是第9个素数。
不使用线程,找到的素数为233,596,最后的素数为3,246,107。但是通过线程,找到了229,972个素数,最后一个素数是3,192,463。
我认为这是错误的,因为多线程应该为单线程获得更好的结果。我相信这是一个非常基本的错误,但我无法解决它,因为我仍然不太了解Perl的并行性。
这是代码。它计算一分钟单线程的素数,然后使用共享变量计算四个线程的相同计算。
use threads;
use threads::shared;
my $seconds = 60;
# WITHOUT THREAD #
print "\n\n Calc without Threads:\n";
my $endTime = time() + $seconds;
calcWithoutThread();
print "\n\n ----------------===========================---------------- \n";
# WITH THREAD #
print "\n\n Calc with Threads:\n";
my $prime :shared = 5; # Starts from the 5th prime
my $totalPrime :shared = 2; # Starts with prime 2 and prime 3
my $lastPrime :shared = 0;
my $endTime1 = time() + $seconds;
my $thread1 = threads->create(\&calcWithThread);
my $thread2 = threads->create(\&calcWithThread);
my $thread3 = threads->create(\&calcWithThread);
my $thread4 = threads->create(\&calcWithThread);
$thread1->join();
$thread2->join();
$thread3->join();
$thread4->join();
print " Was found $totalPrime prime numbers. Last prime: $lastPrime.";
# SUB's #
sub calcWithoutThread{
$prime = 5; # Starts from the 5th prime
$totalPrime = 2; # Starts with prime 2 and prime 3
$lastPrime = 0;
while (time() < $endTime){
if(calcPrime($prime)){
$totalPrime ++;
$lastPrime = $prime;
}
$prime ++;
}
print " Was found $totalPrime prime numbers. Last prime: $lastPrime.";
}
sub calcWithThread{
while (time() < $endTime1) {
lock($prime);
if(calcPrime($prime)){
$totalPrime ++;
$lastPrime = $prime;
}
$prime ++;
}
}
sub calcPrime{
for($i=2 ; $i< sqrt ($prime) ; $i++){
if( $prime % $i == 0){
return 0;
}
}
return 1;
}
逻辑是线程同步执行此计算,如果它是素数或不是素数,并且在计算时也不重叠值。
答案 0 :(得分:4)
问题是您的线程通过锁定变量$prime
彼此同步。这意味着他们没有机会同时运行,并且还会遭受切换线程和同步访问变量的开销
对于并行处理,Primes不是一个很好的测试,因为每个素数的计算取决于之前的结果。但是,您可以通过保留单独的$prime
变量来实现,但是对于四个线程从5,6,7和8开始并在测试之间添加4。这样他们就不会重复彼此的工作,而是一起覆盖每个整数
这有一个问题,因为没有一个偶数将是素数,所以四个线程中的两个永远不会产生结果。它仍然是对并行性的有效测试,但显然效率很低。你可以通过启动5,7,9和11的线程,并在每次测试之前递增8来解决这个问题。然后每个线程都将盈利
不要忘记您必须为单线程代码编写相同的算法,否则并行部分会获得不公平的优势
或许更好的方法是锁定$prime
仅用于获取要测试的下一个数字并递增它。这样,所有线程都可以并行执行计算,只排队获取另一个作业
然后,您必须锁定$total_prime
以防止两个线程同时递增它,并在该锁定生效时更新$last_prime
。因为并行线程可能会不按顺序生成素数,所以您还必须检查刚刚发现的素数是否大于任何线程发现的素数
你的子程序看起来像这样。我为更改标识符道歉,但Perl传统上使用snake_case
,我发现camelCase
令人不愉快。对于第一语言不是英语,不能轻易挑出大写字母的人来说,蛇案也容易多了
sub calc_with_thread {
while ( time() < $end_time_1 ) {
my $test = do {
lock $prime;
$prime++;
};
if ( calc_prime($test) ) {
lock $total_prime;
++$total_prime;
$last_prime = $test if $test > $last_prime;
}
}
}
答案 1 :(得分:2)
多线程不是自动速度提升。
共享变量是绑定的,绑定变量很慢。
这是一个更简单,改进的版本,可以达到最大数量而不是时间。它避免了锁定。线程仍然明显变慢,因为它们中的每一个都必须为它们检查的每个数字执行get和set on slowggish共享变量。
#!/usr/bin/env perl
use strict;
use warnings;
use Time::HiRes qw(time);
use v5.10;
use threads;
use threads::shared;
my $Max_Number = 50_000;
{
print "\n\nCalc without Threads:\n";
my $start_time = time;
calcWithoutThread();
my $end_time = time;
say "$Max_Number took @{[ $end_time - $start_time ]} seconds.";
}
my $Shared_Prime :shared = 5;
{
print "\n\nCalc with Threads:\n";
my $start_time = time;
my $thread1 = threads->create(\&calcWithThread);
my $thread2 = threads->create(\&calcWithThread);
my $thread3 = threads->create(\&calcWithThread);
my $thread4 = threads->create(\&calcWithThread);
$thread1->join();
$thread2->join();
$thread3->join();
$thread4->join();
my $end_time = time;
say "$Max_Number took @{[ $end_time - $start_time ]} seconds.";
}
sub calcWithoutThread {
my $prime = 5; # Starts from the 5th prime
while( $prime <= $Max_Number ) {
calcPrime($prime++);
}
}
sub calcWithThread {
while( $Shared_Prime <= $Max_Number ) {
calcPrime($Shared_Prime++);
}
}
sub calcPrime {
my $prime = shift;
for( my $i=2 ; $i < sqrt($prime) ; $i++){
if( $prime % $i == 0){
return 0;
}
}
return 1;
}
相反,尽可能避免共享状态或协调工作人员。例如,您可以将工作分成几部分。如果你有4个工人,给他们每个人一个起始编号,他们每个增加4.然后他们可以覆盖整个空间而没有共享状态。
#!/usr/bin/env perl
use strict;
use warnings;
use Time::HiRes qw(time);
use v5.10;
use threads;
my $Max_Number = 7_000;
{
print "\n\nCalc without Threads:\n";
my $start_time = time;
calcWithoutThread(5, 1);
my $end_time = time;
say "$Max_Number took @{[ $end_time - $start_time ]} seconds.";
}
{
print "\n\nCalc with Threads:\n";
my $start_time = time;
my $thread1 = threads->create(\&calcWithThread, 5, 4 );
my $thread2 = threads->create(\&calcWithThread, 6, 4 );
my $thread3 = threads->create(\&calcWithThread, 7, 4 );
my $thread4 = threads->create(\&calcWithThread, 8, 4 );
$thread1->join();
$thread2->join();
$thread3->join();
$thread4->join();
my $end_time = time;
say "$Max_Number took @{[ $end_time - $start_time ]} seconds.";
}
sub calcWithoutThread {
my($start, $inc) = @_;
my @primes;
for( my $prime = $start; $prime <= $Max_Number; $prime += $inc ) {
push @primes, $prime if calcPrime($prime);
}
return \@primes;
}
sub calcWithThread {
my($start, $inc) = @_;
my @primes;
for( my $prime = $start; $prime <= $Max_Number; $prime += $inc ) {
push @primes, $prime if calcPrime($prime);
}
return \@primes;
}
sub calcPrime {
my $prime = shift;
for( my $i=2 ; $i < sqrt($prime) ; $i++){
if( $prime % $i == 0){
return 0;
}
}
return 1;
}
对于我的i7 MacBook(2个真核,4个虚拟),线程在大约6000处收支平衡。要获得结果,$thread->join
将返回calcWithThread
的素数列表。