The last time Hackerfall tried to access this page, it returned a not found error. A cached version of the page is below, or clickhereto continue anyway

Look at This Modern Sausage – Baremetal Labs

If you think adopting the latest-and-greatest C++11/14/17 will guarantee your firm the latest technology, please follow me looking at how this sausage is made. We will be looking at gcc-7-20161204 and clang/libc++ 3.9 (stable) sources fresh from last month releases.

std::string::to_string( T )

This one is a crowd favorite. to_string resides in libc++/src/string.cpp. Let’s assume T=int. The implementation calls

and then

Performance warning: clang’s libc++ is running a loop, increasing the size of the string in the expectation that it does not need to allocate more memory.

Same thing with gcc-7/libstdc++-v3

and __to_xstring is defined in include/ext/string_conversions.h as

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

  // Helper for the to_string / to_wstring functions.

  template

    _String

    __to_xstring(int (*__convf) (_CharT*, std::size_t, const _CharT*,

__builtin_va_list), std::size_t __n,

const _CharT* __fmt, ...)

    {

      // XXX Eventually the result should be constructed in-place in

      // the __cxx11 string, likely with the help of internal hooks.

      _CharT* __s = static_cast<_CharT*>(__builtin_alloca(sizeof(_CharT)

  * __n));

 

      __builtin_va_list __args;

      __builtin_va_start(__args, __fmt);

 

      const int __len = __convf(__s, __n, __fmt, __args);

 

      __builtin_va_end(__args);

 

      return _String(__s, __s + __len);

    }

The big problem here is that gcc/libstdc++ is always allocating memory (exactly __n = 16 bytes) for the biggest string the conversion can yield. So even if you are printing “0” it will always allocate 16 characters. Not counting the time for std::string creation itself.

So BOTH implementations of std::to_string are calling vsnprintf() which has been part of the c library for what, 30 years already? I have to look it up, perhaps more.

The sausage: std::to_string() is just a wrapper, with an expensive hack job inside. Just avoid it.

std::chrono::now()

std::chrono is one of the biggest stars of C++11. It promised to standardize all the clock mechanisms once and for all. Let’s look at the implementation? This is libstdc++-v3/src/c++11/chrono.cc line 53

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

system_clock::time_point

system_clock::now() noexcept

{

#ifdef _GLIBCXX_USE_CLOCK_REALTIME

timespec tp;

// -EINVAL, -EFAULT

#ifdef _GLIBCXX_USE_CLOCK_GETTIME_SYSCALL

syscall(SYS_clock_gettime, CLOCK_REALTIME, &tp);

#else

clock_gettime(CLOCK_REALTIME, &tp);

#endif

return time_point(duration(chrono::seconds(tp.tv_sec)

+ chrono::nanoseconds(tp.tv_nsec)));

#elif defined(_GLIBCXX_USE_GETTIMEOFDAY)

timeval tv;

// EINVAL, EFAULT

gettimeofday(&tv, 0);

return time_point(duration(chrono::seconds(tv.tv_sec)

+ chrono::microseconds(tv.tv_usec)));

#else

std::time_t __sec = std::time(0);

return system_clock::from_time_t(__sec);

#endif

}

Surprise! It is calling clock_gettime(), which has been in the libc since SUSv2, POSIX.1-2001. On clang 3.9.0/src/chrono.cpp it looks like a straight copy of it:

system_clock::time_point

system_clock::now() _NOEXCEPT

{

#ifdef CLOCK_REALTIME

struct timespec tp;

if (0 != clock_gettime(CLOCK_REALTIME, &tp))

__throw_system_error(errno, "clock_gettime(CLOCK_REALTIME) failed");

return time_point(seconds(tp.tv_sec) + microseconds(tp.tv_nsec / 1000));

#else // !CLOCK_REALTIME

timeval tv;

gettimeofday(&tv, 0);

return time_point(seconds(tv.tv_sec) + microseconds(tv.tv_usec));

#endif // CLOCK_REALTIME

}

Also, I have seen folks using and boosting std::high_resolution_clock as a new feature. A closer look reveals:

<span class="hljs-comment">/**

* @brief Highest-resolution clock

*

* This is the clock "with the shortest tick period." Alias to

* std::system_clock until higher-than-nanosecond definitions

* become feasible.

*/</span>

<span class="hljs-keyword">using</span> high_resolution_clock = system_clock;

What a disappointment.

std::random_device

So what about <random> that allegedly got rid of all the libc rand() calls? Here we see gcc is shamelessly opening /dev/urandom, a Linux kernel facility

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

void

random_device::_M_init(const std::string& token)

{

const char *fname = token.c_str();

 

if (token == "default")

{

       fname = "/dev/urandom";

}

else if (token != "/dev/urandom" && token != "/dev/random")

fail:

std::__throw_runtime_error(__N("random_device::"

"random_device(const std::string&)"));

 

_M_file = static_cast&lt;void*&gt;(std::fopen(fname, "rb"));

if (!_M_file)

goto fail;

}

On clang 3.9.0/src/random.cpp we see basically the same thing

random_device::random_device(const string& __token)

    : __f_(open(__token.c_str(), O_RDONLY))

