计算RMI版本与并发版本范围内的素数数量

时间:2014-01-23 20:16:22

标签: java concurrency parallel-processing rmi distributed-computing

我有两个版本的程序具有相同的目的:计算0到n之间有多少素数。

第一个版本使用并发,Callable类“执行数学运算”,结果通过Future数组检索。创建的线程与计算机中的处理器一样多(4)。

第二个版本是通过RMI实现的。所有四台服务器都在本地主机中注册。显然,服务器也在并行工作。

我希望第二个版本比第一个版本慢,因为我猜网络会涉及延迟,而另一个版本会同时运行该程序。

然而,RMI版本比paralel版本快两倍......为什么会发生这种情况?!

我没有粘贴任何代码,因为它很大,但是如果你需要的话请求它,我会看到我能做什么......

编辑:添加代码。我评论了要发布不必要代码的部分。

Paralell版本

public class taskPrimes implements Callable
{
  private final long x;
  private final long y;
  private Long total = new Long(0);

  public taskPrimes(long x, long y)
  {
    this.x = x;
    this.y = y;
  }

  public static boolean isPrime(long n)
  {
    if (n<=1) return false ;
    for (long i=2; i<=Math.sqrt(n); i++)
      if (n%i == 0) return false;
    return true;
  } 

  public Long call()
  {   
    for (long i=linf; i<=lsup;i++)
      if (isPrime(i)) total++;
  return total;
  }
}

public class paralelPrimes 
{
  public static void main(String[] args) throws Exception 
  {
    // here some variables...
    int nTasks = Runtime.getRuntime().availableProcessors();

    ArrayList<Future<Long>> partial = new ArrayList<Future<Long>>(); 
    ThreadPoolExecutor ept = new ThreadPoolExecutor();
    for(int i=0; i<nTasks; i++)
    {
      partial.add(ept.submit(new taskPrimes(x, y))); // x and y are the limits of the range
      // sliding window here
    }  
    for(Future<Long> iterator:partial)
      try { total +=  iterator.get(); } catch (Exception e) {}
  }   
}

RMI版本

服务器

public class serverPrimes 
    extends UnicastRemoteObject 
        implements interfacePrimes
{
    public serverPrimes() throws RemoteException {}

    @Override
    public int primes(int x, int y) throws RemoteException
    {
        int total = 0;
        for(int i=x; i<=y; i++)
            if(isPrime(i)) total++;
        return total;
    }

    @Override
    public boolean isPrime(int n) throws RemoteException
    {
        if (n<=1) return false;
        for (int i=2; i<=Math.sqrt(n); i++)
            if (n%i == 0) return false ;
        return true;
    }

    public static void main(String[] args) throws Exception 
    {
        interfacePrimes RemoteObject1 = new serverPrimes();
        interfacePrimes RemoteObject2 = new serverPrimes();
        interfacePrimes RemoteObject3 = new serverPrimes();
        interfacePrimes RemoteObject4 = new serverPrimes();

        Naming.bind("Server1", RemoteObject1);
        Naming.bind("Server2", RemoteObject2);
        Naming.bind("Server3", RemoteObject3);
        Naming.bind("Server4", RemoteObject4);
    }
}

客户端

public class clientPrimes implements Runnable
{
    private int x;
    private int y;
    private interfacePrimes RemoteObjectReference;
    private static AtomicInteger total = new AtomicInteger();

    public clientPrimes(int x, int y, interfacePrimes RemoteObjectReference)
    {
        this.x = x;
        this.y = y;
        this.RemoteObjectReference = RemoteObjectReference;
    }

    @Override
    public void run()
    {
        try
        {
            total.addAndGet(RemoteObjectReference.primes(x, y));
        }
        catch (RemoteException e) {}
    }

