c ++: чрезмерное копирование больших объектов - PullRequest
1 голос
/ 10 мая 2011

Хотя в SO уже есть довольно много вопросов о конструкторах копирования / операторах присваивания, я не нашел ответа, подходящего для моей проблемы.

У меня есть класс, подобный

class Foo
{
   // ...
private:
   std::vector<int> vec1;
   std::vector<int> vec2;
   boost::bimap<unsigned int, unsigned int> bimap;
   // And a couple more
};

Теперь кажется, что происходит некоторое чрезмерное копирование (на основе данных профиля) .. Поэтому мой вопрос заключается в том, как лучше всего решить эту проблему?

Должен ли я реализовать пользовательский оператор конструктора / назначения копирования и использовать swap?Или я должен определить свой собственный метод обмена и использовать его (при необходимости) вместо присваивания?

Поскольку я не эксперт по c ++, примеры, показывающие, как правильно справиться с этой ситуацией, очень ценятся.

ОБНОВЛЕНИЕ: Похоже, я не совсем ясно ..Позвольте мне попытаться объяснить.Программа в основном представляет собой программу поиска в ширину «на лету», и для каждого предпринятого шага мне нужно сохранять метаданные об этом шаге (это класс Foo). Теперь проблема в том, что есть (обычно)экспоненциально, так что вы можете себе представить, что нужно сохранить большое количество этих объектов. Я всегда передаю (const) ссылку, насколько мне известно. Каждый раз, когда я вычисляю преемника из узла в графе, мне нужносоздайте и сохраните ОДИН объект Foo (однако, некоторые члены данных будут добавлены к этому следующему элементу в дальнейшем при обработке этого преемника) ..

Данные моего профиля показывают примерно что-то вроде этого (я нена этой машине нет фактических чисел):

SearchStrategy::Search    13s
FooStore::Save            10s

Таким образом, вы можете видеть, что я трачу почти столько же времени на сохранение этих метаданных, сколько и на поиск по графику ... О, и FooStore сохраняет Fooв google::sparse_hash_map<long long, Foo, boost::hash<long long> >.

Компилятор g ++ 4.4 или g ++ 4.5 (я не на своем компьютере разработчика, поэтому я не могу проверить в данный момент) ..

ОБНОВЛЕНИЕ 2 назначаю такя из членов после создания экземпляра Foo вроде

void SetVec1(const std::vector<int>& vec1) { this->vec1 = vec1; };

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

IПрошу прощения, если мне не совсем понятно, какой семантики я пытаюсь достичь, но причина в том, что я не совсем уверен.

С уважением,

Мортен

Ответы [ 6 ]

3 голосов
/ 10 мая 2011

Все зависит от того, что означает копирование этого объекта в вашем случае:

  1. это означает копирование всего его значения
  2. это означает, что скопированный объект будет ссылаться на тот же контент

Если это 1, то этот класс кажется правильным.Вы не очень понимаете, какие операции, по вашим словам, делают много копий, поэтому я предполагаю, что вы пытаетесь скопировать весь объект.

Если это 2, то вам нужно использовать что-то вроде shared_ptr для совместного использованияконтейнеры между объектами.Простое использование shared_ptr вместо реальных объектов в качестве члена неявно позволит ссылаться на буферы обоими объектами (копией и копией).Это более простой способ (использование boost :: shared_ptr или std :: shared_ptr, если у вас есть компилятор с поддержкой C ++ 0x, предоставляющий его).

Есть более сложные способы, но они наверняка станут проблемой позже.

2 голосов
/ 10 мая 2011
  1. Конечно, и все говорят это, не оптимизируйте преждевременно. Не беспокойтесь об этом до тех пор, пока вы не докажете, а) что ваша программа работает слишком медленно, и б) она будет работать быстрее, если вы не скопируете так много данных.

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

  3. Если вашей программе не требуется несколько одновременных копий этих данных, то у вас есть несколько приемов для уменьшения количества копий, которые вы выполняете.

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

