dummzeuch 1505 Posted January 20, 2021 (edited) I need to set a double variable in a thread safe manner. I could, of course use some synchronization object, e.g. a critical section, but on the other hand there is InterlockedExchange64, which sets the contents of a 64 bit integer variable, and a double is also 64 bits, so I thought this should also be possible in the same manner. Unfortunately that function, while described as part of the WinAPI, doesn't seem to actually be a WinAP function but implemented as a C macro in winnt.h and in assembler in Delhpi 10.2. Google found DsiInterlockedExchange64 in the OmniThreadLibrary unit DSiWin32, which implements it in assembler for Win32 and Win64. I took that code and came up with the following for atomically setting a double variable: procedure InterlockedSetDouble(var target: double; Value: double); asm {$IFDEF CPUX64} lock xchg [target], value mov rax, value {$ELSE} { -> EAX target } { ESP+4 value } PUSH EDI PUSH EBX MOV EDI, EAX MOV EBX, DWORD PTR [value] MOV ECX, DWORD PTR [value+4] @@1: LOCK CMPXCHG8B [EDI] JNZ @@1 POP EBX POP EDI {$ENDIF ~CPUX64} end; { InterlockedSetDouble } I am for now only interested in 32 bit Windows where this seems to work, but being far from an assembler expert, I wonder whether I might be missing something. Edited January 20, 2021 by dummzeuch Share this post Link to post
Guest Posted January 20, 2021 1 hour ago, dummzeuch said: I wonder whether I might be missing something. I don't see problem with 32bit, but there is problem with 64bit, and i think it should be like this : (that due to the usage of XMM1 for Value) {$IFDEF CPUX64} movq rdx, Value lock xchg qword ptr[rcx], rdx //mov rax, rdx {$ELSE} Share this post Link to post
David Heffernan 2345 Posted January 20, 2021 (edited) What are you trying to do here? Are you trying to make an atomic assignment but ignore any race conditions? Where is the corresponding code that reads in an atomic fashion? No point having atomic writes if you don't pair them with atomic reads. You'll just suffer tearing on reading. For 64 bit code, reads and writes of aligned 64 bit values are already atomic. So plain Pascal assignment operators will do what you need. So long as your values are aligned. Can you be sure that they will be? Are local variables of type double aligned? It's quite an unusual thing to be doing, I know I've never had a need to do this with doubles. What is the motivation out of interest? Edited January 20, 2021 by David Heffernan 1 Share this post Link to post
Dalija Prasnikar 1396 Posted January 20, 2021 How about http://docwiki.embarcadero.com/Libraries/Sydney/en/System.SyncObjs.TInterlocked.Exchange 2 1 Share this post Link to post
David Heffernan 2345 Posted January 20, 2021 assembly - How do I atomically move a 64bit value in x86 ASM? - Stack Overflow is also relevant 1 Share this post Link to post
dummzeuch 1505 Posted January 20, 2021 48 minutes ago, David Heffernan said: What are you trying to do here? Are you trying to make an atomic assignment but ignore any race conditions? Where is the corresponding code that reads in an atomic fashion? No point having atomic writes if you don't pair them with atomic reads. You'll just suffer tearing on reading. For 64 bit code, reads and writes of aligned 64 bit values are already atomic. So plain Pascal assignment operators will do what you need. So long as your values are aligned. Can you be sure that they will be? Are local variables of type double aligned? It's quite an unusual thing to be doing, I know I've never had a need to do this with doubles. What is the motivation out of interest? Yeah, good point. I'd need atomic reading code for that too. I guess I didn't think far enough. The alignment would not be a problem, I could make sure it is as required. The use case is one thread reading data from an external device, a different one displaying that data on the screen, and a third one writing it to a file. Most of of the data is integers but there are also two doubles. It doesn't really matter whether the different values match but of course a double must still be a valid double which could be a problem when reading two 4 byte parts from two different double values. Since the data is read at different times only one variable would be changed at any time, so I thought I could maybe get way without any of the heavier sync objects. I guess I'll just stick with a critical section. I know how to use these and it's unlikely that there will be a performance bottleneck here. Share this post Link to post
dummzeuch 1505 Posted January 20, 2021 59 minutes ago, Dalija Prasnikar said: How about http://docwiki.embarcadero.com/Libraries/Sydney/en/System.SyncObjs.TInterlocked.Exchange Hm, yes, could be an option once I get this project to a later Delphi version. Currently it's Delphi 2007, but that should not be a permanent hindrance. Share this post Link to post
David Heffernan 2345 Posted January 20, 2021 53 minutes ago, dummzeuch said: I guess I'll just stick with a critical section. I know how to use these and it's unlikely that there will be a performance bottleneck here. Unless you have a performance bottleneck, this is the shrewd approach. Share this post Link to post
Rollo62 536 Posted January 20, 2021 (edited) To add another solution, I have used this here., from Zacherl in the German DP, and separated this into separate 32- and 64-Bit units, and reworked a little. So far I have not yet worked with 64-Bit, so I cannot say its working there, or not. But I remember I put this into some unit tests, and was OK, I'm not sure .... Edited January 20, 2021 by Rollo62 1 Share this post Link to post
Stefan Glienke 2003 Posted January 25, 2021 movq xmm0,[value] movq [target],xmm0 should do the trick when target is naturally aligned - for reference. Share this post Link to post
Guest Posted January 25, 2021 26 minutes ago, Stefan Glienke said: movq xmm0,[value] movq [target],xmm0 should do the trick when target is naturally aligned - for reference. That is right with many other compilers by default, but what if Value/Target are in packed record after one byte field (or any odd sum of fields length)?! Also, is there a guarantee that Target/Value are not crossing a cache line ? ( 64byte alignment) So i would not depend on it, not without the context of its use safety in well documented. Share this post Link to post
David Heffernan 2345 Posted January 25, 2021 22 minutes ago, Kas Ob. said: That is right with many other compilers by default, but what if Value/Target are in packed record after one byte field (or any odd sum of fields length)?! Also, is there a guarantee that Target/Value are not crossing a cache line ? ( 64byte alignment) So i would not depend on it, not without the context of its use safety in well documented. This is true for all atomic memory operations. Is your argument that we should not use atomic memory operations? That can't be what you mean. Like all code, there are pre-conditions for it to work. In this case we require the memory to be aligned. Onus for that falls on the user. And it's clear that Thomas knows this. Share this post Link to post
Guest Posted January 25, 2021 4 minutes ago, David Heffernan said: This is true for all atomic memory operations. Is your argument that we should not use atomic memory operations? That can't be what you mean. Like all code, there are pre-conditions for it to work. In this case we require the memory to be aligned. Onus for that falls on the user. And it's clear that Thomas knows this. Yes that is true for atomic memory operation, but without LOCK instruction explicitly the operation will be silent success without any guarantee of atomicity (without raised exception), and here is the problem, and it is also my argument, when Thomas is asking for atomic operation, i can't care any less for its purposes, and this discuss is going good so far with resources in explaining how and when simple load/store can be atomic, and when it is not, (both cases are equally important) Share this post Link to post
Stefan Glienke 2003 Posted January 25, 2021 (edited) If you actually clicked on the link I provided and followed the other links provided in that answer by Peter Cordes (who is like the Jon Skeet on asm related stuff on SO) you will ultimately find a quote from the Intel documentation (and the AMD one agreeing on that) which states: Quote The Pentium processor (and newer processors since) guarantees that the following additional memory operations will always be carried out atomically: Reading or writing a quadword aligned on a 64-bit boundary (e.g. x87 load/store of a double, or cmpxchg8b (which was new in Pentium P5)) Which is why I wrote that it works when target is aligned naturally which means by 64 bit - that in itself ensures that it does not cross a cache line. Now arguing that the presented solution does not work when this is not given is kinda moot. Thomas stated that he wants to "set a double variable in a thread safe manner" - which I assume is meant literally and a double variable will be 64-bit aligned. Edited January 25, 2021 by Stefan Glienke 1 Share this post Link to post
David Heffernan 2345 Posted January 25, 2021 3 hours ago, Kas Ob. said: Yes that is true for atomic memory operation, but without LOCK instruction explicitly the operation will be silent success without any guarantee of atomicity (without raised exception), and here is the problem, and it is also my argument, when Thomas is asking for atomic operation, i can't care any less for its purposes, and this discuss is going good so far with resources in explaining how and when simple load/store can be atomic, and when it is not, (both cases are equally important) @Stefan Glienke already explained that quadword reads/writes are atomic for aligned data. Share this post Link to post
Marat1961 17 Posted February 14, 2021 (edited) The variable must be allocated on the heap. If it is a local variable, we have no guarantee that it will be aligned. By the way, under certain conditions, the parameter passed by value may also be misaligned. Edited February 14, 2021 by Marat1961 Share this post Link to post
David Heffernan 2345 Posted February 14, 2021 4 minutes ago, Marat1961 said: The variable must be allocated on the heap. If it is a local variable, we have no guarantee that it will be aligned. What is the question here? Is it, "is this operation atomic for aligned data?" Or is it, "what are the rules for determining whether or not data is aligned?" Share this post Link to post
Marat1961 17 Posted February 14, 2021 (edited) 23 minutes ago, David Heffernan said: for aligned data He who is forewarned is armed. For a long time I was looking for an error when using the Windows API due to the fact that the parameter was not aligned on the boundary of a 32-bit word. procedure InterlockedSetDouble(var target: double; flag: Byte; Value: double); I think now Value will be misaligned Edited February 14, 2021 by Marat1961 Share this post Link to post
David Heffernan 2345 Posted February 14, 2021 12 minutes ago, Marat1961 said: He who is forewarned is armed. The alignment requirement has been stated many times in this topic. It's doesn't need to be repeated. Share this post Link to post
dummzeuch 1505 Posted February 15, 2021 15 hours ago, Marat1961 said: He who is forewarned is armed. For a long time I was looking for an error when using the Windows API due to the fact that the parameter was not aligned on the boundary of a 32-bit word. procedure InterlockedSetDouble(var target: double; flag: Byte; Value: double); I think now Value will be misaligned Did you test it? Since access to 32 bit aligned memory is faster, it's quite possible that the compiler generates code that aligns all parameters, regardless of size (I didn't test it either though). But the parameter target might not be aligned. That depends on what is passed into that procedure. Share this post Link to post
David Heffernan 2345 Posted February 15, 2021 53 minutes ago, dummzeuch said: procedure InterlockedSetDouble(var target: double; flag: Byte; Value: double); It's impossible to draw any conclusions from this declaration. This isn't code that executes. It's just the signature of a procedure. Its kinda tedious that you have said over and over that your data is aligned but people keep coming back to say "your data has to be aligned." Share this post Link to post
Stefan Glienke 2003 Posted February 15, 2021 (edited) I think the reason why people keep coming back is that there is uncertainty about when it can be assumed that the data is aligned properly. A local or global variable for example is not guaranteed to be naturally aligned. Edited February 15, 2021 by Stefan Glienke 1 Share this post Link to post
Marat1961 17 Posted February 15, 2021 don't worry, be happy When passed through the stack, parameters will be aligned on the 8-byte boundary if the parameters are passed by value and one of the following conditions is met: 1. all parameters are multiples of 8 bytes 2. optimization enabled 3. 64 bit mode Therefore, I would exclude passing by var and passing two double parameters. This would ensure that the values are aligned in memory. Share this post Link to post
David Heffernan 2345 Posted February 15, 2021 4 hours ago, Marat1961 said: I would exclude passing by var Gonna be hard to implement an atomic increment without using a var parameter. Perhaps if people want to talk about the issue of whether certain variables are aligned, then perhaps a new topic would be better. 1 Share this post Link to post
Marat1961 17 Posted February 16, 2021 Due to the causal relationship, it seemed logical to me to discuss here. 1. The atomicity of the data assignment operation is ensured if the data is aligned on the double word boundary. 2. The data is aligned if .. Share this post Link to post