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