class Foo {
private:
  static int numberOfConstructors;
  static int numberofCopyConstructors;
  static int numberofAssignments;
  Foo() { ++numberOfConstructors; ...; }
  Foo(const Foo& f) : vec1(f.vec1), vec2(f.vec2), bimap(f.bimap) {
    ++numberOfCopyConstructors;
    ...;
  }
  Foo& operator=(const Foo& f) {
    ++numberOfAssignments;
    ...;
  }
};

Запустите вашу программу с вашими улучшениями и без них. Распечатайте значение этих статических членов, чтобы увидеть, повлияли ли ваши изменения.

Избегайте присваиваний в вызовах функций, используя ссылки Если вы передаете объекты типа Foo функциям, подумайте, можете ли вы сделать это по ссылке. Если вы не измените переданную копию, передача ее по константной ссылке не составит труда.

// WAS:
extern SomeFuncton(Foo f);
// EASY change -- if this compiles, you know that it is correct
extern SomeFunction(const Foo& f);
// HARD change -- you have to examine your code to see if this is safe
extern SomeFunction(Foo& f);

Избегайте копий с помощью Foo :: swap Если вы часто используете методы копирования (явные или неявные), подумайте, может ли назначенный элемент отказаться от своих данных, а не копировать их.

// Was:
vectorOfFoo.push_back(myFoo);
// maybe faster:
vectorOfFoo.push_back(Foo());
vectorOfFoo.back().swap(myFoo);

// Was:
newFoo = oldFoo;
// maybe faster
newfoo.swap(oldFoo);

Конечно, это работает, только если myFoo и oldFoo больше не нуждаются в доступе к своим данным. И вы должны реализовать Foo::swap

void Foo::swap(Foo& old) {
    std::swap(this->vec1, old.vec1);
    std::swap(this->vec2, old.vec2);
    ...
}

Что бы вы ни делали, измерьте свою программу до и после внесения изменений. Измерьте количество вызовов методов копирования и общее улучшение времени в вашей программе.

1 голос
/ 10 мая 2011

Копирование огромных векторов вряд ли может быть дешевым. Самый многообещающий способ - копировать реже. Хотя в C ++ довольно легко (может быть слишком просто) вызывать копию без намерения, есть способы избежать ненужного копирования:

  • передача по константной и неконстантной ссылке
  • Move-конструкторы
  • умных указателей с передачей права собственности

Эти методы могут оставлять только копии, требуемые алгоритмом.

Иногда возможно избежать даже некоторых из этих копий. Например, если вам нужны два объекта, у которых второй является обращенной копией первого, может быть создан объект-обертка, который действует как перевернутый, но вместо сохранения всей копии имеет только ссылку.

1 голос
/ 10 мая 2011

Если это действительно проблема, вы можете рассмотреть вариант реализации pimpl idiom .Но я сомневаюсь, что это проблема, хотя я должен был бы убедиться, что вы используете класс, чтобы быть уверенным.

1 голос
/ 10 мая 2011

Ваш класс не так уж плох, но вы не показываете, как вы его используете.

Если копирование много, вам нужно передать объекты этого класса по ссылке (или, если возможно, по ссылке). Если этот класс нужно скопировать, то вы ничего не можете сделать.

0 голосов
/ 10 мая 2011

Очевидный способ сократить копирование - использовать что-то вроде shared_ptr. Однако при многопоточности это лечение может быть хуже, чем болезнь - увеличение и уменьшение количества отсчетов необходимо выполнять атомарно, что может быть довольно дорогим. Однако, если вы, как правило, в конечном итоге модифицируете копии и вам нужно, чтобы каждая копия действовала уникально (т. Е. Изменение копии не влияет на оригинал), вы все равно можете получить худшую производительность, заплатив атомный прирост / убыль за подсчет ссылок и все равно делаю много копий.

Есть несколько очевидных способов избежать этого. Одним из них является перемещение уникальных объектов вместо копирования вообще - это здорово, если вы можете заставить его работать. Другой способ - использовать неатомарный подсчет ссылок большую часть времени и делать глубокие копии только при перемещении данных между потоками.

Нет единственного универсального и действительно чистого ответа.

...