比较以下使用 C++ 多态内存源的项目的编译草案。为了看看发生了什么,我覆盖了std::pmr::monotonic_buffer_resource
与我的 LoggingResource:
LiveDemo https://godbolt.org/#z:OYLghAFBqd5QCxAYwPYBMCmBRdBLAF1QCcAaPECAMzwBtMA7AQwFtMQByARg9KtQYEAysib0QXACx8BBAKoBnTAAUAHpwAMvAFYTStJg1DIApACYAQuYukl9ZATwDKjdAGFUtAK4sGIAGwapK4AMngMmAByPgBGmMQgABykAA6oCoRODB7evgFBaRmOAmER0SxxCcl2mA5ZQgRMxAQ5Pn6Btpj2xQwNTQSlUbHxSbaNza15HQrjA%2BFDFSOJAJS2qF7EyOwc5gDM4cjeWADUJrtuAG61RMRn2CYaAIJ7B0eYp%2BdsLCQAngD6xEwCnWm0wdwezzM%2BwYhy8JzObiaxCYP3BTxeMLeHzcyBm%2BFQaOeTxmxC8DmOKRYxD%2BTAA7k1MH80IImPNbuiAOxWJ7HXnHLwZIzHMS0VCiG5/Ag/FLvM4AEWOeJAIEpCRVnh%2B32IKQQeGQNNoovFJARSpAMR%2BBDBu3uu25RMefOOAHoAFTHBwkBTHV3OiEQp3O53HLBUJheWgEAN81U0%2BmApmyVkRYgQZbHEAUqlxhmJllskxckVipgSqUywtWDlywsKyu1l3BrD0YCl95EYVMD0CElkm7%2Bnl8zCqFK0PWELPUum55mNNkQZkzYWGktl6XvYvIZbRp28zMkv4QcxmAASXVFx1FQpJ4WAYHvD%2BPpGXRvT9cHu4pxHCBCoR7MZihuGkbdgwvaesQHoipg6D3iYACsbgMMe252jupzVgOjp8kG3YpKiH6NpOOYJrOybxAuPYEMR06kUmBZmP4b67AqQERlGaHooRsa0YyZHzou1E8fGfH0Sm5j%2BMcqAEAg8TPpupYkJK64vmKHx1lytZvoRTr7gQ1IQNJsnEAAdAe8krluGH2p%2BvIpN%2Bgh/seHj4aB4E3FBhowXBiHIQBqE2XytZYYGwbfFc6HCTOYkUVFdH5uJjESVJMnxOmDCoMOWwpFGOl8np1KVma4WYIZqWmQeqwpcZJnAJgBAGkaimpm%2BmGEe%2B2GfvZP5OQBkSZao2XUSVbn6X2JCefQsFgAhSEoWcgW8sFXGdTRIl5nOKYQHFokJfEEnJUZcmjapxrUuWYKMad255Xuir6YexWoFcZU1ZVFmvuhHW2V%2BPX/mYQgysgeA0MgxwjYJpIQZN3kzb582caty0OqFwoKBkwARGwgghTG2a8Rt5G3FdqAysi/YsZRYFCfj638YlknEAgCjMaxmBhuxC2RbT0V7cTkmk/EzXyttPPxZt%2B1JVdTMs%2BpIbs8BHH2uhAB%2BO2E/OrPyxzkZcytToKWuMrHHVDWGyQaYnd9tmAgQGwMPd1K1fVjWrhbAVfW1KN8maqrKje14PXLx5niul4CMAju3g%2Bj4AXrzyYYjWGHEw6PHCEqDAMAt4AEpAiCWwZhSXgxGOYO%2B1SypfL8AL5xsWz%2BppTwpCXZcgOhGdZ7ndeghAFdqtXxD/ICwL1%2B8rpeAwWDELQPy3rXo%2BgummaT9Ps/zyPBeMpWq/xOvRgL1vDaVhhNYrd1Fxtu3hEXKgeDoD66CoK74qlRkABejLURaVoKM%2BH9f2XHgTG6ZnrxG/PCJuq0nTdUcv9R4llSy3lOGYeCD8f5Ah8nNACz4MEs3jj9W29t%2BRTz3nPA%2Bm8x5/AALR3ENqVPBFlgEMA9u1L26Fb73xDM/LA9CICcPQK6FI/88Cf0lMcRhipRGALEMw1qi1dy7xnuQ4Ah8qG0JtLwxBVptq4MtECJhICCFBXYYRGIqBPDcL%2BHgBQfxMAAEcvBiD7gQdAyo/YgEHsPHuhdBKHXKumQSxwMpZUwDlKSVxiAQNlFAwh9ViFKP3qoyhoIaF3BsXYxxzijrEFYUjUxq1%2B5V0wFqbxi8tjukSSotRqT47BSTk8H84Nkxpkbgos0SIUSmlccqDBz4zAaDMJIO4EivBUGMbyIpniBDSQEHqP4MQxlUHiDUwuFQFCVkWVQEy6BSxMDTLgsZZlpGtMTsrQinds5GDzuU94opkk%2BLBJpRi6z6nnMKT0lUlcQBXAggidW9N9o2mOBcE%2BJ8AUxVuFyFB/h7mrKeXKZ8EK%2BZgsYnClJDdqyn2fBJdFjy3mNzlBwVYtBODwV4H4DgWhSCoE4G4aw1hFRbxQbsHgpACCaGJasAA1iAeCkgTL%2BDMFIDQABOUVwr/Aci4IM/QnBJAUs5TSzgvAFAgCCByqlxLSBwFgDARAKBUAsBSHQOSFBKLGtNQkYAoquB8DoFaYgaqIAxCVTEcITQficDZWgFg2MCAAHkGCzyVVgFghhgDiC1aQfAgI6hXDVdG4ctQvBWiVT%2BLoSqxwxGREPDwWAlX6TwCwb12qqAGGAAoAAangTAtIA0ykpWy/gggRBiHYFIGQghFAqHUNG3QdqDBGBQAyyw%2Bg8AxDVZAVYpMeiJuodQvE8pTCWGsFwDkxxqEBrMKqro1wsguCnpMPwiR4LBCnoMcolQQBmA5KK1I6RMgCGPXoQoT6GCXuGAkRi0gah1AEH0CYng2hJDPX%2BnogG5hlC/Teu9Yx%2BgvrtTMfon7Fjfv8JIVYty9D6UwNsHgJKyWKujbSjgqhEj%2BGoRhk2yAwaipMlwY4EB6WrrHccXAhAJp7DtccDwlr6CQW48sXgmqtDLB5XyoIpKOAKtICWyQuwTK7HghyXY/hlOSFvbsUVkgz2UupaR1V6r2WctWLqg16wCAtwIOQSgvqTUCciKwbY5HKPUZTkKCAY0GDcuE8EfANx756BbcIUQ4hO0hZ7WoJVA7SC0mRCkUthGODktIPp3gpGA2pus1JKgxxXNUckDRujDGmN8Yc/EFlXA/Oia5aQWSTBp6UGS7Jkt8F/CCs0%2BpjDKnGIaHgnppVhnbDGdq%2BJ0gvLdNKY5Ke0VGgNAzd2GYRIorkjSd2MRgzKqTNavG9JndaWhvbbG6sSJGRnCSCAA%3D
#include <vector>
#include <memory_resource>
#include <array>
#include <cstdio>
struct pmr_aware_container
{
using allocator_type = std::pmr::polymorphic_allocator<std::byte>;
/* ctors */
// default
pmr_aware_container() : pmr_aware_container{allocator_type{}} {} // delegate to aa constructor
explicit pmr_aware_container(const allocator_type alloc)
: str_("Hello long string!!!", alloc) {
printf("default constructor called!\n");
}
// copy
// pmr_aware_container(const pmr_aware_container&) = default;
pmr_aware_container(const pmr_aware_container& other, allocator_type alloc = {})
: str_(other.str_, alloc) {
printf("Copy constructor called!\n");
}
// move
pmr_aware_container(pmr_aware_container&& other) noexcept
: str_{std::move(other.str_), other.get_allocator() }
{
printf("Noexcept move constructor called!\n");
}
pmr_aware_container(pmr_aware_container&& other, const allocator_type& alloc)
: str_(std::move(other.str_), alloc)
{
printf("Specific move constructor called!\n");
}
// assignement
pmr_aware_container& operator=(const pmr_aware_container& rhs) = default;
pmr_aware_container& operator=(pmr_aware_container&& rhs) = default;
~pmr_aware_container() = default;
allocator_type get_allocator() const {
return str_.get_allocator();
}
std::pmr::string str_ = "Hello long string!!!";
};
class LoggingResource : public std::pmr::memory_resource
{
public:
LoggingResource(std::pmr::memory_resource *underlying_resource) : underlying_resource_{underlying_resource} { }
private:
void *do_allocate(size_t bytes, size_t align) override {
printf("Allocating %d bytes!\n", bytes);
return underlying_resource_->allocate(bytes, align);
}
void do_deallocate(void*p, size_t bytes, size_t align) {
underlying_resource_->deallocate(p, bytes, align);
}
bool do_is_equal(std::pmr::memory_resource const& other) const noexcept override {
return underlying_resource_->is_equal(other);
}
std::pmr::memory_resource* underlying_resource_;
};
int main()
{
std::array<std::byte, 2024> buf;
std::pmr::monotonic_buffer_resource mbs{buf.data(), buf.size()};
LoggingResource log_resource{&mbs};
std::pmr::vector<pmr_aware_container> v{ { pmr_aware_container{ &log_resource}, pmr_aware_container{ &log_resource} }, &log_resource};
}
检查二进制文件后,我非常惊讶地发现,即使使用最新的编译器 gcc 12.1,我的 LoggingResource 所做的虚拟调用实际上也没有被虚拟化。我认为这种情况会发生,因为 pmr 的速度必须像广告中宣传的那样快。这是程序集中各自的 vtable:
vtable for LoggingResource:
.quad 0
.quad typeinfo for LoggingResource
.quad LoggingResource::~LoggingResource() [complete object destructor]
.quad LoggingResource::~LoggingResource() [deleting destructor]
.quad LoggingResource::do_allocate(unsigned long, unsigned long)
.quad LoggingResource::do_deallocate(void*, unsigned long, unsigned long)
.quad LoggingResource::do_is_equal(std::pmr::memory_resource const&) const
我可以看到从 gcc 9.1 到 12.1 有了改进,当时甚至连单调缓冲区资源都没有去虚拟化。 gcc 9.1 的输出:
vtable for std::pmr::monotonic_buffer_resource:
.quad 0
.quad typeinfo for std::pmr::monotonic_buffer_resource
.quad std::pmr::monotonic_buffer_resource::~monotonic_buffer_resource() [complete object destructor]
.quad std::pmr::monotonic_buffer_resource::~monotonic_buffer_resource() [deleting destructor]
.quad std::pmr::monotonic_buffer_resource::do_allocate(unsigned long, unsigned long)
.quad std::pmr::monotonic_buffer_resource::do_deallocate(void*, unsigned long, unsigned long)
.quad std::pmr::monotonic_buffer_resource::do_is_equal(std::pmr::memory_resource const&) const
vtable for LoggingResource:
.quad 0
.quad typeinfo for LoggingResource
.quad LoggingResource::~LoggingResource() [complete object destructor]
.quad LoggingResource::~LoggingResource() [deleting destructor]
.quad LoggingResource::do_allocate(unsigned long, unsigned long)
.quad LoggingResource::do_deallocate(void*, unsigned long, unsigned long)
.quad LoggingResource::do_is_equal(std::pmr::memory_resource const&) const
如果我尝试嵌套不同的内存分配器(例如单调缓冲区资源上的池分配器等),这可能证明性能至关重要。但是,让我好奇的是std::monotonic_buffer_resource
did实际上去虚拟化。这怎么可能?我怎样才能为我的分配器实现同样的目标?