Note: this post is specifically about Java.
One thing I noticed about the benchmarks in the blog post I linked to on my last post is that the Java tests weren't wrapped in synchronized blocks (which would be a given in any concurrent program).
So I decided to modify it a bit and see how things went. Here's the code:
public final class Bench {
public static Object mutex = new Object();
public static Object getMutex() {
return mutex;
}
//immutable
private static final class Person0 {
public final String name;
public final int age;
public final double balance;
public Person0(final String name, final int age, final double balance) {
this.name = name;
this.age = age;
this.balance = balance;
}
}
//idiomatic mutable
private static final class Person1 {
private String name;
private int age;
private double balance;
public Person1(final String name, final int age, final double balance) {
this.name = name;
this.age = age;
this.balance = balance;
}
public void setName(final String n) {
this.name = n;
}
public void setAge(final int n) {
this.age = n;
}
public int getAge() {
return this.age;
}
public void deposit(final double n) {
this.balance += n;
}
}
//mutable w/ public fields
private static final class Person2 {
public String name;
public int age;
private double balance;
public Person2(final String name, final int age, final double balance) {
this.name = name;
this.age = age;
this.balance = balance;
}
public void deposit(final double n) {
this.balance += n;
}
}
private interface Test {
double test(int iters);
}
private static final Test TEST0 = new Test() {
public double test(int iters) {
Person0 person = new Person0("John Doe", 19, 100.0);
for (int i = 0; i != iters; i++) {
synchronized (mutex) {
person = new Person0(person.name, person.age + i, person.balance + 1.0);
}
}
return person.balance;
}
};
private static final Test TEST1 = new Test() {
public double test(int iters) {
Person0 person = new Person0("John Doe", 19, 100.0);
Person0 p;
for (int i = 0; i != iters; i++) {
p = new Person0(person.name, person.age + i, person.balance + 1.0);
synchronized (mutex) {
person = p;
}
}
return person.balance;
}
};
private static final Test TEST2 = new Test() {
public double test(int iters) {
Person1 person = new Person1("John Doe", 19, 100.0);
for (int i = 0; i != iters; i++) {
synchronized (getMutex()) {
person.setName("John Doe");
person.setAge(person.getAge() + i);
person.deposit(1.0);
}
}
return person.balance;
}
};
private static final Test TEST3 = new Test() {
public double test(int iters) {
Person1 person = new Person1("John Doe", 19, 100.0);
for (int i = 0; i != iters; i++) {
synchronized (mutex) {
person.name = "John Doe";
person.age += i;
person.deposit(1.0);
}
}
return person.balance;
}
};
private static final Test TEST4 = new Test() {
public double test(int iters) {
Person0 person = new Person0("John Doe", 19, 100.0);
for (int i = 0; i != iters; i++) {
person = new Person0(person.name, person.age + i, person.balance + 1.0);
}
return person.balance;
}
};
private static final Test TEST5 = new Test() {
public double test(int iters) {
Person1 person = new Person1("John Doe", 19, 100.0);
for (int i = 0; i != iters; i++) {
person.name = "John Doe";
person.age += i;
person.deposit(1.0);
}
return person.balance;
}
};
private static double test(int times, Test test, int iters) {
long best = Long.MAX_VALUE;
double balance;
for (int i = 0; i != times; i++) {
long now = System.currentTimeMillis();
balance = test.test(iters);
now = System.currentTimeMillis() - now;
if (best > now) best = now;
}
return (double) iters / ((double) best / 1000.0);
}
public static void main(String[] args) {
final int iters = 10000000;
System.out.printf("Immutable sync all: %f updates/s\n", test(5, TEST0, iters));
System.out.printf("Immutable sync =: %f updates/s\n", test(5, TEST1, iters));
System.out.printf("Mutable idiomatic: %f updates/s\n", test(5, TEST2, iters));
System.out.printf("Mutable fields: %f updates/s\n", test(5, TEST3, iters));
System.out.printf("Unsafe immutable: %f updates/s\n", test(5, TEST4, iters));
System.out.printf("Unsafe mutable: %f updates/s\n", test(5, TEST5, iters));
}
}
And the results:
| Immutable syncing constructor | 14,903,129 updates/s |
| Immutable syncing assignment | 14,880,952 updates/s |
| Mutable with idiomatic Java | 18,832,391 updates/s |
| Mutable accessing fields directly | 18,832,391 updates/s |
| Non-synced immutable | 70,921,985 updates/s |
| Non-synced mutable | 322,580,645 updates/s |
Interesting points to note:
- Immutables turn out to be slightly faster than mutables when synchronization is involved.
- Using idiomatic set/get methods directly seems to be about as fast as accessing fields directly (albeit more verbose).
- Non-thread-safe code runs about an order of magnitude faster than the synchronized counterparts. Does that mean I need 20+ CPUs to match the speed of a single-threaded program (due to synchronization overhead)?
So is concurrency really overhyped or did I just do something dumb with the code?
No comments:
Post a Comment