我们说我有一个类Manager
做了一些工作,然后我有一个DistributedManager
,它继承自Manager
但重新实现了一些使用多线程的方法。
使用它们的代码是
Manager<T,W,P> manager(initargs);
manager.compute(runargs);
或
DistributedManager<T,W,P> manager(initargs, 4); // 4 is number of thread to use
manager.compute(runargs);
然后在某些时候我想要通过命令修改我可以使用的线程数量。所以我创建了一个size_t nbthread = 1
,它可以通过一个选项修改,我修改我的代码如下:
Manager<T,W,P>* manager;
switch(nbthread)
{
case 0:
case 1:
manager = new Manager<T,W,P>(initargs);
break;
default:
manager = new DistributedManager<T,W,P>(initargs, nbthread);
break;
}
manager->compute(runargs);
它编译和工作......但我得到了crapy表演!
使用带有DistributedManager
和4个线程的第一个方法,我可以在500毫秒运行,使用第二个方法,相同的计算运行超过2000毫秒。
分配部分不应该那么长:
sizeof(Manager<T,W,P>) : 104
sizeof(DistributedManager<T,W,P>) : 128
出了什么问题?
使用
完成基准测试std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();
densityfieldptr->compute(particles, massfield, massthreshold, densityfunctor);
std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now();
printf("computation time: %ld ms\n", std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count());
时间的增加大约是因子4 ...因为非多线程版本是被调用的版本。使用指向基类的指针会使用方法!
virtual
关键字不能用于解决这个问题,因为方法是模板化的
我不得不重新考虑我的模板模式,一切都按预期工作
答案 0 :(得分:1)
我同意评论说没有足够的信息可以明确地说出正在发生的事情,但我有一个理论(false sharing)这个评论太长了,所以这是一个试探性的答案。
两者之间的主要区别是存储分配
自动存储在堆栈上实现,堆上的动态存储。你这么说什么? 那么“new”的实现并不能保证在缓存行边界和多个缓存行的分配。我之前遇到过性能问题,其中堆上的对象与另一个被另一个线程访问的对象共享,并且至少有一个对象被修改(由线程写入) - false sharing。这导致高速缓存行在线程运行的核心之间来回“乒乓”。它不是一个逻辑错误,但它是一个性能错误。编译器可以以防止错误共享的方式在堆栈上进行分配,或者它可能刚刚发生而不是那里的问题,但可能无法保证,更改某些内容在您的程序中,您可能也会看到自动存储的问题。解决这个问题的唯一方法是使用自定义分配器。
答案 1 :(得分:0)
使用指针可能会导致缓存未命中和分支未命中预测。但是如果没有分析器,你只能得到猜测,而不是一个可靠的答案。