Deno
Deno

Reputation: 137

Why is Java RMI with multiple threads on client faster even though server is single-threaded?

I've been taking lots of measurements lately of my RMI program so as to identify what is the most expensive operation in my program (e.g. marhsalling the object, running the method, etc). Basically, as can be seen on the code below, I have a float array which may be passed as parameter to three remote operations: power (of all elements), log in any base (of all elements), and sum an offset (to all elements). The size of the array is N=10^8.

My client is multithreaded (K threads) and will divide the array to N/K and pass each chunck to a thread, while each will invoke a RMI call. The server is purely single-threaded. Client and server are running on the same machine.

For number of client threads K=1,2,4,8,16,32, the time it takes for each of these methods to return is as follows (IN SECONDS - 10 iterations sampled - Machine: i7 quad-core (8 logic processors)):

  1. Logarithm in any base (2 calls to Math.log):

    • K=1 -> 7.306161
    • K=2 -> 3.698500
    • K=4 -> 2.788655
    • K=8 -> 2.679441 (best)
    • K=16 -> 2.754160
    • K=32 -> 2.812091
  2. Sum Offset (simple sum, no calls to other methods):

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

I also measured my CPU usaged during each method: for adding the offset, CPU usage was about 60% most of the time, while the logarithm method invocation required well over 80% of the CPU, peaking at 100% multiple times. I also tried the power method (abstracted from the code below), and it showed very similar results to adding an offset (just a little more expensive).

It would be easy to conclude that adding the offset is super cheap so handling more threads just makes it more expensive due to thread scheduling and all. And, since calculating logarithms is more expensive, more threads make the problem faster and that's why K=8 is perfect for my machine with 8 CPUs.

HOWEVER, the server is single-threaded! How can this be possible? How can 8 client threads be making a much better job than 2 client threads in this situation?

I'm having a lot of trouble thinking about these results. Any help is appreciated. The code for each module is shown below.

Code for 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();
    }
}

Code for 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();
        }
    }
}

Code for 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;
}

Code for 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;
    }
}

Upvotes: 0

Views: 1918

Answers (2)

user207421
user207421

Reputation: 310980

Why is Java RMI with multiple threads on client faster even though server is single-threaded?

Because your assumption is false. RMI servers are not single-threaded.

Upvotes: 1

Dmitry P.
Dmitry P.

Reputation: 834

RMI specification 3.2 states:

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.

So at least there is no guarantee that RMI requests are executed in one thread. What we see in practice is that the standard java RMI implementation uses multiple threads to handle requests.

Upvotes: 3

Related Questions