C ++:如何处理未使用的空向量消耗的RAM?

时间:2019-05-21 01:19:47

标签: c++ algorithm design-patterns

我有一个C ++软件,可以执行基于代理的仿真。在模拟中,有Population个包含Patch的元素,这些元素包含两个两个Individual的{​​{1}}个元素。每个Haplotype包含12个向量,每个向量都用于追踪不同类型的基因。

Haplotype

但是,实际上,用户永远不会使用一种或两种以上的基因。这意味着这些未使用的向量中的每一个都消耗几个字节(至少两个指针)。对于具有很多基因和很少class Haplotype { std::vector<A> genesA; std::vector<B> genesB; std::vector<C> genesC; std::vector<D> genesD; .... }; 的模拟,可以忽略不计。但是,对于很少有基因和很多个体的模拟,这些额外的字节可能开始变得重要。性能(CPU时间和RAM)至关重要。

是否有一个不错的设计模式可以让我处理此问题?是否可以仅根据需要将向量添加到Individual中?

3 个答案:

答案 0 :(得分:2)

我认为无论您做什么,都必须保留一些空间以容纳数据。

向量有可能“浪费”一些未使用的空间。另外-对象本身会占用一些内存,就像您说的那样。

但是,使用数组时,您可以完全控制。 使用std::vector<Gene>[] genes;,您可以完全使用所需的内存。 但是现在您必须知道哪个索引代表哪种类型(A,B,C ...)。这些信息将再次消耗您的内存... 另外,如果要添加另一个数组,还必须复制并重新分配该数组。

您可以避免使用指针,并使用nullptr对其进行初始化,从而为每个向量使用分配的内存。然后,您可以根据需要分配它们。 这样,您只会浪费指针的空间。 当前实现它的方式,您将初始化每个向量对象(可能还有一些保留空间)。

答案 1 :(得分:1)

我不知道这是否是您要寻找的东西,但我认为,可以存储一个仅包含有用矢量的矢量,而不是存储仅一个或两个有用的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,那么我认为这可以作为替代选择。

答案 2 :(得分:0)

如果所有基因类型的大小都大致相同,那么做这样的事情可能是一个胜利:

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的位置,然后将其推到最后。然后,您将递增bEndcEnd中的每一个。

例如:

最初:[A1, A2, A3, B1, B2, C1, C2, C3, C4, D1, D2, D3, D4, D5]

在bEnd处添加新的B:[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]

要在恒定时间内删除基因,您将做相反的事情。关键是要保持不变,所有A都在所有B之前,在所有C之前在所有D之前。

此方案可让您以较少的开销摆脱繁琐的工作:每种类型仅一个int(用于存储结束索引),以换取更难看的代码,添加或删除基因时会花费一些额外的时间,并且无法保持每组基因的插入顺序。这些缺点是否值得您决定。

此外,如果类型的大小截然不同,则此设计将招致每个元素的开销,因为联合必须至少与其最大成员一样大。