Subject: Re: [boost] [system] Would it be possible to trial a breaking change to Boost.System and see what happens?
From: Andrey Semashev (andrey.semashev_at_[hidden])
Date: 2018-01-14 11:54:29
On 01/14/18 03:27, Niall Douglas via Boost wrote:
>> , but
>> having a virtual function call overhead whenever one wants to test if
>> the error_code has a success value seems a too high price for that. I
>> don't want to be paying that price in my Linux programs, sorry.
> You have a completely misguided estimation of relative costs here. This
> argument has no basis in empirical reality.
> Also, it's prejudiced as well. Your use case never sees more than one
> success value, so you fret about the "extra cost" of an indirect jump
> for testing that. But in my use case in AFIO I have to insert extra if
> statements to work around the fact that there are multiple success
> values possible from calls to the NT kernel. So I'm getting loaded with
> overhead so you don't have to be, and given the whole original design
> purpose of Boost.System which was to wrap C error code domains into
> better C++, I don't find that fair.
I assume that the extra `if` statements you're talking about are
something like this:
if (ec.category() == my_category() &&
(ec.value() < min_success || ec.value() > max_success))
// handle failure
How would these tests be optimized if you moved the comparison into the
category? The category test would be replaced with a virtual function
call (which is arguably more expensive) but the error code value tests
would still be executed.
> (The reason I quote "extra cost" of an indirect jump is that there is no
> extra cost on any recent out of order CPU including ARM and Intel. It's
> literally zero cost in the hot cache case because it's been
> speculatively dereferenced, there isn't even a pipeline stall. Haswell
> and later can even make a virtual function call immediately calling a
> virtual function zero overhead in the hot cache case, the speculative
> execution reaches two levels down most of the time in most circumstances)
I don't think zero cost is a correct estimate. A virtual function call
involves loading an entry from the virtual function table, which may
stall and pollutes cache. Then there's the function body that consists
of a few instructions that also need to be loaded and executed. Compare
that to a couple (or four, in case of a range of codes to test for)
inlined instructions which are likely already in the cache, prefetched
with the rest of the function body. I'm sure the difference will be
there, although it may not look that significant in absolute numbers. It
will add up if you have multiple error code checks of have them in a loop.
>>> It would still return a reference, just as now. You would check for
>>> internal nullptr, if so return a static null_category.
>> Why check in the first place if you could initialize the pointer to the
>> global instance of the category?
> It cannot currently be done portably on all current compilers.
It doesn't need to. `std::system_category` is implemented by compiler
writers, they can use whatever magic they need. And they have already
done that for iostreams.
>>> But it cannot elide the potential call to an extern function, the
>>> function which retrieves the error_category&. And therefore must emit
>>> code, just in case the system_category might be fetched.
>> I don't think the current standard requires an external function call.
>> The default constructor can obtain the pointer to the global category
>> instance directly, if it is exported. `system_category()` can be an
>> inline function as well.
> You are incorrect. The C++ standard imposes a hard requirement that all
> error categories cannot - ever - have more than one instance in a
> process. That rules out inline sources of error categories.
It does not, if the category instance is exported from a shared library.
> If you don't implement that, including in custom error categories, bad
> things happen, specifically error condition testing stops working right.
Custom error categories do not need any special treatment. Only the
error category that is used in the default constructor of `error_code`
(which is currently the system category) have to be available as an
external object and initialized before the rest of the dynamic
initialization. All other categories can continue to use function-local
statics or whatever.
>> I don't have an estimate on how widespread custom error categories are,
>> but I surely have a few. I know that several Boost libraries define
>> their categories (namely, Boost.Filesystem, Boost.ASIO, Boost.Beast,
>> Boost.Thread come to mind). Searching on GitHub finds a few examples
>> of custom categories (with a lot of noise from the comments though).
>> Judging by that, I wouldn't say that custom categories are rare.
> But they are however rare enough, and moreover, extremely easy to
> upgrade. My intended proposal for WG21 is that when you compile code
> with C++ 23 or later, the new string_view signature takes effect. If you
> compile with C++ 20 or earlier, the string signature remains.
Sounds like fun for implementers of the error categories that are
supposed to be compatible with more than one language version. We
already have the painful experience with virtual overrides in codecvt
facets that changed at some point.
>> Some parts of your proposal (namely, switching to `string_view`) do not
>> have a clear migration path, especially for C++03 users.
> You didn't fully read my earlier email. There is no point using
> std::string_view with earlier than C++ 17. And boost::string_view
> doesn't have sufficient interop with std::string to substitute.
> The std::string_view message() is 100% C++ 17 or later only.
My objection was not just about `std::string_view` being C++17-only,
although it was part of it. Not all error categories can be ported to
`std::string_view` in principle.
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk