Repository navigation
Use of LongAdder in StatisticsCollector #140
Description
According to javadoc:
This class is usually preferable to AtomicLong when multiple threads update a common sum that is used for purposes such as collecting statistics, not for fine-grained synchronization control. Under low update contention, the two classes have similar characteristics. But under high contention, expected throughput of this class is significantly higher, at the expense of higher space consumption.
I tried to update the StatisticsCollector to use LongAdder but it is not really possible to make it very efficient,
because StatisticsCollector uses increment and get pattern.
Example:
@Override
public <K> long incrementLoadCount(IncrementLoadCountStatisticsContext<K> context) {
return loadCount.incrementAndGet();
}
With LongAdder it would be something like:
@Override
public <K> long incrementLoadCount(IncrementLoadCountStatisticsContext<K> context) {
loadCount.increment();
return loadCount.sum(); // this could be more expensive
}
sum() is triggered on every call, so it could slow down a bit, but luckily the return value is never used, at least internally.
I'm aware that changing all methods to return void it is a breaking change but in theory it could greatly improve performance under high contention (i.e. lot of threads updating statistics).
In case you're interested, here is the implementation:
/**
* This simple collector uses {@link java.util.concurrent.atomic.LongAdder}s to collect
* statistics
*
* @see org.dataloader.stats.StatisticsCollector
*/
public class ScalableStatisticsCollector implements StatisticsCollector {
private final LongAdder loadCount = new LongAdder();
private final LongAdder batchInvokeCount = new LongAdder();
private final LongAdder batchLoadCount = new LongAdder();
private final LongAdder cacheHitCount = new LongAdder();
private final LongAdder batchLoadExceptionCount = new LongAdder();
private final LongAdder loadErrorCount = new LongAdder();
@Override
public <K> long incrementLoadCount(IncrementLoadCountStatisticsContext<K> context) {
loadCount.increment();
return loadCount.sum();
}
@Deprecated
@Override
public long incrementLoadCount() {
return incrementLoadCount(null);
}
@Override
public <K> long incrementLoadErrorCount(IncrementLoadErrorCountStatisticsContext<K> context) {
loadErrorCount.increment();
return loadErrorCount.sum();
}
@Deprecated
@Override
public long incrementLoadErrorCount() {
return incrementLoadErrorCount(null);
}
@Override
public <K> long incrementBatchLoadCountBy(long delta, IncrementBatchLoadCountByStatisticsContext<K> context) {
batchInvokeCount.increment();
batchLoadCount.add(delta);
return batchLoadCount.sum();
}
@Deprecated
@Override
public long incrementBatchLoadCountBy(long delta) {
return incrementBatchLoadCountBy(delta, null);
}
@Override
public <K> long incrementBatchLoadExceptionCount(IncrementBatchLoadExceptionCountStatisticsContext<K> context) {
batchLoadExceptionCount.increment();
return batchLoadExceptionCount.sum();
}
@Deprecated
@Override
public long incrementBatchLoadExceptionCount() {
return incrementBatchLoadExceptionCount(null);
}
@Override
public <K> long incrementCacheHitCount(IncrementCacheHitCountStatisticsContext<K> context) {
cacheHitCount.increment();
return cacheHitCount.sum();
}
@Deprecated
@Override
public long incrementCacheHitCount() {
return incrementCacheHitCount(null);
}
@Override
public Statistics getStatistics() {
return new Statistics(loadCount.sum(), loadErrorCount.sum(), batchInvokeCount.sum(), batchLoadCount.sum(), batchLoadExceptionCount.sum(), cacheHitCount.sum());
}
@Override
public String toString() {
return getStatistics().toString();
}
}
Activity
dfa1 commentedon Mar 17, 2024
Attaching a simple benchmark that is trying to reproduce a very busy thread pool used for batching:
On my workstation I get the following numbers for
AtomicLong:and the following for
LongAdder:Basically
LongAdderoutperformsAtomitcLongwhen nThreads >= 2. It would be nice to have anotherStatisticsCollectorclass usingLongAdderto be used out of the box.@bbakerman @dondonz what do you think?
bbakerman commentedon Mar 17, 2024
By all means add a new version that breaks the contract and does not return the value.
By the way NoOpStatisticsCollector is used by default right - so people have to opt into any of this anyway
github-actions commentedon Jan 2, 2025
Hello, this issue has been inactive for 60 days, so we're marking it as stale. If you would like to continue this discussion, please comment within the next 30 days or we'll close the issue.
github-actions commentedon Feb 1, 2025
Hello, as this issue has been inactive for 90 days, we're closing the issue. If you would like to resume the discussion, please create a new issue.
dfa1 commentedon Apr 14, 2025
resurrected by #186
I completely forgot about these changes... please have a look to the PR @bbakerman @dondonz.
Beware: this is breaking change for people implementing the
StatisticsCollector, but there the return value of the increment operation is not really used (and I don't think it is useful for a collector).dondonz commentedon Apr 15, 2025
No worries let's reopen this issue
dondonz commentedon May 12, 2025
Closed by #186