Jump to content
dummzeuch

atomic setting of a double variable

Recommended Posts

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 by dummzeuch

Share this post


Link to post
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}

 

  • Thanks 1

Share this post


Link to post

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 by David Heffernan
  • Thanks 1

Share this post


Link to post
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
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

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 by Rollo62
  • Thanks 1

Share this post


Link to post
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
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
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

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 by Stefan Glienke
  • Like 1

Share this post


Link to post
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

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 by Marat1961

Share this post


Link to post
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
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 by Marat1961

Share this post


Link to post
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
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
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

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 by Stefan Glienke
  • Like 1

Share this post


Link to post

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
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. 

  • Like 1

Share this post


Link to post

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

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×