Вложенный вектор STL использует слишком много памяти - PullRequest
3 голосов
/ 21 августа 2010

У меня есть вектор STL My_Partition_Vector из Partition объектов, определенных как

struct Partition // the event log data structure
{
    int key;
    std::vector<std::vector<char> > partitions;
    float modularity;
};

Фактическая вложенная структура Partition.partitions варьируется от объекта к объекту, но общее количество символов, хранящихся в Partition.partitions, всегда равно 16.

Поэтому я предположил, что общий размер объекта должен быть более или менее 24 байта (16 + 4 + 4). Однако на каждые 100 000 элементов, которые я добавляю к My_Partition_Vector, потребление памяти (определяется с помощью ps -aux) увеличивается примерно на 20 МБ, что указывает примерно на 209 байт для каждого объекта раздела.

Это почти 9-кратное увеличение !? Откуда все это дополнительное использование памяти? Какое-то дополнение в векторе STL или структура? Как я могу решить это (и остановить его, достигнув в своп)?

Ответы [ 6 ]

3 голосов
/ 21 августа 2010

С одной стороны, std::vector моделирует динамический массив, так что если вы знаете, что у вас всегда будет 16 символов в partitions, то с помощью std::vector это излишне.Используйте старый добрый массив / матрицу в стиле C, boost :: array или boost :: multi_array .

Для уменьшения количества перераспределений, необходимых для вставки / добавления элементов, из-за ограничений макета памяти std::vector разрешено предварительно выделять память для определенного количества элементов заранее (и функция-член capacity()скажу сколько).

2 голосов
/ 22 августа 2010

Хотя я думаю, что он, возможно, немного преувеличивает ситуацию, я в целом согласен с выводом DeadMG о том, что то, что вы делаете, вызывает проблемы.

Хотя я, как правило, выгляжуat (что бы ни сделал кто-нибудь) и говоря «не делай этого, просто используй вектор», этот случай вполне может быть исключением.Вы создаете огромное количество объектов, которые должны быть крошечными.К сожалению, вектор обычно выглядит примерно так:

template <class T>
class vector { 
    T *data;
    size_t allocated;
    size_t valid;
public:
    // ...
};

На типичной 32-битной машине это уже двенадцать байтов.Поскольку вы используете vector<vector<char> >, у вас будет 12 байтов для внешнего вектора, плюс еще двенадцать для каждого вектора, который он содержит.Затем, когда вы на самом деле сохраняете какие-либо данные в своих векторах, каждому из них необходимо выделить блок памяти из свободного хранилища.В зависимости от того, как реализован ваш бесплатный магазин, у вас обычно будет минимальный размер блока - часто 32 или даже 64 байта.Хуже того, куча, как правило, имеет свои собственные издержки, поэтому она добавляет больше памяти в каждый блок для своего собственного учета (например, она может использовать связанный список блоков, добавляя другойстоимость указателя данных для каждого распределения).

Только для осмыслений, давайте предположим, что вы усредняете четыре вектора по четыре байта за штуку, и что ваш менеджер кучи имеет минимальный размер блока 32 байта и один дополнительный указатель (или int) для его учета (давая реальный минимум 36 байтов на блок).Умножая это, я получаю 204 байта за штуку - достаточно близко к вашим 209, чтобы верить, что это достаточно близко к тому, с чем вы имеете дело.

В этот момент вопрос состоит в том, как решить проблему.Одна из возможностей - попытаться работать за кулисами.Все контейнеры в стандартной библиотеке используют распределители, чтобы получить их память.Хотя они по умолчанию распределитель получает память непосредственно из бесплатного хранилища, вы можете заменить другой, если вы выберете.Если вы немного осмотритесь, то сможете найти любое количество альтернативных распределителей, многие / большинство из которых должны помочь именно в той ситуации, в которой вы находитесь - уменьшая потерянную память при выделении большого количества мелких объектов.Пара, на которую стоит обратить внимание, - это Boost Pool Allocator и Loki маленький объектный распределитель.

Другая возможность (которая может быть объединена с первой) - это вообще отказаться от использования vector<vector<char> > и заменить егос чем-то вроде:

char partitions[16];
struct parts { 
    int part0 : 4;
    int part1 : 4;
    int part2 : 4;
    int part3 : 4;
    int part4 : 4;
    int part5 : 4;
    int part6 : 4
    int part7 : 4;
};

На данный момент я предполагаю, что максимум 8 разделов - если это может быть 16, вы можете добавить больше к parts.Это, вероятно, должно немного уменьшить использование памяти, но (как есть) повлияет на ваш другой код.Вы также можете заключить это в небольшой собственный класс, который обеспечивает адресацию в стиле 2D, чтобы минимизировать влияние на остальную часть вашего кода.

1 голос
/ 22 августа 2010

... Это немного побочный разговор, но boost :: multi_array был предложен в качестве альтернативы использованию OP вложенных векторов.Я обнаружил, что multi_array использует аналогичный объем памяти при применении к рабочим параметрам OP.

Я получил этот код из примера на Boost.MultiArray .На моей машине это показало, что multi_array использует примерно в 10 раз больше памяти, чем в идеале, при условии, что 16 байтов расположены в простой прямоугольной геометрии.