{

    if (__f_ &lt; 0)

        __throw_system_error(errno, ("random_device failed to open " + __token).c_str());

}

std::thread

On libstdc++-v3 we can see on include/std/thread:

  /// thread

  class thread

  {

  public:

    // Abstract base class for types that wrap arbitrary functors to be

    // invoked in the new thread of execution.

    struct _State

    {

      virtual ~_State();

      virtual void _M_run() = 0;

    };

    using _State_ptr = unique_ptr;

 

    typedef __gthread_t native_handle_type;

Then __gthread is defined in gcc-7/libgcc/gthr-posix.h:

typedef pthread_t __gthread_t;

typedef pthread_key_t __gthread_key_t;

typedef pthread_once_t __gthread_once_t;

Bingo! Here it is C++ leaning on its poor cousin. std::threads is in fact, a gift-wrapped pthreads which was defined in POSIX.1c, Threads extensions (IEEEStd 1003.1c-1995). We know gcc it is using gthr-posix.h because you can see in the command line:

$ gcc -v

Using built-in specs.

Target: x86_64-w64-mingw32

Configured with: ../gcc-6.2.0/configure --prefix=/mingw64 --with-local-prefix=/m                                                          ingw64/local --build=x86_64-w64-mingw32 --host=x86_64-w64-mingw32 --target=x86_6                                                          4-w64-mingw32 --with-native-system-header-dir=/mingw64/x86_64-w64-mingw32/includ                                                          e --libexecdir=/mingw64/lib --enable-bootstrap --with-arch=x86-64 --with-tune=ge                                                          neric --enable-languages=c,lto,c++,objc,obj-c++,fortran,ada --enable-shared --en                                                          able-static --enable-libatomic --enable-threads=posix --enable-graphite --enable                                                          -fully-dynamic-string --enable-libstdcxx-time=yes --disable-libstdcxx-pch --disa                                                          ble-libstdcxx-debug --disable-isl-version-check --enable-lto --enable-libgomp --                                                          disable-multilib --enable-checking=release --disable-rpath --disable-win32-regis                                                          try --disable-nls --disable-werror --disable-symvers --with-libiconv --with-syst                                                          em-zlib --with-gmp=/mingw64 --with-mpfr=/mingw64 --with-mpc=/mingw64 --with-isl=                                                          /mingw64 --with-pkgversion='Rev2, Built by MSYS2 project' --with-bugurl=https://                                                          sourceforge.net/projects/msys2 --with-gnu-as --with-gnu-ld

Thread model: posix

gcc version 6.2.0 (Rev2, Built by MSYS2 project)

On clang/libc++ the same story

#if defined(_LIBCPP_HAS_THREAD_API_PTHREAD)

#include

#include

#endif

 

(...)

 

// Thread

typedef pthread_t __libcpp_thread_t;

 

inline _LIBCPP_ALWAYS_INLINE

int __libcpp_thread_create(__libcpp_thread_t* __t, void* (*__func)(void*), void* __arg)

{

    return pthread_create(__t, 0, __func, __arg);

}

Again std::thread is pthreads, wrapped. Oh you C++, can’t you do anything by yourself?

No need to add, std::shared_mutex, std::shared_timed_mutex, shared_lock also are wrapping pthreads on posix platforms.

std::regex

I got to give this one: both implementations are native C++. Well done.

Coroutines

Let’s talk about something that has not even made into the standard yet? Well coroutines are still a proposal but the C library has had it since 2001 with makecontext/setcontext.

Here I had a surprise: even though there is no mention of coroutines in both the libc++ or the stdlibc++, boost::context DOES have a very comprehensive implementation of the execution stack swap by Oliver Kowalke (see boost/libs/context/src/asm/ontop_xxx) where XXX is the platform eg x86_64_sysv_elf_gas. That is beautiful engineering, the type I would like to see all over the current C++ implementations.

However, I really doubt this will make into the standard the way it is on boost so I still stand correct.

Conclusion

I could go on for hours perusing both implementations of the standard library, even going into supc++ as well, showing that the entire “Modern C++” infrastructure is just wrappers around our old friend POSIX & WIN32 C-library.

Worse, usually these wrappers will impose significant performance penalties to your programs in order to stay consistent with the ISO C++ standard requirements. I strongly encourage you to glance over and have both the gcc/stdlibc++ and the libc++ source trees unzipped somewhere in your hard drive. The gcc/stdlibc++ because it is the de facto standard – it is even used by clang by default. The libc++, because its implementation is really 10x less cluttered than that of GCC (as the clang compiler sources are also much easier to read than gcc itself).

The libstdc++-v3 sources are found inside the GCC source tree, which can be downloaded from https://gcc.gnu.org/releases.html

The libc++ is provided as a module that can be inserted into the LLVM project tree. It is optional. It can be downloaded from http://libcxx.llvm.org/.

I want to show the new standards are all wrappers. Some lazy wrappers, some better implemented. I wanted to destroy the myth some MDs have that adherence to the latest standards is necessary for having the best technology onboard. The latest technology you will find on places like github, boost, the Linux kernel, vendor white papers and most importantly, in software as configurable hardware.

Continue reading on www.vitorian.com