    public static void main(String[] args) throws Exception
    {
        // some variables here...
        int nServers = 4;
        ExecutorService e = Executors.newFixedThreadPool(nServers);

        double t = System.nanoTime();
        for (int i=1; i<=nServers; i++)
        {
            e.submit(new clientPrimes(xVentana, yVentana, (interfacePrimes)Naming.lookup("//localhost/Server"+i)));
            // sliding window here
        }
        e.shutdown();
        while(!e.isTerminated());
        t = System.nanoTime()-t;
    }
}

3 个答案:

答案 0 :(得分:1)

要考虑的一件有趣的事情是,默认情况下,jvm在客户端模式下运行。这意味着线程不会以最激进的方式跨越核心。尝试使用-server选项运行程序会影响结果,尽管如上所述,算法设计对于并发版本可能存在瓶颈至关重要。鉴于问题,您的算法存在瓶颈,但确实需要考虑它。

rmi版本真正并行运行,因为每个对象在不同的​​机器上运行,因为这往往是一个处理问题而不是通信问题,因此延迟起着非常重要的作用。

[UPDATE]

现在我看到你的代码了解了更多细节。

您依靠ThreadExecutorPool和Future为您执行线程控制和同步,这意味着(通过文档)您的运行对象将在现有线程上分配,一旦您的对象完成其计算,该线程将是返回到该池,另一方面,Future将定期检查计算是否已完成,以便它可以收集值。

这种情况最适合某些计算,这些计算是以ThreadPool通过预先分配线程来提高性能的方式定期执行的(仅在第一次线程不存在时创建线程的开销) )。

你的实现是正确的,但它更多地集中在程序员的方便(这没有什么不妥,我总是在捍卫这种观点)而不是系统性能。

由于(主要)2件事情,RMI版本的表现不同:

1 - 你说你在同一台机器上运行,大多数操作系统会识别localhost,127.0.0.1甚至是真正的自我IP地址作为其自身地址并对通信进行一些优化,因此网络开销很小这里。

2 - RMI系统将为您创建的每个服务器对象创建一个单独的线程(如前所述),这些服务器将在调用后立即开始计算。

你应该尝试的事情:

1 - 尝试在网络上真正运行您的RMI版本,如果您可以将其配置为10Mbps,则可以更好地查看通信开销(尽管由于它是一次性通信,因此可能对通知影响不大,您可以客户端应用程序可能会多次调用计算,然后您会看到方法中的延迟)

2 - 尝试更改并行实现以直接使用Threads而不使用Future(可以使用Thread.join监视执行结束)然后在机器上使用-server选项(尽管有时,JVM会执行检查查看机器配置是否真的可以说是服务器并且将拒绝移动到该配置文件)。主要问题是,如果您的线程无法使用所有计算机内核,您将看不到任何性能提升。还尝试多次执行计算以克服线程创建的开销。

希望有助于阐明情况:)

干杯

答案 1 :(得分:0)

这取决于您的算法如何针对并行和并发解决方案而设计。没有一个标准,并行必须优于并发或反之。例如,如果您的并发解决方案有许多同步块,它可能会降低您的性能,在另一种情况下,并行算法中的通信可能是最小的,因此网络上没有开销。

如果你能得到Peter Pacheco一书的副本,它可以清除一些想法:http://www.cs.usfca.edu/~peter/ipp/

答案 2 :(得分:0)

鉴于您提供的详细信息,它主要取决于您使用的范围有多大,以及您将工作分配到服务器的效率。

例如,我敢打赌,对于小范围的N,你可能没有通过RMI分发的加速。在这种情况下,RMI(网络)开销可能会超过分布在多个服务器上的好处。当N变大并且使用有效的分配算法时,就实际计算时间而言,这种开销将变得越来越微不足道。

例如,假设同质服务器,相对有效的分布可能是告诉每个服务器计算所有数字 n 的素数,使得n % P = i,其中n <= N , P 是服务器的数量, i 是分配给每个服务器的[0,P-1]范围内的索引,是模运算。