使 2 个非静态字段(即动态数组)彼此靠近地使用内存

Make 2 non-static fields (that are dynamic arrays) use memory near each other

提问人:cppBeginner 提问时间:9/11/2017 最后编辑:cppBeginner 更新时间:9/11/2017 访问量:50

问:

让 和 成为动态大小的存储类。
(例如
B1B2B1~std::vector<char> B2~std::vector<float>)

在 C++ 11 中,如果我编码和 's 和 函数(五法则),默认情况下,将它们作为字段包含的类将自动正确复制/移动。B1B2movecopyC

class C{
    B1 b1; B2 b2;
};

它工作得很好。

问题

今天,我得到了配置文件结果+做了一些关于性能问题的测试。
主要目标:我必须使和相同的实例在彼此附近分配内存:-
b1b2C

b1[0]  b1[1] ... b1[b1.size-1] (minimum gap) b2[0]  b2[1] ... b2[b2.size-1] 

如果可以的话,我将在整个程序中获得 10-20% 的性能提升。

我可怜的解决方案

我可以使用这样的自定义分配器(伪代码):-

class C{
    B1 b1; 
    B2 b2;
    Allocator* allo_; // can be heap allocator 
    public: void reserve(int size){
        //old : b1.reserve(size); b2.reserve(size);  .... so easy
        //new :-
        B1 b1Next; B2 b2Next;
        int nb1=b1Next.howMuchIWant(size); 
        int nb2=b2Next.howMuchIWant(size);
        //^ request amount of bytes needed if capacity="size"
        void* vPtr=allo_->allocate(nb1+nb2);
        b1Next.setMemory(vPtr);
        b2Next.setMemory(vPtr + nb1);  //add "vPtr" by "nb1" bytes
        b1Next=b1;   //copy assignment (not move memory)
        b2Next=b2;   //copy assignment (not move memory)
        b1=std::move(b1Next);   //move memory
        b2=std::move(b2Next);   //move memory 
        //clean up previous "vPtr" (not shown)
    }
};

它可以工作,但代码变得更难调试/维护。更不用说移动和复制了。C

在旧版本中,all / mess 仅出现在 和 中。
现在,混乱出现在每个使用数据结构的类中,就像 和 直接一样。
copymoveB1B2B1B2

问题

有哪些 C++ 技术/设计模式/习语可以提供帮助?
要回答这个问题,不需要任何可运行的代码。伪代码或只是一个概念就足够了。

我很遗憾没有提供MCVE。
自定义分配器和阵列管理是真正难以最小化的事情。

C++ 内存管理 C++14 三法则

评论

0赞 nh_ 9/11/2017
有必要拥有所有和内部 s 吗?否则,您可以分配一个字节 () 并在自己内部布局数据。然后,数据始终组织在单个内存区域中,并且复制和移动操作应该易于实现。charintvectorvectoruint8_t
0赞 cppBeginner 9/11/2017
@nh_ 如果我只需要实现一个 .在实际情况下,目前有 5 个这样的类,并且还在增加。我希望使数组本身更可重用。嗯。。。。C
0赞 Jarod42 9/11/2017
真的是动态的,还是只是设置为构造?因为push_back可能会使 .B1B1B2
0赞 cppBeginner 9/11/2017
@Jarod42 是的,push_back可以使一切无效。 就是一个例子——如果容量不够,也会被重新分配作为副作用。在实践中,通常在读取模式下查询多次,然后在写入模式下查询多次,...。reserve()b1b2C

答:

1赞 nh_ 9/11/2017 #1

改善数据局部性的一种可能性是从 a of s 变为 a of s。而不是structvectorvectorstruct

struct S
{
    std::vector<char> c;
    std::vector<int> i;
};
S data;

使用

struct S
{
    char c;
    int i;
};
std::vector<S> data;

这样,数据始终存储在一起,您无需修改自定义分配器。这是否适用于您的情况主要取决于两个条件:

  • 是否有必要拥有所有(或)连续的?例如,因为定期调用 API 需要相应类型的 API。charintvector
  • 存储的数量是否相等(至少几乎相等)?charint

评论

0赞 cppBeginner 9/11/2017
就我而言,由于您已经提到的原因,我无法使用它。(它们的大小不相等,必须是连续的,数据局部性和缓存未命中问题等)但是谢谢!如果我变得绝望,这是个好主意。i
0赞 nh_ 9/11/2017
@cppBeginner 在执行过程中,和 的数量是否发生了巨大变化?和 是否有任何估计的边界?而且,两者的顺序是否重要?intcharc.size()i.size()ic
0赞 cppBeginner 9/11/2017
这很难回答。我有许多具有此症状的自定义数据结构,例如 SparseMap<T>、、.它们彼此如此不同。它们具有共同的功能,如果需要,还可以提供自动生长1->2->4->8。其自身数组内的顺序通常很重要,但数组和数组之间的顺序并不重要。SetOfIntSimpleArray<T>reserve(int size)icic
0赞 nh_ 9/11/2017
@cppBeginner 恐怕如果您需要最佳性能,解决方案将在某种程度上针对特定问题量身定制,并且通常不适用。我正在考虑一个连续的字节数组,它被分成两半,使得 s 从前面到中间增长,而 s 从中间到结尾增长。但很难说这是否有益,因为如果数字定期变化,重新分配可能经常是必要的。ic
0赞 cppBeginner 9/11/2017
我目前喜欢这个。如果我从 开始,那么我加上 ,它将变成 。性能已经足够好了。我的问题主要是关于代码的可维护性/可重用性。我已经使用和放置新的,而且速度非常快。:)[c0,c1,i0,i1]c2[c0,c1,c2,-,i0,i1,-,-]char[]