C++ Logo

std-proposals

Advanced search

回复: 回复: Delay the judgement for coroutine function after the instantiation of template entity.

From: chuanqi.xcq <yedeng.yd_at_[hidden]>
Date: Fri, 22 Jan 2021 11:57:30 +0800
Hi,

> I find myself agreeing with both camps here - but one of the camps is talking about design, and the other about language semantics, and they don't actually conflict.

Yes, I totally agree with you. We want to talk about why constexpr-if can't support for coroutine. But I find others are more interesting in the design design pattern for coroutine. I agree with that the design pattern for coroutine is more valuable. And our problem is solved by other methods in fact.
But I feels the two things are not the same, I feel they want to say:
- If you could use other methods to sovle the problem better other than constexpr-if for coroutine, the support of constexpr-if for coroutine shouldn't be enabled.

I think it is really valuable to discuss what's the best practice to solve the problem for all of us. And I still want to say:
- We need to enable constexpr-if for coroutine since I wonder most of progammers would be suprising that constexpr-if won't support for coroutine. Every one needs to lookup in the C++ Standard carefully to get this behavior is desired.

> Intuition is nice and all that, but that description seems imprecise
at best, and possibly incorrect at worst. An if constexpr
is a pair of templated block statements. The usual template rules
apply, like "there must be at least one valid specialization".

I agree with the statement "there must be at least one valid specialization". But I think it doesn't matter for constexpr-if for coroutine. We only delay the judgement for cororoutine after the instantiation. If it isn't a valid specialization, it would stiil emit errors.

> Because a function should either be a coroutine or not be a coroutine.

I agree with this statement. But a template function isn't a function. From 13.7.7.1.1 of N4878, we can get:
```
A function template defines an unbounded set of related functions.
```

It says a template function is a set of functions. So I think it isn't confilict with the statement "a function should either be a coroutine or not be a coroutine".

> Well, to be fair, the static if proposal had a couple of Over The Nuclear Weapons of My Friends opposition arguments.

I can't understand this statement literally. Does it mean that this change would give a big influence? I think it is a small change and maybe we only need to delete the note from 8.7.5.1 [stmt.return.coroutine].

> Being a coroutine *does* change these things. External code has to be
written differently for the coroutine version than the non-coroutine
version.

This may be one side effects. But my thought is the template argument is part of the function declaration. And now I think it is not easy to know whether function is coroutine by the declaration.

For example, the following code:
```
SomeType func();
```

Can we know whether func is coroutine or not from declaration? No, we need to see the implementation or the comment to decide.

> Let's say that we have a function X which is, by its nature, an
asynchronous coroutine function. This means that X has to schedule its
resumption based on some external asynchronous process, like doing
file/networking IO, etc. Doing this is *why* you made the function a
coroutine; it is the nature of X that it waits on something
asynchronously. And let's say that we have some function Y which gets
called by X.

The picture I see is that X is a coroutine and Y is a caller of X which need to do something only after X made his job. So Y should be a coroutine too, isn't it?

> I was looking for something more specific in terms of the actual code,
not the details of how the compiler generated non-optimal assembly.
And specifically, I'm trying to understand the *meaning* of the code,
not just a bunch of no-name functions that call each other. I want to
know the details of what you're trying to do that led to the cases of
both good performance and bad performance.

