volatile?

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

User avatar
lucasart
Posts: 3232
Joined: Mon May 31, 2010 1:29 pm
Full name: lucasart

volatile?

Post by lucasart »

looking at senpai's code, i came across something odd:
* variables subject to concurrent access are declared as volatile. why?
* they are always modified under lock, which is correct. but could it be faster in some cases to not use locking, and use atomic variables? eg. you just need to increment the node cnt by 1 seems wasteful to use a lock.
Theory and practice sometimes clash. And when that happens, theory loses. Every single time.
Xann
Posts: 127
Joined: Sat Jan 22, 2011 7:14 pm
Location: Lille, France

Re: volatile?

Post by Xann »

I think at a level more abstract than C++ because of my interest in programming-language design. I used "volatile" as you said, as an "executable comment" that these variables are shared. I agree that it's not what volatile means in C++ but I would like to have this feature in a programming language, ideally with a run-time error in a special debug mode if I forget a marker.

Regarding atomic. I first implemented SMP in "normal" C++, and only changed threads to use the C++11 model later. During the last week before the release we had many last-minute problems and I decided to sacrifice code sanitisation in order to minimise the risk of introducing a new bug, especially in SMP. But I will look at the C++11 memory model which seems interesting, including thread-local storage.

Short version: I couldn't clean up in time and I suggest not over-thinking my weird stuff.
User avatar
lucasart
Posts: 3232
Joined: Mon May 31, 2010 1:29 pm
Full name: lucasart

Re: volatile?

Post by lucasart »

Xann wrote:I think at a level more abstract than C++ because of my interest in programming-language design. I used "volatile" as you said, as an "executable comment" that these variables are shared. I agree that it's not what volatile means in C++ but I would like to have this feature in a programming language, ideally with a run-time error in a special debug mode if I forget a marker.
Yes, it doesn't look like there is a concept of "please make this variable thread safe" in C++. From what I understood, you have to use either locks, or atomic variables (but then you need to make sure your operations are guaranteed to be atomic, which looks like another can of worms...)

The reason I ask, is because I was wondering what this obscure "volatile" keyword really means. I read this short article:
https://www.kernel.org/doc/Documentatio ... armful.txt
Essentially they make the point that
In properly-written code, volatile can only serve to slow things down
They say that volatile has nothing to do with concurrency, it's almost never correct to use it, and the lock is enough.

On the other hand, Stockfish also declares all shared variables as volatile. And I know that Marco is much more knowledgeable than I am in C++, especially when it comes to multi-threading. So I can't help wondering if there isn't indeed a good reason for all this volatile stuff :?
Theory and practice sometimes clash. And when that happens, theory loses. Every single time.
Rein Halbersma
Posts: 741
Joined: Tue May 22, 2007 11:13 am

Re: volatile?

Post by Rein Halbersma »

lucasart wrote: The reason I ask, is because I was wondering what this obscure "volatile" keyword really means. I read this short article:
https://www.kernel.org/doc/Documentatio ... armful.txt
Essentially they make the point that
In properly-written code, volatile can only serve to slow things down
They say that volatile has nothing to do with concurrency, it's almost never correct to use it, and the lock is enough.
Quote from the latest draft C++ Standard
7.1.6.1 The cv-qualifiers [dcl.type.cv]
7 [ Note: volatile is a hint to the implementation to avoid aggressive optimization involving the object
because the value of the object might be changed by means undetectable by an implementation. Furthermore,
for some implementations, volatile might indicate that special hardware instructions are required to access
the object. See 1.9 for detailed semantics. In general, the semantics of volatile are intended to be the
same in C++ as they are in C. — end note ]
syzygy
Posts: 5557
Joined: Tue Feb 28, 2012 11:56 pm

Re: volatile?

Post by syzygy »

lucasart wrote:The reason I ask, is because I was wondering what this obscure "volatile" keyword really means. I read this short article:
https://www.kernel.org/doc/Documentatio ... armful.txt
Essentially they make the point that
In properly-written code, volatile can only serve to slow things down
They say that volatile has nothing to do with concurrency, it's almost never correct to use it, and the lock is enough.
Volatile means the compiler must generate code that reads from and writes to it in the order specified by the C or C++ abstract machine.

Volatile is just what you want if you're accessing memory mapped hardware I/O registers. You don't normally do this in application programs.

For multithreaded programs using volatile is not necessary if you are properly using the synchronisation primitives of a thread library, e.g. pthreads or C++11 threads.

