实现类型擦除列表以实现快速写入/读取

时间:2014-08-27 14:03:12

标签: c++ type-conversion type-erasure

我正在尝试实现一种用于写入和删除的类型擦除数据结构 读取列表中任何类型的大型数组,具有以下内容 要求:

  • 快速插入批量数据(接收std::vector<T>,其中T是基本类型)。
  • 如果类型匹配,则快速读取所有/最新值
  • 如果类型不匹配,则读取/转换。在大多数情况下,从原始到原始(例如double-&gt; float,int-&gt; double)

我想到的界面看起来像这样:

class Trace {
      template<typename T> std::vector<T> read();
      template<typename T> std::vector<T> latest();
      template<typename T> void append(std::vector<T> values);
      template<typename T> void replace(std::vector<T> values);
      void clear();
};

然后在TraceHandler类(Singleton结构)中使用它,它允许每个键访问跟踪:

class TraceHandler {
public:
  template<typename T>
  std::vector<T> read(std::string const& key);
  template<typename T>
  void write(std::string const& key, std::vector<T> const& val);
private:
  // STore all Traces for different types
};

用例看起来像这样:

TraceHandler th;
std::vector<double> vals(1000,1.0), res;
th.write("values",vals);
std::vector<int> resInt;
res = th.read<double>("values");
resInt = th.read<int>("values");

我们当前的实现为每种数据类型创建了一个跟踪 用户必须跟踪正确的类型,这不是很好 灵活(例如使用writeDouble()撰写,使用readDouble阅读。)

我的第一个方法是更改​​内部存储的类型 矢量到any类型(我们正在使用Poco库,所以我正在使用 Poco::AnyPoco::DynamicAny),但这会带来很大的影响 表现很受欢迎。

从具有高频率的设备写入数据(获取数据 高达20khz,然后以大约4k的块写入Trace), 和普通矢量与一个矢量之间的测量性能差异 任何类型的因素是因子500-1000(测量800毫秒与大4毫秒 批量插入/读取循环)。大部分时间都因为失去了 构造函数调用与简单的memcopy。

所以我的问题是:有没有办法实现这个接口(或者 替代方案)具有良好的批量插入/读取性能?


编辑: 这是我目前使用的实现:

class LWDynamicVector
{
private:
  typedef std::vector<Poco::DynamicAny> DataList;   
  DataList m_data;
public:
  LWDynamicVector() {}

  template<typename T> std::vector<T> read() {
    return std::vector<T>(m_data.begin(),m_data.end());
  }

  template<typename T> void writeAppend(std::vector<T> v) {
    m_data.insert(m_data.end(),v.begin(),v.end());
    }

  template<typename T> void writeReplace(std::vector<T> v) {
    m_data.assign(v.begin(),v.end());
  }
};

我正在使用的测试:

TEST(DynamicVector,Performance) {
  typedef double Type;
  size_t runs = 100; size_t N = 20480;
  std::vector<Type> input;
  input.reserve(N);
  for(size_t i = 0; i < N; ++i) {
    input.push_back(rand());
  }

  {
    OldVector<Type> instance;
    Poco::Timestamp ts;
    for(size_t i = 0; i < runs; ++i) {
      instance.writeAppend(input);
    }
    std::cout << "Old vector: time elapsed(ms) = " << ts.elapsed() / 1000.0 << std::endl; 
    std::vector<Type> output = instance.read();
    EXPECT_EQ(output.back(),output.back()); 
  }
  {
    LWDynamicVector dbv;
    Poco::Timestamp ts;
    for(size_t i = 0; i < runs; ++i) {
      dbv.writeAppend(input);
    }
    std::cout << "New vector: time elapsed(ms) = " << ts.elapsed() / 1000.0 << std::endl;

    std::vector<Type> output = dbv.read<Type>();
    EXPECT_EQ(output.back(),output.back());
  }

}

结果是:

Old vector: time elapsed(ms) = 44.004
New vector: time elapsed(ms) = 4380.44

关于编译器选项和优化:不幸的是,我没有选择更改它们,而是停留在当前设置。在大多数情况下,构建在调试模式下运行,但仍然必须满足时序要求。但无论如何,在发布模式下性能没有提高:

Old vector: time elapsed(ms) = 20.002
New vector: time elapsed(ms) = 1013.1

3 个答案:

答案 0 :(得分:2)

我认为问题出在收集数据阶段而不是评估中。

首先,您的OldVector不需要进行任何类型的转换,因此在POD数据上,它在插入数据时基本上会使用memcpy。

如果你真的需要动态变量内容,那么DynamicAny是一个非常好的类,但在类中我们可以看到(其中一个)性能问题

VarHolder* _pHolder;

表示插入的每个数据的一些内存分配和一些内容保存。

现在是一个概念实现,因为我无法测试它,你的Trace类

template<class T>
class Trace {
    std::vector<T> trace;

public:
    template<typename T, class U> std::vector<U> read();
    template<typename T, class U> std::vector<T> latest();
    template<typename T> void append(std::vector<T> values);
    template<typename T> void replace(std::vector<T> values);
    void clear();
};

如果您只使用一个T.这样可以正常工作。隐藏TraceHandler中的类型

class TraceHandler {
public:
  template<typename T, class U>
  std::vector<U> read(std::string const& key);
  template<typename T>
  void write(std::string const& key, std::vector<T> const& val);
private:
  // Store all Traces for different types
  std::unordered_map<const std::string, Poco::DynamicAny> values;  // abstraction
};

仅当每个键仅使用一个T且DynamicAny可以使用向量时,此方法才有效。

template<class T>
void TraceHandler::write(std::string const& key, std::vector<T> const& val) {
  if (values.find(key) == values.end()) {  // doesn't exists
    Trace<T> temp;
    temp.append(val);
    values[key] = temp;
  } else
    values[key].append(val);  // only works if DynamicAny can return original type
}

它是否适用于您的用例?

TraceHandler th;
std::vector<double> vals(1000,1.0), res;
th.write("values",vals);
std::vector<int> resInt;
//res = th.read("values"); // could work if DynamicAny could return original.
td.read("values", res);
//resInt = th.read("values"); // wont work as read can't guess the type
th.read("values", resInt); // read can guess the return type

// handle conversion from stored type to wanted return type
template<class T, class U>
void TraceHandler::read(std::string const& key, std::vector<U>& res) {
  // convert from T to U, maybe use Poco???
  ... work here!!! can be slow as its after it is captured
}

// specialization where T==U ... more work needed.
template<class T, class U>
std::vector<T>& TraceHandler::read(std::string const& key, std::vector<T>& res) {
  // no conversion needed
  // convince DynamicAny to return the original data
  res = values[key]; // will not work out of the box???
}

这应该有更好的性能,因为每个调用每个表只有一次使用Poco :: DynamicAny。可以进行一些进一步的优化以减少复制,但这可以在它运行之后完成。

答案 1 :(得分:1)

你知道你只写原始类型。你事先知道所有这些类型。使用普通的旧联合+类型标记。无法击败那个。 boost::variant也应该有用。

typedef enum { type_int, type_double } type_tag_t;
struct data_t {
  type_tag_t tag;
  union {
     int int_elem;
     double double_elem;
  }
};

boost::variant也应该有用。

或者,将整个std::vector个数据存储在

std::map<std::string, 
          boost::variant<std::vector<int>, 
                         std::vector<double>, 
                         ...
                        >
        > mymap;

答案 2 :(得分:0)

std::vector<boost::any>

它是一个专用于实现类型擦除技术的类型的库。

boost::any