开发者

The performance of Scala's loop-react is so poor. Why?

开发者 https://www.devze.com 2023-03-30 19:29 出处:网络
I just write one producer-consumer demo in scala and java. The demo shows that the performance of Scala is so poor. Is my code wrong?

I just write one producer-consumer demo in scala and java. The demo shows that the performance of Scala is so poor. Is my code wrong?

Java AVG:1933534.1171935236

Scala AVG:103943.7312328648

The Scala code:

import sca开发者_开发技巧la.actors.Actor.actor
import scala.actors.Actor.loop
import scala.actors.Actor.react
import scala.concurrent.ops.spawn
object EventScala {

case class Event(index: Int)

def test() {
    val consumer = actor {
        var count = 0l
        val start = System.currentTimeMillis()
        loop {
            react {
                case Event(c) => count += 1
                case "End" =>
                    val end = System.currentTimeMillis()
                    println("Scala AVG:" + count * 1000.0 / (end - start))
                    exit()
            }
        }
    }
    var running = true;
    for (i <- 0 to 1) {
        {
            spawn {
                while (running) {
                    consumer ! Event(0)
                }
                consumer!"End"
            }
        }
    }
    Thread.sleep(5000)
    running = false
}

def main(args: Array[String]): Unit = {
    test
}

}

The Java code:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;

public class EventJava {
static BlockingQueue<Event> queue = new LinkedBlockingQueue<EventJava.Event>();
static volatile boolean running = true;
static volatile Event sentinel = new Event(0);

static class Event {
    final int index;

    public Event(int index) {
        this.index = index;
    }
}

static class Consumer implements Runnable {
    @Override
    public void run() {
        long count = 0;
        long start = System.currentTimeMillis();
        while (true) {
            try {
                Event event = queue.take();
                if (event == sentinel) {
                    long end = System.currentTimeMillis();
                    System.out.println("Java AVG:" + count * 1000.0
                            / (end - start));
                    break;
                }
                count++;
            } catch (InterruptedException e) {
            }
        }
    }
}

static class Producer implements Runnable {
    @Override
    public void run() {
        while (running) {
            queue.add(new Event(1));
        }
        queue.add(sentinel);
    }
}

static void test() throws InterruptedException {
    ExecutorService pool = Executors.newCachedThreadPool();
    pool.submit(new Consumer());
    pool.execute(new Producer());
    pool.execute(new Producer());
    Thread.sleep(5000);
    running = false;
    pool.shutdown();
}

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

}


You are testing two very different codes. Let's consider Java, for instance:

    while (true) {

Where's the opportunity for the other "actors" to take over the thread and do some processing of their own? This "actor" is pretty much hogging the thread. If you create 100000 of them, you'll see JVM get crushed under the weight of the competing "actors", or see some get all processing time while others languish.

            Event event = queue.take();
            if (event == sentinel) {

Why are you taking the event out of the queue without checking if it can be processed or not? If it couldn't be processed, you'll loose the event. If you added it back to the queue, it will end up after other events sent by the same source.

These are just two things that the Scala code does and the Java one doesn't.


Overall, this is a very un-scientific test. No warmup. Low number of iterations. Very very un-sciency. Look at google caliper or such for ideas on making better micro-benchmarks.

Once your numbers are clear: compile it into scala, and then decompile it into java. The answer may jump out.

I think in your case it may be the configuration of the actors. Try akka also.


I have a machine with 4 processors. If I run your java code, I get full processor usage on a single processor (25%). That is, you're using a single thread.

If I run your scala code I get full usage of all processors, I'm getting four threads.

So I suspect that two things are happening: you're getting contention updating count, and/or count isn't being incremented correctly.

Also, the test that you're doing in the loop is a pattern match in Scala, but is a simple equality in Java, but I suspect this is a minor part.


Actors are meant for small messages that result in meaningful computations, not for element-by-element data processing as above.

Your Actor code is really more comparable to an ExecutorService with multiple threads, where each message represents a new Runnable/Callable being submitted, rather than what you have in your Java code.

Your benchmark is really comparing "how fast a worker thread can consume an item from a queue" vs. "how fast can scala send a message to a mailbox, notify and schedule the actor, and handle the message". It's just not the same thing, and it's not fit for the same purpose.

Regardless, Scala can use Java threads too. Scala just gives you an additional (safer, simpler, and communications-based) concurrency mechanism.


loop and react both throw exceptions for the purpose of flow control. This means that there are two tasks given to the thread pool, only one of which does actual work. Exceptions are also much more expensive than regular returns, even when the JVM successfully optimizes them down to longjmps.

0

精彩评论

暂无评论...
验证码 换一张
取 消