Reputation: 241
I work on a high concurrency app. In the app code I try to avoid synchronization where possible. Recently, when comparing test performance of a unsynchronized and synchronized code versions, it turned out synchronized code performed three-four times faster than its unsynchronized counterpart.
After some experiments I came to this test code:
private static final Random RND = new Random();
private static final int NUM_OF_THREADS = 3;
private static final int NUM_OF_ITR = 3;
private static final int MONKEY_WORKLOAD = 50000;
static final AtomicInteger lock = new AtomicInteger();
private static void syncLockTest(boolean sync) {
System.out.println("syncLockTest, sync=" + sync);
final AtomicLong jobsDone = new AtomicLong();
final AtomicBoolean stop = new AtomicBoolean();
for (int i = 0; i < NUM_OF_THREADS; i++) {
Runnable runner;
if (sync) {
runner = new Runnable() {
@Override
public void run() {
while (!stop.get()){
jobsDone.incrementAndGet();
synchronized (lock) {
monkeyJob();
}
Thread.yield();
}
}
};
} else {
runner = new Runnable() {
@Override
public void run() {
while (!stop.get()){
jobsDone.incrementAndGet();
monkeyJob();
Thread.yield();
}
}
};
}
new Thread(runner).start();
}
long printTime = System.currentTimeMillis();
for (int i = 0; i < NUM_OF_ITR;) {
long now = System.currentTimeMillis();
if (now - printTime > 10 * 1000) {
printTime = now;
System.out.println("Jobs done\t" + jobsDone);
jobsDone.set(0);
i++;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
stop.set(true);
}
private static double[] monkeyJob() {
double[] res = new double[MONKEY_WORKLOAD];
for (int i = 0; i < res.length; i++) {
res[i] = RND.nextDouble();
res[i] = 1./(1. + res[i]);
}
return res;
}
I played with the number of threads, workload, test iterations - each time synchronized code perfomed much faster than unsunchronized one.
Here are results for two different values of NUM_OF_THREADS
Number of threads:3
syncLockTest, sync=true
Jobs done 5951
Jobs done 5958
Jobs done 5878
syncLockTest, sync=false
Jobs done 1399
Jobs done 1397
Jobs done 1391Number of threads:5
syncLockTest, sync=true
Jobs done 5895
Jobs done 6464
Jobs done 5886
syncLockTest, sync=false
Jobs done 1179
Jobs done 1260
Jobs done 1226
Test environment Windows 7 Professional Java Version 7.0
Here's a simillar case Synchronized code performs faster than unsynchronized one
Any ideas?
Upvotes: 5
Views: 649
Reputation: 116878
This is fascinating. I think @jtahlborn nailed it. If I move the Random
and make it local to the thread, the times for the non-sync jump ~10x while the synchronized
ones don't change:
Here are my times with a static Random RND
:
syncLockTest, sync=true
Jobs done 8800
Jobs done 8839
Jobs done 8896
syncLockTest, sync=false
Jobs done 1401
Jobs done 1381
Jobs done 1423
Here are my times with a Random rnd
local variable per thread:
syncLockTest, sync=true
Jobs done 8846
Jobs done 8861
Jobs done 8866
syncLockTest, sync=false
Jobs done 25956 << much faster
Jobs done 26065 << much faster
Jobs done 26021 << much faster
I also wondered if this was GC related but moving the double[] res
to being a thread local did not help the speeds at all. Here's the code I used:
...
@Override
public void run() {
// made this be a thread local but it affected the times only slightly
double[] res = new double[MONKEY_WORKLOAD];
// turned rnd into a local variable instead of static
Random rnd = new Random();
while (!stop.get()) {
jobsDone.incrementAndGet();
if (sync) {
synchronized (lock) {
monkeyJob(res, rnd);
}
} else {
monkeyJob(res, rnd);
}
}
}
...
private static double[] monkeyJob(double[] res, Random rnd) {
for (int i = 0; i < res.length; i++) {
res[i] = rnd.nextDouble();
res[i] = 1. / (1. + res[i]);
}
return res;
}
Upvotes: 4
Reputation: 53694
Random is a thread-safe class. you are most likely avoiding contention on calls into the Random class by synchronizing around the main job.
Upvotes: 8