4

std::uninitialized_copy copies into an uninitialized range of memory. This could be done using memmove for bitwise copyable types. I stepped through below example code in gdb (compiling with gcc 5.2.0). Thereby I observed that memmove isn't used at all.

In the example __is_trivial(Bar) is used to determine whether memmove may be used. It (correctly) evaluates to false as Bar has a non-trivial default constructor (cf. call to std::__uninitialized_copy<false>::__uninit_copy(...) in bits/stl_uninitialized.h line 123ff). But why is __is_trivial even relevant to std::uninitialized_copy? According to Bjarne std::is_trivially_copyable should be sufficient. Note that the latter evaluates to true in the example, ie. the memmove optimization is applicable.

I'm aware, that the standard does not require any specific implementation of std::uninitialized_copy. I'm just wondering why __is_trivial is favored even if std::is_trivially_copyable is present as an applicable alternative to the gcc implementation?

Example Code:

#include <iostream>
#include <memory>
#include <vector>
#include <type_traits>

struct Bar
{
   Bar () : v(42) {};
   Bar(Bar const &) = default;
   Bar(Bar &&) = default;
   Bar & operator=(Bar &&) = default;
   Bar & operator=(Bar const &) = default;
   ~Bar() = default;
   int v;
};

int main() {
   std::cout
      << std::is_trivially_move_constructible<Bar>::value
      << " " << std::is_trivially_copy_constructible<Bar>::value
      << " " << std::is_trivially_copyable<Bar>::value
      << " " << std::is_trivial<Bar>::value
      << " " << __is_trivial(Bar) << std::endl;
   size_t const num_elements = 1 << 27;
   std::vector<Bar> v(num_elements);
   Bar * vc = (Bar *) std::malloc(num_elements * sizeof(Bar));
   std::uninitialized_copy(v.begin(), v.end(), vc);
   std::free(vc);
}

Example Output: 1 1 1 0 0

Update: We did some tests comparing actual runtimes of memmove, uninitialized_copy and a simple for loop. If Bar is trivial (cf. __is_trivial(Bar)), uninitialized_copy is as fast as memmove, if it's not, uninitialized_copy is as fast as our for loop. Overall memmove was only significantly faster (2x) on small Bars (ie. change int v; to char v;). Otherwise the performance was essentially the same.

Edit: Correct references to std::is_trivially_.... State title more precisely.

Community
  • 1
  • 1
m8mble
  • 1,513
  • 1
  • 22
  • 30
  • 2
    You are mixing up `is_trivially_copy_constructible` and `is_trivially_copyable`. Bjarne's example code uses the second, not the first. – pmr Nov 02 '15 at 16:46
  • 2
    That's true, I've corrected the question. The problem remains though as `is_trivially_copyable` evaluates to `true` as well. – m8mble Nov 03 '15 at 08:38
  • You copy to an uninitialized range of memory. The way I interpret it, there cannot be overlaps (since one is initialized the other is not), so you could use `memcpy` instead of `memmove`. – Karoly Horvath Nov 04 '15 at 10:33
  • libstdc++ welcomes patches (or bugzilla entries if you don't want to spend the time). – Marc Glisse Nov 04 '15 at 13:24
  • Note that GCC doesn't have a `__is_trivially_copyable` intrinsic until GCC 5. – T.C. Dec 28 '15 at 00:26

1 Answers1

1

For future readers: This has been submitted as a gcc enhancement here.

m8mble
  • 1,513
  • 1
  • 22
  • 30