尝试以下关于Pablo Halpern的多态内存资源的文章Section 5.9.2 Class monotonic_buffer_resource中的示例:
文档编号:N3816
日期:2013-10-13
作者:Pablo Halpern
phalpern@halpernwightsoftware.com
多态内存资源-r1
(最初为N3525 –多态分配器)
该文章声称:
monotonic_buffer_resource类设计用于非常快速的内存分配 在使用内存建立一些对象然后将其全部释放的情况下 当这些对象超出范围时立即执行。
那:
monotonic_buffer_resource的一个特别好的用途是为 容器或字符串类型的局部变量。例如下面的代码 连接两个字符串,在连接的字符串中查找单词“ hello”,然后 然后,在找到或找不到该单词之后,丢弃串联的字符串。的 串联字符串的长度不应超过80个字节,因此代码为 使用小的monotonic_buffer_resource [...]
针对这些短字符串进行了优化
我已经使用google benchmark library和boost.container 1.69's polymorphic resources对示例进行了基准测试,并使用以下代码在Ubuntu 18.04 LTS hyper-v虚拟机上使用g ++-8对其进行了编译和链接以发布二进制文件:>
// overload using pmr::string
static bool find_hello(const boost::container::pmr::string& s1, const boost::container::pmr::string& s2)
{
using namespace boost::container;
char buffer[80];
pmr::monotonic_buffer_resource m(buffer, 80);
pmr::string s(&m);
s.reserve(s1.length() + s2.length());
s += s1;
s += s2;
return s.find("hello") != pmr::string::npos;
}
// overload using std::string
static bool find_hello(const std::string& s1, const std::string& s2)
{
std::string s{};
s.reserve(s1.length() + s2.length());
s += s1;
s += s2;
return s.find("hello") != std::string::npos;
}
static void allocator_local_string(::benchmark::State& state)
{
CLEAR_CACHE(2 << 12);
using namespace boost::container;
pmr::string s1(35, 'c'), s2(37, 'd');
for (auto _ : state)
{
::benchmark::DoNotOptimize(find_hello(s1, s2));
}
}
// pmr::string with monotonic buffer resource benchmark registration
BENCHMARK(allocator_local_string)->Repetitions(5);
static void allocator_global_string(::benchmark::State& state)
{
CLEAR_CACHE(2 << 12);
std::string s1(35, 'c'), s2(37, 'd');
for (auto _ : state)
{
::benchmark::DoNotOptimize(find_hello(s1, s2));
}
}
// std::string using std::allocator and global allocator benchmark registration
BENCHMARK(allocator_global_string)->Repetitions(5);
与std :: string相比,pmr :: string基准测试这么慢吗?
我假设std :: string的std :: allocator在保留调用上应该使用“ new”,然后在调用时构造每个字符:
s += s1;
s += s2
使用持有monotonic_buffer_resource的多态分配器将其与pmr :: string进行比较,保留内存应该归结为简单的指针算法,因为char缓冲区应该足够,所以不需要“ new”。随后,它将像std :: string那样构造每个字符。
因此,考虑到find_hello的pmr :: string版本和find_hello的std :: string版本之间唯一的不同操作是保留内存的调用,其中pmr :: string使用堆栈分配,std :: string使用堆分配:
答案 0 :(得分:1)
多种因素共同导致助推pmr::basic_string
变慢:
pmr::monotonic_buffer_resource
的建造需要一定的成本(此处为17纳秒)。pmr::basic_string::reserve
保留了多个需求。在这种情况下,它会保留96个字节,超过您拥有的80个字节。pmr::basic_string
中的预留空间并不是免费的,即使缓冲区足够大(此处超出8纳秒)。pmr::basic_string::find
的实现不理想。这是速度较慢的实际成本。在GCC中,std::basic_string::find
使用__builtin_memchr
查找可能匹配的第一个字符,而boost在一个大循环中完成了所有工作。显然,这是主要成本,这是使Boost运行比std慢的原因。因此,在增加缓冲区并将boost::container::string
与boost::container::pmr::string
进行比较之后,pmr版本的运行速度会稍慢一些(293 ns vs.
276 ns)。这是因为new
和delete
对于此类微基准实际上非常快,并且比pmr的复杂机制(构建时仅17 ns)要快。实际上,默认的Linux / gcc new / delete一次又一次地重复使用相同的指针。此优化具有非常简单和快速的实现,也可以与CPU缓存配合使用。
作为证明,请尝试一下(无优化):
for (int i=0 ; i < 10 ; ++i)
{
char * ptr = new char[96];
std::cout << (void*) ptr << '\n';
delete[] ptr;
}
这将一次又一次打印相同的指针。
理论是,在真实的程序中,new / delete的表现不那么好,并且不能一次又一次地重复使用同一块,然后new / delete大大降低了执行速度,并且缓存位置变得相当较差的。在这种情况下,pmr + buffer是值得的。
结论:boost pmr字符串的实现比gcc的字符串慢。 pmr机制的成本比新的/删除的默认简单方案高。