C ++: Как бороться с оперативной памятью, занятой неиспользуемыми пустыми векторами? - PullRequest
2 голосов
/ 21 мая 2019

У меня есть программное обеспечение на C ++, которое выполняет агентное моделирование.В симуляции есть Population s, которые содержат Patch es, которые содержат Individual s, которые содержат два Haplotype s.Каждый Haplotype содержит 12 векторов, каждый из которых служит для отслеживания различных типов генов.

class Haplotype
{
    std::vector<A> genesA;
    std::vector<B> genesB;
    std::vector<C> genesC;
    std::vector<D> genesD;
    ....

};

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

Есть ли хороший шаблон проектирования, который позволил бы мне решить эту проблему?Что-то, что могло бы добавить векторы в Haplotype только по требованию?

Ответы [ 3 ]

2 голосов
/ 21 мая 2019

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

Вектор может «тратить» некоторое пространство, которое не используется.Также - сам объект использует некоторую память, как вы сказали.

Однако, когда вы используете массивы, вы получаете полный контроль.с std::vector<Gene>[] genes; вы можете использовать именно ту память, которая вам нужна.Но теперь вы должны знать, какой индекс представляет какой тип (A, B, C ...).Эта информация снова будет стоить вам памяти ... Также вам придется скопировать и перераспределить массив на случай, если вы захотите добавить еще один.

Вы можете избежать использования выделенной памяти для каждого вектора с помощью указателей и инициализировать ихс nullptr.Затем вы размещаете их по требованию.Таким образом, вы будете только тратить пространство указателей.То, как вы сейчас это реализовали, вы будете инициализировать каждый векторный объект (и, возможно, некоторое зарезервированное пространство).

1 голос
/ 21 мая 2019

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

Например, вы создаете базовый класс для ваших типов генов:

class Gene
{
    // Make it pure virtual (abstract) for example
};

Затем вы создаете 12 различных типов генов, наследующих этот базовый класс:

class A : public Gene // Gene type number 1
{
    // ...
};

...

class L : public Gene // Gene type number 12
{

};

Таким образом, все ваши 12 типов генов Gene.

Затем в Haplotype вы можете хранить полезные гены следующим образом:

class Haplotype
{
    std::vector<std::vector<Gene*>> genes;
};

Таким образом, выхраните только те гены, которые полезны в векторе и ничего больше.Конечно, эта конструкция, использующая полиморфизм, подразумевает, что вы храните указатели вместо значений.

Вы можете добавить метод в Haplotype, который извлекает реальный тип его компонентов (dynamic_cast, который успешно выполняется), чтобы сделать его незаметным для"сторона пользователя".

Я не знаю, будет ли это решение хорошо для вас, но я надеюсь, что оно может помочь.


РЕДАКТИРОВАТЬ:

Если вы до C ++ 17 и поэтому не можете использовать std::variant, я думаю, что это может быть альтернативой.

0 голосов
/ 22 мая 2019

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

union Gene
{
    A a;
    B b;
    C c;
    D d;
};
class Haplotype
{
    std::vector<Gene> genes;
    int aEnd, bEnd, cEnd;
  public:
    A getA(int idx) {
        return genes[idx].a;
    }
    B getB(int idx) {
        return genes[idx + aEnd].b;
    }
    C getC(int idx) {
        return genes[idx + bEnd].c;
    }
    D getD(int idx) {
        return genes[idx + cEnd].d;
    }
};

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

Например, если вам нужно добавить B, вы должны переместить его в точку первого C, затем переместить его в точку первого D, а затем нажать на него в конце. Затем вы увеличиваете каждый из bEnd и cEnd.

Например:

Первоначально: [A1, A2, A3, B1, B2, C1, C2, C3, C4, D1, D2, D3, D4, D5]

Добавить новый B на bEnd: [A1, A2, A3, B1, B2, B3, C2, C3, C4, D1, D2, D3, D4, D5]

Заменить выселенного C: [A1, A2, A3, B1, B2, B3, C2, C3, C4, C1, D2, D3, D4, D5]

Добавьте выселенный D в конце: [A1, A2, A3, B1, B2, B3, C2, C3, C4, C1, D2, D3, D4, D5, D1].

Чтобы удалить ген за постоянное время, вы должны сделать то же самое в обратном порядке. Суть в том, чтобы сохранить инвариант, что все As предшествуют всем B, которые предшествуют всем C, которые предшествуют всем D.

Эта схема позволяет вам обходиться меньшими издержками: только один int на тип (для хранения конечного индекса), в обмен на более уродливый код, немного дополнительных временных затрат при добавлении или удалении генов и невозможность Держите каждый набор генов в порядке вставки. Вам решать, стоят ли эти недостатки этого.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...