boost :: multiprecision::cpp_int копируется, а затем удаляется каждый раз, когда я пытаюсь его распечатать

#c #boost #new-operator #boost-multiprecision

#c #повышение #new-operator #повышение-многопоточность

Вопрос:

Когда я печатаю cpp_int из Boost, кажется, что копируется весь объект целиком.

 #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
 

Сбивает с толку то, что перегрузка для печати есть ostreamamp; operator<<(ostreamamp;, const Tamp;) , но переход *u в такую функцию, как template <typename T> void cr(const Tamp;) {} , не показывает никакого нового выделения памяти. Я тоже пробовал u->str() , но это также приводит к выделению 2-й памяти.

Я также попытался перегрузить cout для cpp_int :

 std::ostreamamp; operator <<(std::ostreamamp; stream, const boost::multiprecision::cpp_intamp; mpi) {
    return stream << mpi.str();
}
 

но результат был тот же самый. Тем не менее, я также удивлен, что это скомпилировано, поскольку я ожидал, что там уже будет перегрузка. Я предполагаю, что мне, возможно, потребуется изменить что-то более базовое.

Как я могу избежать этого? Я не хочу копировать, а затем удалять более 30 байт каждый раз, когда я хочу распечатать cpp_int .

Если нет, то о переключении типа данных не может быть и речи, если интерфейс аналогичен для минимального рефакторинга.

Комментарии:

1. Почему вы используете new здесь оператора?

2. Это всего лишь минимальный пример (но я использую new его в реальной, неминимальной программе). Здесь это не служит никакой реальной цели, кроме как показать, что new перегрузка действительно работает. Однако, если это делается в стеке, это все та же проблема.

3. Примечание: соответствие free отсутствует, потому что оно не казалось актуальным, поскольку я просто пытался показать выделенную нежелательную память.

Ответ №1:

Способ, которым вы не соответствовали 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::ostreamamp; 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::ostreamamp; operator<<(std::ostreamamp; os, const number<Backend, ExpressionTemplates>amp; 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() amp; 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 сможет похвастаться высоко оптимизированной реализацией операций ввода-вывода.

Профилирование памяти

Чтобы точно увидеть, какие распределения выполняются где, я запустил отладочную сборку через 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::ostreamamp; 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 не копируется, что имеет смысл.

Комментарии:

1. Выполнил некоторое профилирование кучи, чтобы попытаться доказать, что число не копируется. Фактические распределения ниже порога отчетности, но мы можем сделать вывод, что число НЕ копируется.

2. Как избежать выделения? Вы не можете, потому что интерфейс сосредоточен вокруг возврата a std::string . настройка распределителя не предусмотрена. Извините. Вы можете выбрать некоторую двоичную сериализацию на основе конечностей вручную.