3 ms·
Very interesting! Cool that the C library holds its own, heh. One very minor quibble is that the C function contains the "counter++;" statement to increment th
by unwind 4y ago
Very interesting! Cool that the C library holds its own, heh.
One very minor quibble is that the C function contains the "counter++;" statement to increment the benchmarking counter, but the corresponding C++ function does not. Mildly confusing but probably just a sign that the code in the article is not necessarily the code that ran for the tests.
Also, very good to showcase the slightly non-intuitive use of memcpy() here. It is compiled out [1] but a very "safe" way to grab data across types. Now if only he omitted the parentheses for non-type name arguments to sizeof ... :)
[1]: https://godbolt.org/z/dc7nMEY3n https://godbolt.org/z/dc7nMEY3n
- benmmurphy 4y agois casting to (double*) unsafe in this case and are there platforms where using memcpy would be slower because the compiler doesn't know the data is correctly aligned? i see in x86-64 its using movsd which allows for both aligned and unaligned access, with unaligned access just being slower.
- unwind 4y agoCasting should be safe as far as I know, the point of void pointers is the ability to point at any kind of data. Not sure about the platform question, I tried building it for a Cortex-M3 (32-bit "small" ARM) but that still doesn't issue the memcpy() call. Instead it just loads pairs of words from memory then moves those into the FPU for comparison. The loads are marked with "@unaligned" but not sure what that actually means and didn't spend the time to research it now. [1]: https://godbolt.org/z/PGTb8abP8 https://godbolt.org/z/PGTb8abP8
- ksherlock 4y agoSwitching among godbolt compilers (at optimization 0) ... gcc on less common architectures (like esp32, risc32 or msp430), icc, and msvc don't optimize out the memcpy.
- mattarm 4y ago> Casting should be safe as far as I know I assume you're talking about this: long x = <whatever> double y = *(double*)&x; Nope, it is undefined behavior, with few exceptions, even if sizeof double == sizeof long. See https://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule https://stackoverflow.com/questions/98650/what-is-the-strict... If you got rid of this rule a huge number of optimizations would be lost, since the compiler would have to assume that any memory, of any type, could change if anything at all is modified through a pointer. You'd be surprised how slow everything would be. You'd also be surprised how much code violates this and gets away with it. Time bombs waiting to happen. ;-) memcpy(), and more generally accessing bytes through char*, are how you avoid this kind of undefined behavior. It comes up so often that C++20 added std::bit_cast for this same use: https://en.cppreference.com/w/cpp/numeric/bit_cast https://en.cppreference.com/w/cpp/numeric/bit_cast
- benmmurphy 4y agoI think he's referring to the cast: (double*)->(void*)->(double*) which should be fine. I'm guessing the original article author prefers using memcpy because there doesn't seem to be any performance cost and its correct in other situations where the alignment is wrong, or there is type aliasing. The advantage would be if you always use a safe method in a code base then you don't have to worry about proving that possibly unsafe methods are safe.
- unwind 4y agoNo, in this context (a qsort() comparison callback) I'm talking about using casts to access the values to compare. Here's how I would have written it, without memcpy(): static int cmp_double(const void *va, const void *vb) { const double a = *(const double *) va, b = *(const double *) vb; return a < b ? -1 : a > b; } I know you can't "jump types", but this doesn't, since qsort() is passing pointers to doubles and we're just getting them back. Edit: missed const:s in function body oops.
- scaredginger 4y agoYes, you're right in that casting an arbitrary void * to double and dereferencing would be unsafe, but in this case, the void pointers point to doubles by construction, so they must be aligned
- mattarm 4y agoCasting from any T* to void* back to T* is always safe. That is one of the few safe things you can do with a void*, and kinda the whole point. :-) I think casting from void* back to double* is the better option here. memcpy() is overkill here since that idiom is mainly for reinterpreting data with a different type without triggering undefined behavior: https://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule https://stackoverflow.com/questions/98650/what-is-the-strict.... We're not changing the type, so why confused people with an overly powerful idiom? That said, the memcpy() idiom is common and useful enough that C++ added a specific API for it: https://en.cppreference.com/w/cpp/numeric/bit_cast https://en.cppreference.com/w/cpp/numeric/bit_cast. Casting and the memcpy() idiom compile down to the same code https://godbolt.org/z/xY6W9ja69 https://godbolt.org/z/xY6W9ja69*
- Teckla 4y agoCasting from any T* to void* back to T* is always safe. Are you sure this also applies to function pointers?
- mort96 4y agoI'm guessing that even though std::sort calls compare slightly more, it'll be faster, since the compiler has available the sort function, the comparison function and the types. In C, the call to qsort is just a normal function call; and likewise, from within qsort, the call to the compare function is just a normal function call, and the indexing operations have to use the size value that's only known at runtime.