Because of business secrets, I can't show the original codes and I also think it isn't necessary. Here is a more detailed example I think is enough to discuss:
```
auto ReaderByPrefix(const std::string& prefixKey, const std::vector<std::string>& suffixKeys,
                    CacheType cache, MemoryPoolPool* pool, MetricsCollector* metricsCollector) {
    assert(pool);
    KeyType keyHash(0);
    if (!GetKeyHash(prefixKey, keyHash))
    {
        if (metricsCollector)
            metricsCollector->EndQuery(KeyHash, "Not find Hash for PrefixKey: ", prefixKey);
        co_return Iterator(pool);
    }
    auto Res = co_await ReaderByKey(keyHash, suffixKeys, cache, pool, metricsCollector);
    tryInsertToCache(Res, cache);
    co_return Res;
}

auto ReaderByKey(KeyType keyHash, const std::vector<std::string>& suffixKeys, CacheType cache,
                 MemoryPoolPool* pool, MetricsCollector* metricsCollector)
{
    assert(pool);
    std::vector<uint64_t> skeyHashs;
    if (!GetSKeyHashVec(suffixKeys, skeyHashs))
    {
        if (metricsCollector)
            metricsCollector->EndQuery(KeyHash, "Not find SKeyHash for vec for hash: ", keyHash);
        co_return Iterator(pool);
    }
    auto Res = co_await LookupVecs(keyHash, std::move(skeyHashs), cache, pool, metricsCollector);
    tryInsertToCache(Res, cache);
    co_return Res;
}

auto LookupVecs(KeyType keyHash, std::vector<uint64_t> skeyHashs, CacheType cache,
                MemoryPoolPool* pool, MetricsCollector* metricsCollector) {
    auto Res = co_await SearchInCache(cache, keyHash, skeyHashs, pool, metricsCollector);
    if (Res) {
        metricsCollector->record(Res);
        co_return Res;
    }
    auto Reader = pool->getReader();
    auto SearchType = keyHash.getType();
    switch (SearchType) {
        case ReaderType1:
            // ReaderType1::LookupImpl maybe blocking
            auto Res = co_await static_cast<ReaderType1*>(Reader)->LookupImpl(keyHash, skeyHashs, pool, metricsCollector);
            tryInsertToCache(Res, cache);
            break;
        case ReaderType2:
            // and so on;
            break;
        default: {
            // ...
        }
    }

    if (metricsCollector)
        metricsCollector->EndQuery(Res, "End of query");
    co_return Iterator(pool);
}
```

Our project is a storage and query library. So there is a lot of interfaces which would be called by the user from the upper layer. Once a user starts a query, we need to made the query first and give the result to the user after that.

> I want to know the details of what you're trying to do that led to the cases of
both good performance and bad performance.

When the concurrency is very high (aka there are a lot of queries from a lot of users), we need to switch very frequently. And here is the advantage of stackless coroutine, the switch cost is most like a normal function call while the thread and stackful coroutine would save and change the context.

The reason why the stackless coroutine get bad performance is that when concurrency is low, stackless coroutine need to pay for the creation cost for coroutine frame and the big try-catch statement which required by the language design.

---
Thanks,
Chuanqi
------------------------------------------------------------------
发件人:Jason McKesson via Std-Proposals <std-proposals_at_[hidden]>
发送时间:2021年1月22日(星期五) 03:38
收件人:std-proposals <std-proposals_at_[hidden]cpp.org>
抄 送:Jason McKesson <jmckesson_at_[hidden]>
主 题:Re: [std-proposals] 回复: Delay the judgement for coroutine function after the instantiation of template entity.
On Thu, Jan 21, 2021 at 1:06 PM Ville Voutilainen via Std-Proposals
<std-proposals_at_[hidden]rg> wrote:
>
> On Thu, 21 Jan 2021 at 19:53, Gašper Ažman via Std-Proposals
> <std-proposals_at_[hidden]> wrote:
> >
> > I find myself agreeing with both camps here - but one of the camps is talking about design, and the other about language semantics, and they don't actually conflict.
> >
> > I personally find it extremely surprising that code guarded by if constexpr is not a parseable comment.
> >
> > I would underline that 3 times if I could (4's too much, 5's right out).
> >
> > Regardless of the inadvisability of the use of these semantics, I would consider fixing this a DR. It's breaking my intuition about what if constexpr does, which aught to be "this is a comment that parses for the purposes of figuring out where it ends".
>
> Intuition is nice and all that, but that description seems imprecise
> at best, and possibly incorrect at worst. An if constexpr
> is a pair of templated block statements. The usual template rules
> apply, like "there must be at least one valid specialization".
>
> Whether that's compatible with your description is another matter, and
> what that really means for a co_keyword in one of the branches
> and no co_keywords in another is another separate matter. :)
The thing is, `if constexpr` actually does work "correctly" with
regard to auto-deducing the return type of a function. A discarded
return statement doesn't count, so you can `if constexpr` return
different types. Given that, it would make some sense to make the
`co_` identifiers only create a coroutine if they are not discarded.
-- 
Std-Proposals mailing list
Std-Proposals_at_[hidden]
https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals

Received on 2021-01-21 21:57:36