1

当我cpp_int从 Boost 打印 a 时,似乎整个对象都被复制了。

#include <iostream>
#include <boost/multiprecision/cpp_int.hpp>
using std::cout;

void* operator new(size_t size) {
    void *memory = malloc(size);
    cout << "New: " << memory << " " << size << "\n";
    return memory;
}

int main() {
    auto u = new boost::multiprecision::cpp_int("987654321");
    cout << "------\n";
    cout << *u << "\n";
}
New: 0x23d4e70 32
------
New: 0x23d52b0 31
987654321

令人困惑的是,打印的重载是ostream& operator<<(ostream&, const T&),但传递*u给诸如template <typename T> void cr(const T&) {}不显示任何新内存分配的函数。我也尝试过u->str(),但这也会导致第二次内存分配。

我还尝试为以下内容重载 cout cpp_int

std::ostream& operator <<(std::ostream& stream, const boost::multiprecision::cpp_int& mpi) {
    return stream << mpi.str();
}

但结果是一样的。然而,我也很惊讶这个编译,因为我预计已经有一个重载。我的假设是我可能需要修改更多后端的东西。

我怎样才能避免这种情况?我不想每次打印cpp_int.

如果没有,切换数据类型也不是不可能的,只要接口相似以进行最少的重构即可。

4

1 回答 1

4

您不匹配 malloc/new 的方式是调用 UB(因为 ubsan+asan 很容易告诉您)。

==32752==ERROR: AddressSanitizer: alloc-dealloc-mismatch (malloc vs operator delete
    #0 0x7fb58c15c407 in operator delete(void*, unsigned long) (/usr/lib/x86_64-lin
    #1 0x564b19759014 in __gnu_cxx::new_allocator<char>::deallocate(char*, unsigned
    #2 0x564b1974b8cb in std::allocator<char>::deallocate(char*, unsigned long) /us
    #3 0x564b1974b8cb in std::allocator_traits<std::allocator<char> >::deallocate(s
    #4 0x564b197478f4 in std::__cxx11::basic_string<char, std::char_traits<char>, s
    #5 0x564b19744f74 in std::__cxx11::basic_string<char, std::char_traits<char>, s
    #6 0x564b19741053 in std::__cxx11::basic_string<char, std::char_traits<char>, s
    #7 0x564b19744993 in std::ostream& boost::multiprecision::operator<< <boost::mu
    #8 0x564b1973da5a in main /home/sehe/Projects/stackoverflow/test.cpp:14
    #9 0x7fb58ab85bf6 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21bf6
    #10 0x564b1973d669 in _start (/home/sehe/Projects/stackoverflow/sotest+0x4a669)

因此,让我们专注于索赔:

#include <boost/multiprecision/cpp_int.hpp>
#include <iostream>

using Int = boost::multiprecision::cpp_int;

int main() {
    Int u("987654321");
    std::cout << u << "\n";
}

当我们要求 clang 遵循重载时,operator<<它会将我们带到这里:

template <class Backend, expression_template_option ExpressionTemplates>
inline std::ostream& operator<<(std::ostream& os, const number<Backend, ExpressionTemplates>& r)
{
   std::streamsize d  = os.precision();
   std::string     s  = r.str(d, os.flags());
   std::streamsize ss = os.width();
   if (ss > static_cast<std::streamsize>(s.size()))
   {
      char fill = os.fill();
      if ((os.flags() & std::ios_base::left) == std::ios_base::left)
         s.append(static_cast<std::string::size_type>(ss - s.size()), fill);
      else
         s.insert(static_cast<std::string::size_type>(0), static_cast<std::string::size_type>(ss - s.size()), fill);
   }
   return os << s;
}

如您所见,该数字由 const-reference 获取,因此不执行复制。将分配缓冲区(在str()实现中)。我认为 Multiprecision 库不会吹嘘高度优化的 IO 操作实现。

内存分析

为了准确查看在哪里完成了哪些分配,我通过 Massif 运行了一个调试版本:

在此处输入图像描述

在高峰期,最高分配是:

99.97% (73,759B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc.
->98.54% (72,704B) 0x50DDFA4: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28
| ->98.54% (72,704B) 0x40108F1: _dl_init (dl-init.c:72)
|   ->98.54% (72,704B) 0x40010C8: ??? (in /lib/x86_64-linux-gnu/ld-2.27.so)
|     
->01.39% (1,024B) 0x58C526A: _IO_file_doallocate (filedoalloc.c:101)
| ->01.39% (1,024B) 0x58D5447: _IO_doallocbuf (genops.c:365)
|   ->01.39% (1,024B) 0x58D4566: _IO_file_overflow@@GLIBC_2.2.5 (fileops.c:759)
|     ->01.39% (1,024B) 0x58D2ABB: _IO_file_xsputn@@GLIBC_2.2.5 (fileops.c:1266)
|       ->01.39% (1,024B) 0x58C6A55: fwrite (iofwrite.c:39)
|         ->01.39% (1,024B) 0x516675A: std::basic_ostream<char, std::char_traits<ch
|           ->01.39% (1,024B) 0x10C85C: std::ostream& boost::multiprecision::operat
|             ->01.39% (1,024B) 0x10B6C6: main (test.cpp:8)
|               
->00.04% (31B) in 1 place, below massif's threshold (1.00%)

可悲的是,不知何故,我无法使阈值 < 1%(这可能是记录在案的限制)。

我们可以看到,尽管 31B 分配发生在某个地方,但与文件输出缓冲区 (1024B) 相比,它相形见绌。

如果我们将输出语句替换为

return u.str().length();

您仍然可以看到 31B 分配,它与 cpp_int 类型的大小不匹配。事实上,如果我们要复制那个:

return std::make_unique<Int>(u)? 0 : 1;

然后我们看到的是 32B 分配:

->00.04% (32B) in 1 place, below massif's threshold (1.00%)

很明显,cpp_int 没有被复制,这是有道理的。

于 2020-12-02T01:11:06.607 回答