java - 为什么即使服务器是单线程的,在客户端上使用多线程的 Java RMI 速度更快?

标签 java multithreading rmi

最近我一直在对我的 RMI 程序进行大量测量,以确定我的程序中什么是最昂贵的操作(例如,编码对象、运行方法等)。基本上,从下面的代码中可以看出,我有一个 float 组,它可以作为参数传递给三个远程操作:幂(所有元素的),登录(所有元素的)任何基数,并求和一个偏移量(到所有元素)。数组的大小为N=10^8。

我的客户端是多线程的(K 个线程),会将数组分成 N/K 并将每个 block 传递给一个线程,同时每个 block 都会调用一个 RMI 调用。服务器是纯单线程的。客户端和服务器在同一台机器上运行。

对于客户端线程数 K=1,2,4,8,16,32,这些方法中的每一个返回所花费的时间如下(秒 - 10 次迭代采样 - 机器:i7 四核(8 个逻辑处理器)):

  1. 任意底数的对数(对 Math.log 的 2 次调用):

    • K=1 -> 7.306161
    • K=2 -> 3.698500
    • K=4 -> 2.788655
    • K=8 -> 2.679441 (最佳)
    • K=16 -> 2.754160
    • K=32 -> 2.812091
  2. Sum Offset(简单求和,不调用其他方法):

    • K=1 -> 3.573020
    • K=2 -> 1.864782 (最佳)
    • K=4 -> 1.874423
    • K=8 -> 2.455411
    • K=16 -> 2.752766
    • K=32 -> 2.695977

我还测量了每种方法期间的 CPU 使用率:为了添加偏移量,大部分时间 CPU 使用率约为 60%,而对数方法调用需要超过 80% 的 CPU,多次达到 100% 的峰值。我还尝试了 power 方法(从下面的代码中抽象出来),它显示出与添加偏移量非常相似的结果(只是稍微贵一点)。

很容易得出结论,添加偏移量非常便宜,因此由于线程调度等原因,处理更多线程只会使成本更高。而且,由于计算对数的成本更高,更多的线程会使问题更快,这就是为什么 K=8 非常适合我有 8 个 CPU 的机器。

但是,服务器是单线程的!这怎么可能?在这种情况下,8 个客户端线程如何比 2 个客户端线程做得更好?

我在考虑这些结果时遇到了很多麻烦。任何帮助表示赞赏。每个模块的代码如下所示。

Server.java 代码:

package rmi;

import java.rmi.Naming;
import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;

public class Server {

    Server(){
        try {
            System.setProperty("java.rmi.server.hostname", "localhost");
            LocateRegistry.createRegistry(1099);
            Service s = new ServiceImple();
            Naming.bind("Service", (Remote) s);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args){
        new Server();
    }
}

Client.java 代码:

package rmi;

import java.rmi.Naming;
import java.util.ArrayList;

public class Client {

    private static final int N = 100000000;
    private static final int K = 64;
    private static final int iterations = 1;



    public static void main(String[] args) throws InterruptedException {

        //Variable to hold current pseudo-random number:
        final int a = 25173;
        final int b = 13849;
        final int m = 3276;

        int x = m/2;

        //Create a list of lists:
        ArrayList<float[]> vector = new ArrayList<>();

        for (int i=0; i<K; i++){
            vector.add(new float[N/K]);
            for (int j=0; j<N/K; j++){
                x = (a * x + b) % m;
                vector.get(i)[j] = (float) x/m;
            }
        }

        long startTime = System.nanoTime();

        for (int count=0; count<iterations; count++){

            //Creates the list of threads:
            ArrayList<ClientThread> threads = new ArrayList<>();

            //Starts the threads
            for (int i=0; i<K; i++){
                threads.add(new ClientThread(vector.get(i), N/K));
                threads.get(i).start();
            }

            //Waits for threads to end:
            for (int i=0; i<K; i++){
                threads.get(i).join();
            }

        }

        long estimatedTime = System.nanoTime() - startTime;

        estimatedTime /= iterations;

        System.out.println("Each loop took: "+(float)estimatedTime/1000000000);
    }

}


class ClientThread extends Thread{

    private float[] vector;
    private int vSize;

    public ClientThread(float[] vectorArg, int initSize){
        vector = vectorArg;
        vSize = initSize;
    }

    @Override
    public void run(){
        try {
            Service s = (Service) Naming.lookup("rmi://localhost:1099/Service");

            //Calculates log in RMI:
            //vector = (float[]) s.log(vector, vSize, 2);

            //Adds an offset in RMI:
            vector = (float[]) s.addOffset(vector, vSize, 100);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Service.java 代码:

package rmi;

import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.List;

public interface Service extends Remote {

    //Return log in parameter base of all elements in vector:
    public float[] log(float[] vector, int vSize, int base) throws RemoteException;

    //Adds an offset to all elements in vector:
    public float[] addOffset(float[] vector, int vSize, int offset) throws RemoteException;
}

ServiceImple.java 代码:

package rmi;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.List;


public class ServiceImple extends UnicastRemoteObject implements Service{

    private static final long serialVersionUID = 1L;

    protected ServiceImple() throws RemoteException {
        super();
    }


    @Override
    public float[] log(float[] vector, int vSize, int base) throws RemoteException {
        for (int i=0; i<vSize; i=i+1){
            vector[i] = (float) (Math.log(vector[i])/Math.log(base));
        }
        return vector;
    }

    @Override
    public float[] addOffset(float[] vector, int vSize, int offset) throws RemoteException {
        for (int i=0; i<vSize; i=i+1){
            vector[i] = vector[i] + offset;
        }
        return vector;
    }
}

最佳答案

RMI specification 3.2状态:

A method dispatched by the RMI runtime to a remote object implementation may or may not execute in a separate thread. The RMI runtime makes no guarantees with respect to mapping remote object invocations to threads.

所以至少不能保证RMI请求在一个线程中执行。 我们在实践中看到的是标准的 java RMI 实现使用多线程来处理请求。

关于java - 为什么即使服务器是单线程的,在客户端上使用多线程的 Java RMI 速度更快?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44101390/

相关文章:

java - Rmi 错误 IllegalArgumentException、MarshalException

java - RMI:在服务器端等待的线程

java - 计算 JTable 中的运行总计

java - 错误的值类:org.apache.mahout.math.VarLongWritable不是类org.apache.mahout.math.VectorWritable

java - 信号量不起作用

java - 当用户按下退出键时,如何中断线程并要求其完成工作?

java - 警告 : Activity not started, 它的当前任务已被带到最前面

java - 在 Java 代码中使用 Eclipse 类路径变量

java - 在 ExecutorService 中等待 Runnable 任务完成的有效方法

java - 哪个最适合聊天应用程序?套接字编程与 rmi?