Reputation: 911
I am constructing a frequency map in a single-threaded environment using a HashMap. The keys are the Strings whose frequencies needs to be tracked.
If I use HashMap<String, Integer>, each increment needs a new Integer.
Would a LongAdder perform better for this use case as I can simply call increment()? Some rudimentary testing showed that LongAdder does indeed perform slightly better but I am not sure why.
Upvotes: 0
Views: 231
Reputation: 40062
Testing to determine relative performance of incrementing integral types.
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.Function;
public class LongAdderTest {
public static void main(String[] args) {
new LongAdderTest().start();
}
public void start() {
int N = 100_000_000;
int warmup = 3;
String[] testNames = { "LongAdder", "Long", "Integer", "long",
"int", "Object", "int[]", "long[]" };
List<Function<Integer, Long>> tests = List.of(
this::longAdderTest, this::longWrapperTest,
this::integerWrapperTest, this::primitiveLongTest,
this::primitiveIntTest, this::objectTest,
this::intArrayTest, this::longArrayTest);
int i = 0;
for (Function<Integer, Long> test : tests) {
runTest(test, warmup, N, testNames[i++]);
}
}
public void runTest(Function<Integer, Long> test, int warmup,
int iterations, String testName) {
// warmup cycle
for (int i = 0; i < warmup; i++) {
long v = test.apply(iterations);
if (v != iterations) {
System.out
.println("Unexpected result - return = " + v);
}
}
long start = System.nanoTime();
long val = test.apply(iterations);
System.out.printf("%-10s : %12f %d%n", testName,
(System.nanoTime() - start) / 1_000_000., val);
}
public long longAdderTest(int iter) {
LongAdder val = new LongAdder();
Map<String, LongAdder> freq = new HashMap<>();
freq.put("A", val);
for (int i = 0; i < iter; i++) {
freq.get("A").increment();
}
return freq.get("A").longValue();
}
public long longWrapperTest(int iter) {
Long val = 0L;
Map<String, Long> freq = new HashMap<>();
freq.put("A", val);
for (int i = 0; i < iter; i++) {
freq.computeIfPresent("A", (k, v) -> v + 1);
}
return freq.get("A");
}
public long integerWrapperTest(int iter) {
Integer val = 0;
Map<String, Integer> freq = new HashMap<>();
freq.put("A", val);
for (int i = 0; i < iter; i++) {
freq.computeIfPresent("A", (k, v) -> v + 1);
}
return freq.get("A");
}
public long primitiveLongTest(int iter) {
Map<String, Long> freq = new HashMap<>();
long val = 0;
freq.put("A", val);
for (int i = 0; i < iter; i++) {
freq.computeIfPresent("A", (k, v) -> v + 1);
}
return freq.get("A");
}
public long primitiveIntTest(int iter) {
Map<String, Integer> freq = new HashMap<>();
int val = 0;
freq.put("A", val);
for (int i = 0; i < iter; i++) {
freq.computeIfPresent("A", (k, v) -> v + 1);
}
return freq.get("A");
}
public long intArrayTest(int iter) {
Map<String, int[]> freq = new HashMap<>();
int[] val = { 0 };
freq.put("A", val);
for (int i = 0; i < iter; i++) {
freq.get("A")[0] += 1;
}
return freq.get("A")[0];
}
public long longArrayTest(int iter) {
Map<String, long[]> freq = new HashMap<>();
long[] val = { 0L };
freq.put("A", val);
for (int i = 0; i < iter; i++) {
freq.get("A")[0] += 1;
}
return freq.get("A")[0];
}
public long objectTest(int iter) {
MyLongIncrement longObject = new MyLongIncrement(0);
Map<String, MyLongIncrement> freq = new HashMap<>();
freq.put("A", longObject);
for (int i = 0; i < iter; i++) {
freq.get("A").increment();
}
return freq.get("A").get();
}
static class MyLongIncrement {
long val;
public MyLongIncrement(long v) {
this.val = v;
}
public long get() {
return val;
}
public void increment() {
val += 1l;
}
}
}
Sample run.
LongAdder : 4166.724472 100000000
Long : 2929.021352 100000000
Integer : 5487.358323 100000000
long : 2993.538570 100000000
int : 2505.171838 100000000
Object : 1032.322116 100000000
int[] : 1132.710126 100000000
long[] : 1107.633331 100000000
Details
LongAdder
but the synchronization code could be a factor (or the test designer) for its less than stellar performance. But then, there could be many factors including my method of accessing the map value.I think I'm done with this. Hope it shed some light on the issue.
Upvotes: 1