`Inc(I)` versus `I := I + 1;` in Delphi – they’re the same, but not atomic per se.
Posted by jpluimers on 2017/10/10
Given a variable I: Integer
, some people like Inc(I);
others like I := I + 1;
.
You might think that part of that discussion nowadays should be multithreading.
In practice this does not matter: the compiler will use the same instructions for both statements.
TL;DR: This might make you think they are always atomic. But that’s not always true, as the below differences show. In addition, it can also depend on your processor archicture.
In the Win32 Delphi Compiler, this is how they look:
var I: Integer; begin I := 1; Inc(I, 1); I := I + 1; end;
Disassembly of the Inc(I);
instruction:
Disassembly of the I := I + 1
; instruction:
The same holds for Inc(I, 1);
which disassembled is:
But it can go wrong when you have this piece of code:
var I: Integer; One: Integer; begin I := 1; One := 1; Inc(I, One); I := I + One; end;
Disassembly of the Inc(I);
instruction:
Disassembly of the I := I + One
; instruction:
Win32 conclusione
So on Win32:
- The
Inc(I);
andI := I + 1;
are atomic when using constant increments. - Neither are atomic when using increments from variables.
This is true for 32-bit data types and (on modern x64 processors that have a 64-bit memory bus) also for 64-bit data types that have proper memory alignment.
So there are a lot of buts and ifs: atomicity is hard and platform dependend.
Architecture neutral
To be absolutely sure you are doing atomic increments, use the AtomicIncrement [WayBack] intrinsic which was introduced in Delphi XE3, is cross-platform and on Win32 looks like this:
Disassembled AtomicIncrement(I);
instruction:
Disassembled AtomicIncrement(I, 1);
instruction:
Disassembled AtomicIncrement(I, One);
instruction:
I’m not sure what the superfluous add eax
instructions are for. I guess a missed compiler optimisation issue.
–jeroen
References:
- [WayBack] atomic – Atomicity in C++ : Myth or Reality – Stack Overflow
- Data structure alignment – Wikipedia
- [WayBack] c++ – Why is integer assignment on a naturally aligned variable atomic? – Stack Overflow
- [WayBack] List of Delphi data types with ‘atomic’ read/write operations? – Stack Overflow
- [WayBack] Is there a list of all compiler intrinsic function for Delphi by version? – Stack Overflow
- AtomicIncrement [WayBack]
- AtomicDecrement [WayBack]
- AtomicCmpExchange [WayBack]
- AtomicExchange [WayBack]
- [Archive.is] On a 64-bit machine, are reads and writes to uint64_t’s atomic? – Quora
jpluimers said
16-bit era LLoyd’s Helpfile: https://web.archive.org/web/19961221204016/http://www.borland.com:80/techsupport/delphi/download_files/ldelphi.zip via https://web.archive.org/web/20171013114622/http://www.delphigroups.info/2/8b/128808.html
jpluimers said
TJoe commenting on G+ https://plus.google.com/+JeroenPluimers/posts/BV8ByGcSDr1 including a patch for TPrinter when printing with some buggy Xerox drivers: https://code4sale.com/texcellent/PrnFAQ.htm#Xerox
abouchez said
And the last “add” is mandatory, to return properly the newly stored value. See http://x86.renejeschke.de/html/file_module_x86_id_327.html
Also note that AtomicIncrement has a noticeable performance cost, since it may flush the CPU cache lines in heavily multi-threaded context.
abouchez said
Your test is confusing: inc(i) is NOT atomic either, in multi-thread execution: if two threads access the same i variable simultaneously, the result stored may be +1 and not +2.
AtomicIncrement() is the only cross-platform incrementation function.
rvelthuis said
I would say it is atomic. Yes, of course, if two threads access it, simultaneously or not, the result is +2. That doesn’t make the instruction less atomic. But if several threads can access the same variable, it must be global, so this can even happen with AtomicIncrement (and actually should).