If you don't want to be restricted by a thread library, but (in addition) want to rely on how the compiler will map your code to the machine, then volatile is useful. Your program will be full of undefined behavior, but if that is a conscious choice that is not necessarily bad. It will just not be "standard C/C++ plus pthreads" or "standard C11/C++11".
On the other hand, Stockfish also declares all shared variables as volatile. And I know that Marco is much more knowledgeable than I am in C++, especially when it comes to multi-threading. So I can't help wondering if there isn't indeed a good reason for all this volatile stuff :?
I'll have a look at this.

For senpai it would be interesting to add a line

Code: Select all

#define volatile
and see how much that speeds up (and whether it crashes or results in funny things).
syzygy
Posts: 5557
Joined: Tue Feb 28, 2012 11:56 pm

Re: volatile?

Post by syzygy »

lucasart wrote:The reason I ask, is because I was wondering what this obscure "volatile" keyword really means.
Btw, there were some very interesting (and very very long) threads on this subject not that long ago. 8-) 8-)
User avatar
lucasart
Posts: 3232
Joined: Mon May 31, 2010 1:29 pm
Full name: lucasart

Re: volatile?

Post by lucasart »

Thanks Ronald. So you basically confirm exactly what the Linux people said in the article. I'll remove volatile and do some measurements (when my computer is free, right now it's busy running tests).
Theory and practice sometimes clash. And when that happens, theory loses. Every single time.
syzygy
Posts: 5557
Joined: Tue Feb 28, 2012 11:56 pm

Re: volatile?

Post by syzygy »

lucasart wrote:On the other hand, Stockfish also declares all shared variables as volatile. And I know that Marco is much more knowledgeable than I am in C++, especially when it comes to multi-threading. So I can't help wondering if there isn't indeed a good reason for all this volatile stuff :?
Many fields of the SplitPoint struct are volatile, but for a reason:

Code: Select all

struct SplitPoint {

...

  // Shared data
  Mutex mutex;
  volatile uint64_t slavesMask;
  volatile uint64_t nodes;
  volatile Value alpha;
  volatile Value bestValue;
  volatile Move bestMove;
  volatile int moveCount;
  volatile bool cutoff;
};
The reason is that these fields are not always accessed under lock protection. For example, in search() you can find several occurrences of:

Code: Select all

          if (SpNode)
              alpha = splitPoint->alpha;
Without the "volatile" keyword, the compiler might in certain cases be allowed to assume (according to the C++ standard) that splitPoint->alpha cannot change. It could then load this value once from memory and keep it in a register or on the stack. Or the compiler could be allowed to deduce that splitPoint->alpha cannot be different from alpha, so that these lines can be optimised away.

It seems splitPoint->moveCount is always accessed under lock protection (except for an assert), so it seems it could be made non-volatile. It might give the compiler a little bit more freedom when compiling this line:

Code: Select all

          moveCount = ++splitPoint->moveCount;
I guess with volatile the compiled code must first read splitPoint->moveCount and increase the value read, and must then write the resulting value to splitPoint->moveCount. It would be illegal to increment splitPoint->moveCount in memory using inc and then read it from memory.

Or do I have it wrong here? Maybe the compiled code must in fact do the following?
1. read splitPoint->moveCount
2. increment the retrieved value
3. write this value to splitPoint->moveCount
4. read this value from splitPoint->moveCount and assign it to moveCount

Does anyone know which of the two (if any of the two) is mandated by the C/C++ abstract machine?

In any case, without volatile anything is allowed as long as the specified result is achieved, and the compiler may assume that no other code or hardware is messing with splitPoint->moveCount (which is in fact a valid assumption due to the locks around this code).

All this means that SF relies on UB. It is not impossible that future compiler optimisations break it in subtle ways. But personally I would advise against fixing this.
AlvaroBegue
Posts: 931
Joined: Tue Mar 09, 2010 3:46 pm
Location: New York
Full name: Álvaro Begué (RuyDos)

Re: volatile?

Post by AlvaroBegue »

Using volatile doesn't allow you to access the variable without locking. You are most likely going to invoke undefined behavior if you do so.

C++11 provides std::atomic, which is probably closer to what you want.
mcostalba
Posts: 2684
Joined: Sat Jun 14, 2008 9:17 pm

Re: volatile?

Post by mcostalba »

lucasart wrote: On the other hand, Stockfish also declares all shared variables as volatile. And I know that Marco is much more knowledgeable than I am in C++, especially when it comes to multi-threading. So I can't help wondering if there isn't indeed a good reason for all this volatile stuff :?
A variable accessed under lock protection does not require to be defined 'volatile'.

Instead if accessed by many threads outside lock protection is better to define volatile, although of course this doesn't give you any protection against races and you really need to know what you are doing.

But races are an intrinsic part of a SMP chess engine, for instance TT table is intrinsically racy for performance reasons, because to protect with lock it would be very slow...this is not a problem per-se, as long as you know it and you deal with it.