5 ms·
I forked this on Github and changed the C code to be more idiomatic, i.e. using error codes instead of allocating a struct (!) and strduping an error string (!!
by juliangoldsmith 10y ago
I forked this on Github and changed the C code to be more idiomatic, i.e. using error codes instead of allocating a struct (!) and strduping an error string (!!) the way it was originally done. It causes the C version to perform significantly better than the C++ version.
Code is here: https://github.com/julian-goldsmith/excspeed https://github.com/julian-goldsmith/excspeed
- pixel_fcker 10y agoDo you have numbers? I want to try doing the same test against the proposed expected<T> as well but haven't had the time yet. EDIT: From your version on a 2013 rMBP, Apple clang 8, I get (top-left 5x5 corner of the matrix) eccCC ecccc ceCcC eceCc ceeec compared to the original: EEEec EEEEE EEEEE EEEEE EEEEE EDIT2: And switching out 'throw std::runtime_error("Error");' for 'throw int();' eccCC eEecc eeeec ecCec Eeeee
- juliangoldsmith 10y agoI ran it on my x86_64 Linux server, using gcc. The full results can be found at https://juliangoldsmith.com/excspeed%20results.txt https://juliangoldsmith.com/excspeed%20results.txt, but the matrix output looked like this: ecCCCCCCCC ccCCCCCCCC ccccCCCCCC cccccccCCC ecccccccCC cccccccCCC ecccccCCcc cccccccccC ecccccCccc cccccccccc
- pixel_fcker 10y agoEDIT3: Changing C code to check for error and increment the error variable if it's non-zero, compared to C++ throwing an int(): ecccc eeeec eecec eeeee EEEEE and against throwing std::runtime_error("Error"): ecccC eeece ceeec eeEee EEeEe
- kentonv 10y agoTo be fair, you should also modify the exceptions case to throw a type of exception that doesn't encapsulate a dynamically-allocated string. Otherwise you're now comparing "errors with dynamically allocated string content" vs. "errors that are just a number". Everyone agrees that the latter will be much faster, but there are obviously significant advantages to dynamic content in terms of expressiveness of the errors and ease of debugging. In any case, C++ exceptions are perfectly capable of being just numbers if that's what you want (in fact, you can literally use an exception type of `int`). I think the point of the article, though, was to compare methods of transmitting the error, not methods of representing the error. (Also, presumably your change doesn't affect the 0%-error-rate case, where exceptions should still be faster?)
- juliangoldsmith 10y ago>Also, presumably your change doesn't affect the 0%-error-rate case, where exceptions should still be faster? In my earlier test, it looked like it was pretty much even between the two. I haven't looked at assembly or anything, but I'd imagine SEH has a bit of overhead. As for the type for exceptions, I'm seeing what happens when the C++ version throws ints. I'll update here once I have results. EDIT: Full results can be found at https://juliangoldsmith.com/excspeed%20results%202.txt https://juliangoldsmith.com/excspeed%20results%202.txt , graph reproduced below. I've also updated the code on my copy of the Github repo. ecCCCCCCCC ccccCCCCCC cccCCCCCcC cccccCCCCC ccccccccCc ecccccccCc eecccccCcC cccccccCcc eccccccccc cccccccccc EDIT2: I'm also testing it without optimization enabled, to see whether optimization was making the C version faster than the C++ version. EDIT3: Apparently turning off optimization makes the C version very significantly faster on my platform: https://juliangoldsmith.com/excspeed%20results%203.txt https://juliangoldsmith.com/excspeed%20results%203.txt cCCCCCCCCC cCCCCCCCCC cCCCCCCCCC cCCCCCCCCC cCCCCCCCCC cCCCCCCCCC cCCCCCCCCC cCCCCCCCCC cCCCCCCCCC cCCCCCCCCC
- kentonv 10y agoC++ exceptions, in modern implementations, should have no time overhead when no exception is thrown. C error codes have the overhead of comparisons to check if the return value is an error return. EDIT: To expand on this: Modern implementations of C++ exceptions build tables mapping code addresses to information about the exception context at that address. The table is created by the compiler and stored in a separate segment of the executable. If no exception is thrown, the table is never consulted. When an exception is thrown, the exception-handling code consults the tables while unwinding the stack. For each return address, the EH tables tell it what destructors need to be run to unwind that stack frame. So, there's no need for comparisons or branching in the code itself. Of course, table-driven execution is expected to be slow (for the same reason interpreted languages are slow), but the idea is to optimize for the common case where there are no exceptions.
- kentonv 10y agoI looked closer at the C code and noticed that it's not really realistic error checking (both before and after your changes). Each level of function is simply tail-calling to the next level. No level is checking whether the return code indicates an error. Instead, an error pointer is passed all the way down the stack and then possibly filled in or not at the bottom, but never inspected in the intermediate frames. This means all the extra branching that is normally associated with error code checking is being skipped in this test. It's not surprising, then, that you're seeing the exception-based code and the error-code-based code performing the same (one or the other winning at random) in the 0% error rate case -- they are actually the same code. In a more realistic test, each intermediate function would call the next function, then inspect its result to see if it indicated an error. If so, the intermediate function would return early (probably passing along the same error code). Otherwise it should do some additional processing on the result (maybe increment it or something, for testing purposes) before returning. In the C++ exceptions version, the error code check could be skipped. With all that said, I would still expect that when exceptions are actually thrown, they should be slower than error code checking.