内容简介:更新:我们仍然没有关于parse()的内存访问模式的大量信息,以及从内存中读取输入数据花费的时间与写入/读取私有临时内存所花费的时间之间的关系.你说p-> parse()“根据XML节点的内容分配内存”.如果它再次释放它,你可能会看到保持在每个线程中分配足够大的暂存缓冲区的大加速.内存分配/释放是一种“全局”的事情,需要线程之间的同步.线程感知分配器可以通过满足刚刚被该线程释放的内存中的分配来处理分配/释放/分配/免费模式,因此它可能仍然在该核心上的私有L1或L2缓存中很热.
我有一个数据结构(向量),元素必须由函数解析,其中元素可以由不同的线程解析.
以下是解析方法:
void ConsumerPool::parse(size_t n_threads, size_t id) { for (size_t idx = id; idx < nodes.size(); idx += n_threads) { // parse node //parse(nodes[idx]); parse(idx); } }
哪里:
> n_threads是线程的总数
> id是当前线程的(单义)索引
并创建线程如下:
std::vector<std::thread> threads; for (size_t i = 0; i < n_threads; i++) threads.emplace_back(&ConsumerPool::parse, this, n_threads, i);
不幸的是,即使这种方法有效,如果线程数太高,我的应用程序的性能也会降低.我想了解为什么即使这些线程之间没有同步,性能也会降低.
以下是根据使用的线程数经过的时间(线程开始和最后一次join()返回之间):
> 2个线程:500毫秒
> 3个线程:385毫秒
> 4个线程:360毫秒
> 5个线程:475毫秒
> 6个线程:580毫秒
> 7个线程:635毫秒
> 8个线程:660毫秒
创建线程所需的时间始终在1/2 ms之间.
该软件已使用其发布版本进行了测试.以下是我的配置:
2x Intel(R) Xeon(R) CPU E5507 @ 2.27GHz Maximum speed: 2.26 GHz Sockets: 2 Cores: 8 Logical processors: 8 Virtualization: Enabled L1 cache: 512 KB L2 cache: 2.0 MB L3 cache: 8.0 MB
编辑:
parse()函数的作用如下:
// data shared between threads (around 300k elements) std::vector<std::unique_ptr<Foo>> vfoo; std::vector<rapidxml::xml_node<>*> nodes; std::vector<std::string> layers; void parse(int idx) { auto p = vfoo[idx]; // p->parse() allocate memory according to the content of the XML node if (!p->parse(nodes[idx], layers)) vfoo[idx].reset(); }
更新:
我们仍然没有关于parse()的内存访问模式的大量信息,以及从内存中读取输入数据花费的时间与写入/读取私有临时内存所花费的时间之间的关系.
你说p-> parse()“根据XML节点的内容分配内存”.如果它再次释放它,你可能会看到保持在每个线程中分配足够大的暂存缓冲区的大加速.内存分配/释放是一种“全局”的事情,需要线程之间的同步.线程感知分配器可以通过满足刚刚被该线程释放的内存中的分配来处理分配/释放/分配/免费模式,因此它可能仍然在该核心上的私有L1或L2缓存中很热.
使用某种分析来找到真正的热点.它可能是内存分配/释放,也可能是读取内存的代码.
您的双插槽Nehalem Xeon没有超线程,因此如果非HT感知操作系统在同一物理核心的两个逻辑核心上调度两个,则无法遇到线程相互减慢的问题.
您应该使用性能计数器(例如Linux perf stat或Intel的VTune)进行调查,一旦传递4个线程,每个线程是否会获得更多缓存未命中. Nehalem使用大型共享(对于整个套接字)L3(也就是最后一级)缓存,因此在同一套接字上运行的更多线程会对此产生更大的压力.相关的性能事件将类似于LLC_something,IIRC.
你肯定应该看看L1 / L2未命中,并看看它们如何随线程数量进行扩展,以及如何通过strided与连续访问node []进行更改.
您可以检查其他性能计数器以查找错误共享(一个线程的私有变量与另一个线程的私有变量共享一个缓存行,因此缓存行在核心之间反弹).真的只是寻找随线程数变化的任何perf事件;这可能指向解释的方式.
像2插槽Nehalem这样的多插槽系统将具有NUMA (Non-uniform_memory_access) .一个支持NUMA的操作系统将尝试为内核分配快速内存.
所以假设你的缓冲区的所有物理页面都连接到你的两个插槽中的一个.在这种情况下,它可能不是你可以或应该避免的东西,因为我假设你在将它交给多个线程进行解析之前以单线程方式填充数组.但是,一般情况下,尝试在最方便使用它的线程中分配内存(特别是临时缓冲区),这很方便.
这可能部分解释了线程数量不完美的扩展.虽然这很可能与事情无关,但如果@ AntonMalyshev的回答没有帮助.让每个线程在一个连续的范围内工作,而不是以n_threads的步幅跨越数组,对于L2 / L1缓存效率应该更好.
node []是一个指针向量(因此有8个线程,每个线程只使用它在node []中接触的每个64字节高速缓存行的8个字节).但是,每个线程可能会触及指向数据结构和字符串中的更多内存.如果节点条目指向其他数据结构和字符串中的单调增加位置,则对节点[]的跨步访问会为线程触及的大多数内存创建非连续访问模式.
跨步访问模式的一个可能的好处:Strided意味着如果所有线程以大致相同的速度运行,它们都会同时查看内存的相同部分.领先的线程将从L3未命中速度减慢,而其他线程因为看到L3命中而赶上. (除非发生某些让一个线程落后的事情,比如操作系统在一个时间片内对其进行调度.)
因此,L3和RAM带宽/延迟可能比有效使用每核L2 / L1更成问题.可能有更多线程,L3带宽无法跟上来自多个核心的L2缓存的相同缓存行的所有请求. (L3速度不够快,无法同时满足所有内核的常量L2未命中,即使它们都在L3中命中.)
仅当node []的连续范围指向其他内存的连续范围时,此参数才适用于node []指向的所有内容.
翻译自:https://stackoverflow.com/questions/38829974/performance-decreases-with-a-higher-number-of-threads-no-synchronization
以上所述就是小编给大家介绍的《c – 性能随着线程数量的增加而降低(无同步)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Java 多线程(二)—— 线程的同步
- 线程的三个同步器
- java synchronize - 线程同步原理
- 多线程六 同步容器&并发容器
- C++ 线程同步的四种方式
- 【译】JVM 进行线程同步背后的原理
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。