Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow user to customize the new/delete behaviour of Lazy's coroutine state #393

Merged
merged 12 commits into from
Oct 28, 2024

Conversation

LXYan2333
Copy link
Contributor

@LXYan2333 LXYan2333 commented Oct 26, 2024

…state

Why

Close #392

What is changing

在LazyPromiseBase里重载operator new/delete以达到控制coroutine state分配行为的目的。

提供async_simple::coro::pmr命名空间:

namespace pmr {
// 类似std::pmr::memory_resource,但是不支持对齐参数(避免使用到17的std::align_val_t库特性)并且不支持抛出异常(因为定义了get_return_object_on_allocation_failure函数)
// This is a class similar to std::pmr::memory_resource, but do not have
// alingment parameter and do not allow throwing exceptions, because
// get_return_object_on_allocation_failure() is defined on all subclasses of
// LazyPromiseBase
class memory_resource {
public:
    void* allocate(std::size_t bytes) noexcept { return do_allocate(bytes); };

    void deallocate(void* p, std::size_t bytes) noexcept {
        do_deallocate(p, bytes);
    };

    bool is_equal(const memory_resource& other) const noexcept;

    virtual ~memory_resource() = default;

private:
    virtual void* do_allocate(std::size_t bytes) noexcept = 0;

    virtual void do_deallocate(void* p, std::size_t bytes) noexcept = 0;

    virtual bool do_is_equal(const memory_resource& other) const noexcept = 0;
};

// 对于使用了17以上标准库的用户,提供一个方便的转换类
#if __has_include(<memory_resource>)
class std_pmr_resource_adaptor : public memory_resource {
    std::pmr::memory_resource* m_resource;

    void* do_allocate(std::size_t bytes) noexcept override {
        try {
            return m_resource->allocate(bytes);
        } catch (std::bad_alloc& e) {
            return nullptr;
        }
    }

    void do_deallocate(void* p, std::size_t bytes) noexcept override {
        m_resource->deallocate(p, bytes);
    }

    bool do_is_equal(const memory_resource& other) const noexcept override {
        return this == &other;
    }

public:
    explicit std_pmr_resource_adaptor(std::pmr::memory_resource* resource)
        : m_resource(resource) {}

    std_pmr_resource_adaptor(const std_pmr_resource_adaptor&) = delete;
    std_pmr_resource_adaptor& operator=(const std_pmr_resource_adaptor&) =
        delete;
};
#endif

// 模拟标准库的new_delete_resource
namespace detail {

class global_new_delete_resource : public memory_resource {
    void* do_allocate(std::size_t bytes) noexcept override {
        return ::operator new(bytes, std::nothrow);
    }

    void do_deallocate(void* p, std::size_t bytes) noexcept override {
        // noexcept since C++11
        ::operator delete(p, bytes);
    }

    bool do_is_equal(const memory_resource& other) const noexcept override {
        return this == &other;
    }
};

constinit inline global_new_delete_resource global_new_delete_resource_object{};

}  // namespace detail

inline memory_resource* new_delete_resource() {
    return &detail::global_new_delete_resource_object;
};
}  // namespace pmr

三个重载的operator new/delete函数:

    void* operator new(std::size_t size, pmr::memory_resource* resource,
                       ...) noexcept {
        char* r = static_cast<char*>(
            resource->allocate(size + sizeof(pmr::memory_resource*)));
        *reinterpret_cast<pmr::memory_resource**>(r + size) = resource;
        return r;
    }

    void* operator new(std::size_t size) noexcept {
        char* r = static_cast<char*>(pmr::new_delete_resource()->allocate(
            size + sizeof(pmr::memory_resource*)));
        *reinterpret_cast<pmr::memory_resource**>(r + size) =
            pmr::new_delete_resource();
        return r;
    }

    void operator delete(void* ptr, std::size_t size) noexcept {
        char* p = static_cast<char*>(ptr);
        (*reinterpret_cast<pmr::memory_resource**>(p + size))
            ->deallocate(p, size + sizeof(pmr::memory_resource*));
    }

因为

Coroutine state is allocated dynamically via non-array operator new.

因此不重载operator new[]算符。

Example

见pmr_lazy.cpp

@CLAassistant
Copy link

CLAassistant commented Oct 26, 2024

CLA assistant check
All committers have signed the CLA.

@LXYan2333
Copy link
Contributor Author

LXYan2333 commented Oct 26, 2024

跑了几个测试都因为address sanitizer报错退出了,切换到1.3分支编译运行也还是报错,所以测试我也没跑完……

