Reputation: 62906
When return value is not of interest, is there any (even irrelevant in practice) difference between AtomicInteger.getAndIncrement()
and AtomicInteger.incrementAndGet()
methods, when return value is ignored?
I'm thinking of differences like which would be more idiomatic, as well as which would put less load in CPU caches getting synchronized, or anything else really, anything to help decide which one to use more rationally than tossing a coin.
Upvotes: 65
Views: 35610
Reputation: 366066
getAndIncrement()
Some CPUs support it directly (even when the return address is used), unlike incrementAndGet()
, so you might save one inc reg
instruction if the return value is used.
The portable CAS retry loop isn't the implementation actually used on real JVMs for mainstream ISAs that have instructions for this. At least I hope not!
If your JVM is any good, these will compile + JIT to a single instruction on x86 or ARMv8.1.
If the return value isn't used, either can be x86 lock inc dword [mem]
If the return value is used (or the optimizer doesn't know this peephole), getAndIncrement()
is the same operation as C++ fetch_add(1)
, which is x86 lock xadd [mem], reg
(exchange-and-add) or ARMv8.1 ldadd
(or ldaddal
for the seq_cst version).
There is no single-instruction IncAndGet; you need a separate add
or inc reg
if you want that. But if you don't use the return value, hopefully a compiler can still omit that even if it uses lock xadd
instead of
For example in C:
#include <stdatomic.h>
void Inc(_Atomic int *p) {
++*p;
}
int GetAndInc(_Atomic int *p) {
//return atomic_fetch_add_explicit(p, 1, memory_order_relaxed);
return (*p)++; // uses seq_cst like Java is stuck with
}
int IncAndGet(_Atomic int *p) {
//return 1 + atomic_fetch_add_explicit(p, 1, memory_order_relaxed);
return ++*p;
}
AArch64 assembly (from Godbolt, Clang 19 -O2 -mcpu=cortex-a77
)
Inc:
mov w8, #1
ldaddal w8, w8, [x0] // Load+ADD Acquire reLease
ret
GetAndInc:
mov w8, #1
ldaddal w8, w0, [x0]
ret
IncAndGet:
mov w8, #1
ldaddal w8, w8, [x0]
add w0, w8, #1 // turn fetch_add result into add_fetch
ret
Inc:
lock inc dword ptr [rdi]
ret
GetAndInc:
mov eax, 1
lock xadd dword ptr [rdi], eax
ret
IncAndGet:
mov eax, 1
lock xadd dword ptr [rdi], eax
inc eax
ret
On ISAs without single-instruction RMWs, like ARMv7 and ARMv8.0, or MIPS or PowerPC or I think RISC-V, there's no difference, or if anything incrementAndGet
needs fewer spare registers since it can overwrite the load result with the add result. Same number of instructions either way for ARMv8.0 when told to inline the old-style instructions instead of calling libatomic helper functions in the hope that ARMv8.1 instructions will be available at run-time even if we don't want to require them (-mno-outline-atomics
= inline atomics). These functions aren't running short on registers, only limited by needing the return value in the same register as the incoming pointer.
# AArch64 clang -O2 -Wall -mcpu=cortex-a53 -mno-outline-atomics
GetAndInc:
.LBB1_1:
ldaxr w8, [x0] // load-acquire exclusive
add w9, w8, #1 // add into a different register
stlxr w10, w9, [x0] // store-release exclusive
cbnz w10, .LBB1_1
mov w0, w8
ret
IncAndGet:
.LBB2_1:
ldaxr w8, [x0]
add w8, w8, #1 // overwrite the load result
stlxr w9, w8, [x0]
cbnz w9, .LBB2_1
mov w0, w8
ret
My recommendation at the top of the answer to favour getAndIncrement()
is based on the fact that if an ISA provides a single-instruction atomic, it's normally a fetch_add
not an add_fetch
. And that is the case on the two most common mainstream ISAs, x86-64 and AArch64.
as well as which would put less load in CPU caches getting synchronized
Absolutely no difference. The atomic RMW is the same either way, it's just a question of whether you need to fix-up the result for local use.
Fun fact: GNU C __atomic
builtins also come in both flavours, __atomic_fetch_add
and __atomic_add_fetch
. (https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html#index-_005f_005fatomic_005fadd_005ffetch).
Upvotes: 1
Reputation: 24229
Just want to add to existing answers: there could be very small non-noticeable difference.
If you look at this implementation:
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
Note - both function call exactly the same function getAndAddInt
, except +1
part, which means that in this implementation getAndIncrement
is faster.
But, here is older implementation:
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
The only difference is return variable, so both functions perform exactly the same.
Upvotes: 9
Reputation: 19
Here I am giving an example. Hope it will clear your doubt.
Suppose I have a variable i as
AtomicInteger i = new AtomicInteger();
i.getAndIncrement() <==> i++;
And
i.incrementAndGet() <==> ++i;
Please have a look of the below programs
public class Test1
{
public static void main(String[] args)
{
AtomicInteger i = new AtomicInteger();
System.out.println(i.incrementAndGet());
System.out.println(i);
}
}
1 1 ======================================**
public class Test2
{
public static void main(String[] args)
{
AtomicInteger i = new AtomicInteger();
System.out.println(i.getAndIncrement());
System.out.println(i);
}
}
0 1 -------------**
Comment: 1) In the class Test1, incrementAndGet() will first increment the i value and then print.
2) In the class Test2, getAndIncrement() will first print the i value and then increment.
That's all.
Upvotes: 0
Reputation: 62906
Since no answer to the actual question has been given, here's my personal opinion based on the other answers (thanks, upvoted) and Java convention:
incrementAndGet()
is better, because method names should start with the verb describing the action, and intended action here is to increment only.
Starting with verb is the common Java convention, also described by official docs:
Upvotes: 45
Reputation: 308269
No, there's no difference (if you don't care about the return value).
The code of those methods (in the OpenJDK) differs only in that one uses return next
and the other uses return current
.
Both use compareAndSet
under the hood with the exact same algorithm. Both need to know both the old and the new value.
Upvotes: 11
Reputation: 328923
The code is essentially the same so it does not matter:
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
Upvotes: 36