c++ - 在运行时添加字段 : suffer either vtable-cost or cache miss at destructor

标签 c++ caching c++14 destructor vtable

我想在运行时向(假)类添加非静态字段。

它对于解耦很有用,例如systemA.cpp可以为某个Data添加一个int字段,而systemB.cpp可以添加 BC 到同一个 Data 类。

因为这是一个很长的问题,我将分别讨论我的设计、MCVE、它的缺陷、我的方法和问题。

设计

在我的设计中,内存布局与普通类相同。
这是图表:-

enter image description here

在我的用例中,上面的布局比 int-int-int ... B-B-B ... 更快。​​
(我剖析了。)

为简单起见,我锁定每个字段 64 个字节。
用户必须在使用之前将每个所需的字段及其类型注册到我的库中:-

    Ref<int> refInt=addField<int>();  //e.g. systemA.cpp
    Ref<B> refB=addField<B>();        //e.g. systemB.cpp
    Ref<C> refC=addField<C>();

我的库将计算用户在每个 Data 实例中需要多少个字段(在本例中为 3 个)。

然后,他们可以分配一些Data实例,并访问Data的动态字段。

    std::vector<Data> datas=allocate(10);
    refInt.get(datas[0])=8;

要删除数据,用户不必调用任何析构函数(BC's) 手动。
^ 这是目标之一。

代码 ( MCVE )

这是我的代码(库级,简化):-

#include <iostream>
#include <vector>
//---- library -----
void* utilAddAddress(void* current,int offset){
    return reinterpret_cast<char*>(current)+offset;
}
int sizePerInstance=0;//accumulate
struct Data{
    void* mem=nullptr;
    Data(void* pmem){mem=pmem;}
};
template<class T>struct Ref{
    int offset=0;
    T& get(Data data){
         return *static_cast<T*>(utilAddAddress(data.mem,offset));
    }
};
template<class T>Ref<T> addField(){
    Ref<T> reff;
    reff.offset=sizePerInstance;
    sizePerInstance+=64;
    return reff;
}
std::vector<Data> allocate(int numInstance){
    void* oNew= ::operator new(static_cast<size_t>( numInstance*sizePerInstance));  
    std::vector<Data> toReturn;
    for(int n=0;n<numInstance;n++){
        toReturn.push_back(Data(utilAddAddress(oNew,n*sizePerInstance)));
    }
    return toReturn;
}

这是它的使用方法:-

class B{    };
class C{   
    std::string danger;    
};
int main(){
    Ref<int> refInt=addField<int>(); //offset = 0
    Ref<B> refB=addField<B>();       //offset = 64
    Ref<C> refC=addField<C>();       //offset = 128
    std::vector<Data> datas=allocate(10);
    refInt.get(datas[0])=8;
    //destructor ?????
}

问题

它有效,但我找不到有效销毁大量 Data 实例的方法。

要销毁 Data 实例,我必须调用每个字段的析构函数:intBC .
有可能有些字段(例如 C)不是 POD 类型,所以我不能跳过这个阶段。

到目前为止,我只找到了 2 个选择。 (我会假设每个实例的每个字段都被构造):-

  1. 按以下顺序调用析构函数:[0]int [0]B [0]C [1]int [1]B [1]C ...
  2. 按以下顺序调用析构函数:[0]int [1]int [2]int ... [0]B [1]B ... [0]C [1]C ...

第一选择

这是一种可能的方式(草案):-

std::vector<std::function<......>> deleters;
template<class T>Ref<T> addField(){
    ......
    deleters.push_back([](......){
        static_cast<T*>(......)->~T();
    });
    ......
}

为了适本地调用所有析构函数,我将:-

for(int n=0;n<10;n++){
    for(int m=0;m<3;m++){
        deleters[m](data[n]);
    }
}

我将承受 v-table 成本。 (我剖析)

第二选择

我将遭受缓存未命中,如下图所示。 (我剖析)

enter image description here

问题

当删除大量连续的Data时,如何高效调用所有合适的析构函数?

我不期待完整的代码。粗略的描述/片段就足够了。

旁注:

  • 在实际情况下,它通常分配约 4000 个实例。 Data 最多有 30 个字段。
  • 该库位于游戏引擎核心中。它支持多种类型,例如数据1 数据2 ...
  • 作为一项临时措施,我放弃了对非 POD 字段的支持。

最佳答案

最快的方法是通过利用自定义分配器来实现 O(1)。

所有进入数据的东西都必须使用特定的分配器,std::stringtypedefstd::basic_string<char> ,它也有一个默认分配器。

using CBString = std::basic_string<char, std::char_traits<char>, CBAllocator<char>>;

现在 Data被销毁,就制作CBAllocator删除其所有池。

您还可以根据 std::is_trivially_destructible 将字段分成两组.要么制作 2 个不同的数据,要么制作一个全无 std::is_trivially_destructible相邻以提高缓存局部性。您现在可以忽略 std::is_trivially_destructible 的析构函数.

关于c++ - 在运行时添加字段 : suffer either vtable-cost or cache miss at destructor,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47737406/

相关文章:

c++ - 如何像 Eclipse Java 一样自动导入包含在代码块中?

c++ - C++ 中的 Lua 转储

ruby-on-rails - 防止 Rails 2/3 缓存 Lib/Classes

c++ - 如何使 '=' 重载在 '=' 的另一端工作

c++ - 如何模拟 remove_unless

c++ - 使用动态转换的 Lambda

c++ - 如何创建 Gdk::Pixbuf 实例的 std::map 并在 Gdk::Cairo 中使用它

android - 计划在用户连接到互联网时执行的离线任务

node.js - 如何将 Mongoose 与 GraphQL 和 DataLoader 结合使用?

c++ - 为什么在使用可变构造函数时必须创建类型别名?