gpt4 book ai didi

c++ - 堆上的多线程(取消)分配

转载 作者:塔克拉玛干 更新时间:2023-11-02 23:15:41 24 4
gpt4 key购买 nike

我在磁盘上存储了一个非常大的数组,其中包含大约 30M 个对象,每个对象大约 80 字节——对于后续对象来说大约是 2.2GB。每个对象的实际大小略有不同,因为每个对象都有一个 QMap<quint32, QVariant>。 child 。

从原始数据中解包这些对象非常昂贵,因此我实现了一个多线程读取操作,该操作按顺序从磁盘中提取几 MB,然后将每个原始数据 block 传递给一个线程以在其中解包通过 QtConcurrent 并行.我的对象是在工作线程内的堆上创建的(通过 new ),然后传递回主线程以进行下一步。完成后,这些对象将在主线程中删除。

在单线程环境中,这种释放相对较快(~4-5 秒)。但是,当在 4 个线程上进行多线程时,这种重新分配非常慢(~26-36 秒)。使用 Very Sleepy 对其进行分析表明速度下降在 MSVCR100 free 中,所以是释放本身很慢。

搜索 SO 表明 allocating and deallocating on different threads is safe .减速的根源是什么,我该怎么办?

编辑:一些示例代码传达了正在发生的事情的想法:为了排除故障,我已从该示例中完全删除磁盘 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);

以下是向 MyObject::map 插入数据和不向 MyObject::map 插入数据以及有 1 个或 4 个线程可用于 QtConcurrent 的结果:

  • 1 个主题:tnew = 2.7 秒; tdelete = 1.1 秒
  • 4 个主题:tnew = 1.8 秒; tdelete = 2.7 秒
  • 1 个线程 + QMap:tnew = 8.6 秒; tdelete = 4.6 秒
  • 4 个线程 + QMap:tnew = 4.0 秒; tdelete = 48.1 秒

在这两种情况下,在 4 个线程上并行创建对象与在 1 个线程上串行创建对象相比,删除对象所花费的时间要长得多,并行插入 QMap 会进一步加剧这种情况。

最佳答案

这几乎是猜测,但我假设操作系统内存管理器将是一个系统范围的,毕竟它确实为一个虚拟内存池提供服务,所以向它投入更多线程不会提高速度,它只会使它窒息高架。线程安全与并发访问总是伴随着惩罚。所以你扔给它的线程越多,你得到的惩罚就越多。

30M 的分配是相当多的,不管分配的大小如何,它也代表了显着的开销内存消耗。我建议您投入时间来实现内存池,预分配整体内存块并使用 placement new 在这些池中分配对象。这将极大地节省 CPU 时间和内存。此外,它将通过减少碎片来提高缓存友好性和缓存命中率。

打个比方,将 4 个厨师放在一个炉子上不会使 cooking 速度快 4 倍,这会使每个厨师至少慢 4 倍,再加上他们在资源使用冲突中浪费的时间。这几乎就是您在实践中看到的情况。

关于c++ - 堆上的多线程(取消)分配,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41248590/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com