我们的一个项目处理大量数据。它从数据库中选择数据,然后将结果序列化为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()函数并不是很聪明。通常,应该使用构造函数来初始化可能的参数。
您通常偏爱哪种方式?之前还是之后?
答案 0 :(得分:13)
一方面,这是主观的。另一方面,广泛观点认为,在C ++中,应避免使用这种“ init function”惯用语,因为:
这是更糟糕的代码
没有必要
每次创建一个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中提出的观点。
仅仅将构造函数移出循环并不是一个好主意。
但是,对于这种样式的循环主体:
在恕我直言的情况下,当使用带有init函数的第二种形式时,转换对象通常会在堆上分配一些缓冲区,这些缓冲区可能会被重用。在幼稚的实现中,这种重用甚至可能不是故意的,而只是实现的副作用。
因此,在IFF中,通过重构(使对象构造函数退出循环),可以看到速度加快,可能是,因为该对象现在可以重用某些缓冲区,并且避免为这些缓冲区重复进行“冗余”堆分配。
因此,总而言之:
您不希望出于自身的原因将构造函数从循环中吊起。 但是,您希望所有可以保留的缓冲区都可以在循环迭代中保留。