Чтобы оценить использование памяти, я проверял монитор системы во время работы программыи я скомпилировал с

( export CXXFLAGS="-Wall -DNDEBUG -O3" ; make main && ./main )

Вот код ...

   #include <iostream>
   #include <vector>
   #include "boost/multi_array.hpp"
   #include <tr1/array>
   #include <cassert>

   #define USE_CUSTOM_ARRAY 0 // compare memory usage of my custom array vs. boost::multi_array

   using std::cerr;
   using std::vector;

  #ifdef USE_CUSTOM_ARRAY
   template< typename T, int YSIZE, int XSIZE >
   class array_2D
      {
      std::tr1::array<char,YSIZE*XSIZE> data;
   public:
      T & operator () ( int y, int x ) { return data[y*XSIZE+x]; } // preferred accessor (avoid pointers)
      T * operator [] ( int index ) { return &data[index*XSIZE]; } // alternative accessor (mimics boost::multi_array syntax)
      };
  #endif

int main ()
   {

   int COUNT = 1024*1024;

  #if USE_CUSTOM_ARRAY
   vector< array_2D<char,4,4> > A( COUNT );
   typedef int index;
  #else
   typedef boost::multi_array<char,2> array_type;
   typedef array_type::index index;
   vector<array_type> A( COUNT, array_type(boost::extents[4][4]) );
  #endif

  // Assign values to the elements
  int values = 0;
  for ( int n=0; n<COUNT; n++ )
     for(index i = 0; i != 4; ++i) 
       for(index j = 0; j != 4; ++j)
           A[n][i][j] = values++;

// Verify values
   int verify = 0;
    for ( int n=0; n<COUNT; n++ )
       for(index i = 0; i != 4; ++i) 
          for(index j = 0; j != 4; ++j)
             {
             assert( A[n][i][j] == (char)((verify++)&0xFF) );
            #if USE_CUSTOM_ARRAY
             assert( A[n][i][j] == A[n](i,j) ); // testing accessors
            #endif
             }

   cerr <<"spinning...\n";
   while ( 1 ) {} // wait here (so you can check memory usage in the system monitor)

   return 0;
   }
1 голос
/ 21 августа 2010

В моей системе sizeof (vector) равен 24. Это, вероятно, соответствует 3 8-байтовым элементам: емкость, размер и указатель.Кроме того, необходимо учитывать фактическое распределение, которое будет составлять от 1 до 16 байтов (плюс издержки выделения) для внутреннего вектора и от 24 до 384 байтов для внешнего вектора (sizeof (vector) * partitions.capacity ()).

Я написал программу, чтобы подвести итог ...

   for ( int Y=1; Y<=16; Y++ )
      {

      const int X = 16/Y;
      if ( X*Y != 16 ) continue; // ignore imperfect geometries

      Partition a;
      a.partitions = vector< vector<char> >( Y, vector<char>(X) );

      int sum = sizeof(a); // main structure
      sum += sizeof(vector<char>) * a.partitions.capacity(); // outer vector
      for ( int i=0; i<(int)a.partitions.size(); i++ )
         sum += sizeof(char) * a.partitions[i].capacity(); // inner vector

      cerr <<"X="<<X<<", Y="<<Y<<", size = "<<sum<<"\n";

      }

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

X=16, Y=1, size = 80
X=8, Y=2, size = 104
X=4, Y=4, size = 152
X=2, Y=8, size = 248
X=1, Y=16, size = 440

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

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

В заключение, std :: vector <> не очень экономит место дляделать целую кучу очень маленьких ассигнований.Если ваше приложение должно быть эффективным, то вам следует использовать другой контейнер.


Мой подход к решению этой проблемы, вероятно, будет заключаться в том, чтобы выделить 16 символов с

std::tr1::array<char,16>

иоберните это пользовательским классом, который отображает 2D-координаты на распределение массива.

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

   template< typename T, int YSIZE, int XSIZE >
   class array_2D
      {
      std::tr1::array<char,YSIZE*XSIZE> data;
   public:
      T & operator () ( int y, int x ) { return data[y*XSIZE+x]; } // preferred accessor (avoid pointers)
      T * operator [] ( int index ) { return &data[index*XSIZE]; } // alternative accessor (mimics boost::multi_array syntax)
      };
1 голос
/ 21 августа 2010

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

Наиболее вероятной причиной потребления памяти являются данные отладки.Реализации STL обычно хранят LOT отладочных данных.Никогда не профилируйте приложение с включенными флагами отладки.

0 голосов
/ 21 августа 2010

16 байт - это полная и полная потеря. Вы храните чертовски много данных об очень маленьких объектах. Вектор вектора - неправильное решение для использования. Вы должны записать sizeof (vector) - это не так уж важно, так как выполняет существенную функцию. В моем компиляторе sizeof (vector) равен 20. Таким образом, каждый раздел равен 4 + 4 + 16 + 20 + 20 * числу внутренних разделов + накладные расходы памяти, такие как векторы, не являющиеся идеальным размером.

Вы храните только 16 байтов данных и тратите впустую невероятные объемы памяти, выделяя их наиболее сегрегированными, наибольшими накладными расходами способами, которые вы только могли себе представить. Вектор не использует много памяти - у вас ужасный дизайн.

...