Search code examples
c++cvolatile

Achieving the effect of 'volatile' without the added MOV* instructions?


I have a piece of code that must run under all circumstances, as it modifies things outside of its own scope. Let's define that piece of code as:

// Extremely simplified C++ as an example.
#include <iostream>
#include <map>
#include <cstdint>
#if defined(__GNUC__) || defined(__GNUG__) || defined(__clang__)
#include <x86intrin.h>
#elif defined(_MSC_VER)
#include <intrin.h>
#endif

uint64_t some_time_function() {
  unsigned int waste;
  return __rdtscp(&waste);
};

void insert_into_map(std::map<uint64_t, float>& data, uint64_t t1, uint64_t t0, float v) {
  data.emplace((t1 - t0), v);
};

void fn(std::map<uint64_t, float>& map_outside_of_this_scope) {
  const float a = 1;
  const float b = 2;
  float v = 0;
  for (uint32_t i = 0; i < 1000000; i++) {
    uint64_t t0 = some_time_function();
    v = (v + b) - a;
    uint64_t t1 = some_time_function();
    insert_into_map(map_outside_of_this_scope, t1, t0, v);
  }
}

int main(int argc, const char** argv) {
  std::map<uint64_t, float> my_map;
  fn(my_map);
  std::cout << my_map.begin()->first << std::endl;
  return 0;
}

This looks like an optimal target for the optimizer in compilers, and that is what I have observed with my code as well: map_outside_of_this_scope ends up empty. Unfortunately the map_outside_of_this_scope is critical to operation and must contain data, otherwise the application crashes. The only way to fix this is by marking v as volatile, however that makes the application significantly slower than an equivalent Assembly based function.

Is there a way to achieve the effect of volatile, without the MOV instructions of volatile?


Solution

  • Inasmuch as you assert in comments that you are most interested in a narrow answer to the question ...

    Is there a way to achieve the effect of volatile, without the MOV instructions of volatile?

    ... the only thing we can say is that C and C++ do not specify the involvement of MOV or any other specific assembly instructions anywhere, for any purpose. If you observe such instructions in compiled binaries then those reflect implementation decisions by your compiler's developers. What's more, where you see them, the MOVs are most likely important to implementing those semantics.

    Additionally, neither C nor C++ specifies any alternative feature that duplicates the rather specific semantics of volatile access (why would they?). You might be able to use inline assembly to achieve custom, different effects that serve your purpose, however.


    With respect to the more general problem that inspired the above question, all we can really say is that the multiple compilers that perform the unwanted optimization are likely justified in doing so, for reasons that are not clear from the code presented. With that in mind, I recommend that you broaden your problem-solving focus to search for why the compilers think they can perform the optimization when volatile is not involved. To that end, construct a MRE -- not for us, but because the exercise of MRE construction is a powerful debugging technique in its own right.