我有一个繁重的数学计算来计算范围内twin prime个数的数量,我已经在线程之间划分了任务。
在这里,您可以看到执行时间与线程数的关系。
我的问题是关于:
的理由为什么单线程和双线程的性能非常相似?
为什么在5线或7线程时执行时间会下降,而在使用6或8个线程时执行时间会增加? (我在几次测试中都经历过这种情况。)
我使用的是8核计算机。根据经验,我可以声称2× n (其中 n 是核心数)是一个很好的线程数吗?
如果我使用RAM使用率高的代码,我会在配置文件中看到类似的趋势,还是会随着线程数量的增加而急剧变化?
这是代码的主要部分,只是为了表明它没有使用太多RAM。
bool is_prime(long a)
{
if(a<2l)
return false;
if(a==2l)
return true;
for(long i=2;i*i<=a;i++)
if(a%i==0)
return false;
return true;
}
uint twin_range(long l1,long l2,int processDiv)
{
uint count=0;
for(long l=l1;l<=l2;l+=long(processDiv))
if(is_prime(l) && is_prime(l+2))
{
count++;
}
return count;
}
规格:
$ lsb_release -a
Distributor ID: Ubuntu
Description: Ubuntu 16.04.1 LTS
Release: 16.04
Codename: xenial
$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 8
On-line CPU(s) list: 0-7
Thread(s) per core: 2
Core(s) per socket: 4
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 94
Model name: Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz
Stepping: 3
CPU MHz: 799.929
CPU max MHz: 4000.0000
CPU min MHz: 800.0000
BogoMIPS: 6815.87
Virtualisation: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 256K
L3 cache: 8192K
NUMA node0 CPU(s): 0-7
Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch intel_pt tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm mpx rdseed adx smap clflushopt xsaveopt xsavec xgetbv1 dtherm ida arat pln pts hwp hwp_notify hwp_act_window hwp_epp
更新(在接受的答案之后)
新资料:
改进的代码如下。现在,工作量得到了公平分配。
bool is_prime(long a)
{
if(a<2l)
return false;
if(a==2l)
return true;
for(long i=2;i*i<=a;i++)
if(a%i==0)
return false;
return true;
}
void twin_range(long n_start,long n_stop,int index,int processDiv)
{
// l1+(0,1,...,999)+0*1000
// l1+(0,1,...,999)+1*1000
// l1+(0,1,...,999)+2*1000
// ...
count=0;
const long chunks=1000;
long r_begin=0,k=0;
for(long i=0;r_begin<=n_stop;i++)
{
r_begin=n_start+(i*processDiv+index)*chunks;
for(k=r_begin;(k<r_begin+chunks) && (k<=n_stop);k++)
{
if(is_prime(k) && is_prime(k+2))
{
count++;
}
}
}
std::cout
<<"Thread "<<index<<" finished."
<<std::endl<<std::flush;
return count;
}
答案 0 :(得分:2)
考虑到 last 线程完成检查其数字范围后,您的程序将完成。也许有些线程比其他线程更快?
is_prime()
确定偶数为素数需要多长时间?它将在第一次迭代中找到它。如果a是素数,则查找奇数的素数将至少进行两次迭代并且可能达到sqrt(a)次迭代。当给出一个大素数而不是偶数时,is_prime()
会慢得多!
在你的双线程情况下,一个线程将检查100000000,100000002,100000004等的素数,而另一个线程将检查100000001,100000003,100000005等。一个线程检查所有偶数而另一个检查全部奇数(包括所有那些缓慢的素数!)。
让你的线程在完成时打印出("Thread at %ld done", l1)
,我认为你会发现有些线程比其他线程快得多,因为你在线程之间划分域的方式。偶数个线程会给同一个线程提供所有偶数值,导致分区特别差,这就是为什么你的偶数线程数比奇数慢。
这将成为一部伟大的XKCD式漫画。 &#34;我们需要检查所有这些数字以找到素数!用手!&#34; &#34;好的,我会检查平均值,你做的几率。&#34;
你真正的问题是像你所做的固定域分解需要每个分区花费相同的时间才能达到最佳状态。
解决此问题的方法是动态进行分区。常用的模式涉及请求以块为单位工作的工作线程池。如果块与一个线程的总工作量相比较小,那么所有线程将在相似的时间内完成它们的工作。
对于您的问题,您可以拥有受互斥锁保护的全局数据集start_number, stop_number, total_twins
。在将全局值增加start_number
之前,每个线程将保存chunk_size
。然后,它会搜索范围[saved_start_number, saved_start_number+chunk_size)
,并在完成后将找到的双胞胎数添加到全局total_twins
。工作线程一直这样做,直到start_number >= stop_number
。访问全局变量使用互斥锁进行保护。必须调整块大小以限制低效率,从获得块和争用互斥锁的成本与空闲工作线程的低效率相比,其中没有更多的块要分配,而另一个线程仍在处理它的最后一个块。如果您使用原子增量来请求块,则块大小可能与单个值一样小,但如果在分布式计算系统中需要网络请求,则块大小需要更大。这是对它如何运作的概述。
is_prime()
测试是天真的,非常慢。如果一个数字不能被2整除,它可以除以4吗?一个人可以做得更好!
答案 1 :(得分:0)