重用对象与创建新对象

时间:2018-10-17 09:19:59

标签: c++

我们的一个项目处理大量数据。它从数据库中选择数据,然后将结果序列化为JSON / XML。

有时选定的行数可以轻松达到5000万个标记。

但是,程序的运行时间在一开始就很糟糕。

因此,我们对程序进行了重大调整:

不会为每一行重新创建用于序列化的工作对象,而是将清除并重新初始化该对象。

例如:

之前:

对于每个数据库行,我们创建一个DatabaseRowSerializer对象,并调用特定的序列化函数。

// Loop with all dbRows
{
    DatabaseRowSerializer serializer(dbRow);
    result.add(serializer.toXml());
}

之后:

DatabaseRowSerializer的构造函数未设置dbRow。而是由initDbRow()函数完成。

这里的主要事情是,整个运行期间将只使用一个对象。 dbRow序列化之后,clear()函数 将被调用以重置对象。

DatabaseRowSerializer serializer;

// Loop with all dbRows
{
    serializier.initDbRow(dbRow);
    result.add(serializer.toXml());
    serializier.clear();
}

所以我的问题:

这真的是解决问题的好方法吗? 我认为init()函数并不是很聪明。通常,应该使用构造函数来初始化可能的参数。

您通常偏爱哪种方式?之前还是之后?

2 个答案:

答案 0 :(得分:13)

一方面,这是主观的。另一方面,广泛观点认为,在C ++中,应避免使用这种“ init function”惯用语,因为:

  1. 这是更糟糕的代码

    • 您必须记住要“初始化”您的对象,如果没有,它处于什么状态?您的对象永远不会处于“死”状态。 (不要让我开始“移出”对象……)这是为什么 C ++引入了构造函数和析构函数,因为旧的C方法有点儿混用,因此生成的程序很难证明是正确的。
  2. 没有必要

    • 每次创建一个DatabaseRowSerializer基本上没有开销 ,除非它的构造函数比您的initDbRow函数做得更多,在这种情况下,您的两个示例是无论如何都不相等。

      即使您的编译器没有优化不必要的“分配”,也实际上并没有分配,因为对象仅占用堆栈空间,并且无论如何都必须这样做。

      因此,如果此更改确实解决了您的性能问题,则可能正在发生其他事情。

使用构造函数和析构函数。自由而自豪!

这是编写C ++时的常见建议。


如果您确实出于任何原因希望使序列化程序可重用,则可能的第三种方法是将其所有状态都移至实际的操作函数调用中:

DatabaseRowSerializer serializer;

// loop with all dbRows
{
    result.add(serializer.toXml(dbRow));
}

如果串行器希望缓存信息或重新使用动态分配的缓冲区以提高性能,则可以执行此操作。当然,这会在序列化器中添加一些状态。

如果您执行此操作后仍然没有任何状态,则整个过程可能只是一个静态调用:

// loop with all dbRows
{
    result.add(DatabaseRowSerializer::toXml(dbRow));
}

...但是它也可能只是一个函数。

最终我们无法确切知道最适合您的选择,但是有很多选择和考虑事项。

答案 1 :(得分:4)

通常,我同意LRiO在other answer中提出的观点。

仅仅将构造函数移出循环并不是一个好主意。

但是,对于这种样式的循环主体:

  1. 向对象提供一些数据
  2. 在对象内转换数据
  3. 从对象返回转换后的数据

在恕我直言的情况下,当使用带有init函数的第二种形式时,转换对象通常会在堆上分配一些缓冲区,这些缓冲区可能会被重用。在幼稚的实现中,这种重用甚至可能不是故意的,而只是实现的副作用。

因此,在IFF中,通过重构(使对象构造函数退出循环),可以看到速度加快,可能是,因为该对象现在可以重用某些缓冲区,并且避免为这些缓冲区重复进行“冗余”堆分配。

因此,总而言之:

希望出于自身的原因将构造函数从循环中吊起。 但是,您希望所有可以保留的缓冲区都可以在循环迭代中保留。

相关问题