有啥需要修改补充的吗?

Edit:

bazel我没用过,那个是我模仿着前面的代码写的。不知道写对没有。

@LXYan2333
Copy link
Contributor Author

尝试了一种更好的办法,可以降低用户使用std::pmr::memory_resource时的开销。根据<memory_resource>的存在与否,分别将lazy_pmr指向std::pmr或async_simple::coro::pmr,使得用户在使用高版本stdlib时没有额外开销。

其中需要注意的是,std::pmr::memory_resource默认对齐alignof(std::max_align_t),而promise的operator new好像不支持传入对齐的参数,所以在operator new这里不知道对齐到多少为好,只好用默认参数了。这可能导致产生过高的对齐限制。

此外代码使用了c++17的enum class align_val_t : size_t {};,我把它从<new>头文件连带几个17新加的operator new的声明一起拷进代码里了。(如果有其他代码库也拷了,可能会导致align_val_t重复定义)

@LXYan2333
Copy link
Contributor Author

emm把除了发布github pages的CI都跑通了。

@LXYan2333
Copy link
Contributor Author

LXYan2333 commented Oct 27, 2024

根据@4kangjc的建议,改用PromiseAllocator分配Lazy的coroutine state。由于定义了get_return_object_on_allocation_failure,因此需要noexcept的PromiseAllocator。

我尝试过用

template <class Allocator, bool no_except_new = false>
……
void* operator new(size_t) noexcept(no_except_new)

的方式,这样应该可以编译期确定函数是否抛异常,但是clang报错说operator new必须要noexcept才能返回nullptr……没办法只能把模板复制一份改一下了。

另外引入 <algorithm>头文件,不然macos的CI说找不到std::max的定义。(我也不知道为什么原来能过CI……)

Comment on lines 315 to 325
try {
void* const ptr = ::operator new[](size + sizeof(DeallocFn));
const DeallocFn dealloc = [](void* const ptr, const size_t size) {
::operator delete[](ptr, size + sizeof(DeallocFn));
};
::memcpy(static_cast<char*>(ptr) + size, &dealloc,
sizeof(DeallocFn));
return ptr;
} catch (...) {
return nullptr;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这种写法有点别扭,直接调用 noexcept 的 new 比较好

Comment on lines 333 to 337
try {
return Allocate(al, size);
} catch (...) {
return nullptr;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同上,让 Allocate noexcept 比较好

Comment on lines 40 to 51
#if __has_include(<memory_resource>)
ac::Lazy<int> foo(
std::allocator_arg_t /*unused*/,
std::pmr::polymorphic_allocator<> /*coroutine state allocator*/,
int i = 0) {
std::cout << "run with async_simple::coro::pmr::memory_resource" << '\n';
int test{};
test = co_await foo();
std::cout << "a pointer on coroutine frame: " << &test << '\n';
co_return test + i;
}
#endif
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

把这些复制一份改一下放到测试里吧,demo example 很多时候不跑的

@LXYan2333
Copy link
Contributor Author

LXYan2333 commented Oct 28, 2024

写到一半发现一个问题,

(size + sizeof(al) + Align - 1) / sizeof(Aligned_block);

这里是不是少加了一个sizeof(DeallocFn)

Edit:

没有问题了,

size += sizeof(DeallocFn);

@LXYan2333 LXYan2333 requested a review from ChuanqiXu9 October 28, 2024 05:58
Comment on lines 354 to 356
if (ptr == nullptr) {
return;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我们为什么会 delete nullptr?遇到这种情况就是出问题了吧

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://en.cppreference.com/w/cpp/memory/new/operator_delete

In all cases, if ptr is a null pointer, the standard library deallocation functions do nothing. If the pointer passed to the standard library deallocation function was not obtained from the corresponding standard library allocation function, the behavior is undefined.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

emm如果觉得没必要模仿标准库行为的话删掉就是了。

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我之前到没听说过,那就先这样吧

@LXYan2333 LXYan2333 requested a review from ChuanqiXu9 October 28, 2024 07:15
@ChuanqiXu9
Copy link
Collaborator

LGTM. Thanks.

@ChuanqiXu9 ChuanqiXu9 merged commit 866cc3a into alibaba:main Oct 28, 2024
14 checks passed
@microcai
Copy link

microcai commented Nov 4, 2024

看起来是使用了多态的分配器。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[feature request]使用自定义的LazyPromise的operator new以支持开发者自己提供的allocator
4 participants