Phl*_*ous 6 c++ qt multithreading memory-management
我有一个非常大的~30M对象,每个大约80字节 - 对于那些跟随的人来说是~2.2GB - 存储在磁盘上.每个对象的实际大小略有不同,因为每个对象都有一个QMap<quint32, QVariant>孩子.
从原始数据中解压缩这些对象是很昂贵的,所以我实现了一个多线程读取操作,它顺序从磁盘中提取几MB ,然后将每个原始数据块传递给一个线程,以便通过并行解压缩QtConcurrent.我的对象new在工作线程内的堆上创建(via ),然后传递回主线程以进行下一步.完成后,将在主线程上删除这些对象.
在单线程环境中,这种释放相对较快(约4-5秒).但是,当在4个线程上进行多线程处理时,这种释放速度非常慢(约26-36秒).使用Very Sleepy进行分析表明减速是在MSVCR100中free,因此释放本身就很慢.
搜索SO表明在不同线程上分配和解除分配是安全的.减速的来源是什么,我该怎么办呢?
编辑:一些示例代码传达了正在发生的事情: 为了排除故障,我已经从这个示例中完全删除了磁盘IO,只需创建对象然后删除它们.
class MyObject
{
public:
MyObject() { /* set defaults... irrelevant here */}
~MyObject() {}
QMap<quint32, QVariant> map;
//...other members
}
//...
QList<MyObject*> results;
/* set up the mapped lambda functor (QtConcurrent reqs std::function if returning) */
std::function<QList<MyObject*>(quint64 chunksize)>
importMap = [](quint64 chunksize) -> QList<MyObject*>
{
QList<MyObject*> objs;
for(int i = 0; i < chunksize; ++i)
{
MyObject* obj = new MyObject();
obj->map.insert(0, 1); //ran with and without the map insertions
obj->map.insert(1, 2);
objs.append(obj);
}
return objs;
}; //end import map lambda
/* set up the reduce lambda functor */
auto importReduce = [&results](bool& /*noreturn*/, const QList<MyObject*> chunkimported)
{
results.append(chunkimported);
}; //end import reduce lambda
/* chunk up the data for import */
quint64 totalcount = 31833986;
quint64 chunksize = 500000;
QList<quint64> chunklist;
while(totalcount >= chunksize)
{
totalcount -= chunksize;
chunklist.append(chunksize);
}
if(totalcount > 0)
chunklist.append(totalcount);
/* create the objects concurrently */
QThreadPool::globalInstance()->setMaxThreadCount(1); //4 for multithreaded run
QElapsedTimer tnew; tnew.start();
QtConcurrent::mappedReduced<bool>(chunklist, importMap, importReduce, QtConcurrent::OrderedReduce | QtConcurrent::SequentialReduce);
qDebug("DONE NEW %f", double(tnew.elapsed())/1000.0);
//do stuff with the objects here
/* delete the objects */
QElapsedTimer tdelete; tdelete.start();
qDeleteAll(results);
qDebug("DONE DELETE %f", double(tdelete.elapsed())/1000.0);
Run Code Online (Sandbox Code Playgroud)
以下是有和没有向MyObject :: map插入数据的结果,以及QtConcurrent可用的1或4个线程:
tnew= 2.7秒; tdelete= 1.1秒tnew= 1.8秒; tdelete= 2.7秒tnew= 8.6秒; tdelete= 4.6秒tnew= 4.0秒; tdelete= 48.1秒在这两种情况下,当在4个线程上并行创建对象时,删除对象所需的时间要长得多,而在1个线程上是串行创建,这通过并行插入QMap进一步加剧了.
这几乎是猜测,但我认为操作系统内存管理器将是一个系统范围的,毕竟它确实为一个虚拟内存池提供服务,因此抛出更多线程不会提高速度,它只会用开销来阻塞它.线程安全加上并发访问总是会受到惩罚.所以你投入的线程越多,你获得的惩罚就越多.
无论分配的大小如何,30M分配都是相当多的,并且它也代表了显着的开销内存消耗.我建议你花时间来实现内存池,预先分配单块内存块,并使用placement new来分配这些池中的对象.这将是一个巨大的CPU节省时间和显着的内存保护.此外,它还可以通过减少碎片来提高缓存友好性和缓存命中率.
把它作为一个比喻,将4个厨师放在一个炉子上不会使烹饪速度提高4倍,这将使每个厨师至少慢4倍加上他们在资源使用冲突中浪费的时间.这就是你在实践中看到的.