TBB:concurrent_queue 高性能的奥秘

2023-12-06 10:38

本文主要是介绍TBB:concurrent_queue 高性能的奥秘,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

这里可以看到图,懒得上传到MSN了
http://ladefense.spaces.live.com/blog/cns!CB45984FA784EC2C!709.entry
/请转载者保留作者全部信息/
TBB:concurrent_queue 高性能的奥秘
 

在如今的多线程开发的滚滚浪潮中,线程安全会是一个充满正面色彩的广告语,还是一个隐含性能低下令人不安的信息?众所周知,STL库所提供的容器均不能保证线程安全,所有的工作都要需要开发者来承担。最简单的实现线程安全的手段便是使用锁来同步对容器的访问,只需要lock和unlock两行语句,容器就变成线程安全了,很简单,不是吗?不过,这时候"线程安全"就成了性能低下的同名词,期望的并发操作成了对容器的串行访问,我们不仅仅需要安全,还需要高性能。

 

TBB::concurrent_queue令人惊异地同时做到了这两点,在让开发者放心地得到线程安全的同时,还可以心安理得的享受高性能的并发访问。这其中会有什么奥秘?作为普通的开发者,我们可以从中学到什么东西?

 

Herb Sutter在DDJ <pillars of concurrency> 一文中抛出并行编程的三个简单论点,一是分离任务,使用更细粒度的锁或者无锁编程;二是尽量通过并行任务使用CPU资源,以提高系统吞吐量及扩展性;三是保证对共享资源访问的一致性。

 

<图>传统的STL:queue 加锁的方案,Intel Thread Profilec测量,图的上半部分 fully utilized 只有9%(绿色部分),下半部分有大段时间处于串行执行状态(黄色),并行运算度很低


这三个论点各有侧重,我们看看concurrent_queue是怎么做的。

 

所谓分解任务,以尽可能细的粒度执行,这样就可以让每个任务运行而不互相干扰。并行编程中有一个对应的重要概念,就是尽量使用thread的private/local数据,例如ptmalloc中采用了好几个private heap,以降低多个线程同时请求分配内存,造成访问global heap时的contend。

 

针对一个concurrent_queue,内部预先分配了8个micro_queue,并保存在名为array数组中,然后通过concurrent_queue_rep::choose函数来选择其中一个队列,这样做的好处是能把Multi Thread对1个queue的访问平均分布到多个内部的micro_queue上,从而尽可能避免冲突。

 

    micro_queue& choose( ticket k ) {

        // The formula here approximates LRU in a cache-oblivious way.

        return array[index(k)];

    }

 

    static size_t index( ticket k ) {

        return k*phi%n_queue;//phi=3,n_queue=8

    }

具体选择哪个micro_queue,取决于一个为ticket类型的变量k,这个变量实质上是push操作次数的计数。

 

例如

k=0, 那么选择 array[0*3%8]=array[0]

k=1,array[1*3%8]=array[3]

...

k=7,array[5]

k=8,array[0]  //又回到了与k=0时一样的结果。

 

 

 

无锁的push操作

即使能把queue分布到内部多个micro_queue上,那还是不可避免的会有冲突存在。例如,有可能2个不同的thread都是在访问同一个micro_queue。由上面结果可以看出,假设thread A在进行push操作时,k=0,那么使用的是array[0];而thread B同时在进行第8个push操作,k=8,使用的也是array[0],这种情况下怎么办?

 

concurrent_queue用这段代码来解决共享资源的访问冲突:

 

void micro_queue::push( const void* item, ticket k, concurrent_queue_base& base ) {

...

        push_finalizer finalizer( *this, k+concurrent_queue_rep::n_queue );

        if( tail_counter!=k ) {

            ExponentialBackoff backoff;

            do {

                backoff.pause();

                // no memory. throws an exception

                if( tail_counter&0x1 ) throw bad_last_alloc();

            } while( tail_counter!=k ) ;

        }

...

}

 

对于每一次push操作,micro_queue使用了一个tail_counter来作为阻塞标记。例如,array[0]被thread A占用时,该标记会阻止thread B更新array[0] (此时tail_counter!=k),程序会在do{}while循环里反复测试等式是否成立,不然去执行backoff.pause()以进入睡眠状态。

 

MorganStanley的Petru marginean 在DDJ上的文章<Lock-Free Queues>上曾经提出过几种阻塞的同步方法,分别是NO_WAIT(循环查询),SLEEP(睡眠),TIMED_WAIT(超时等待)以及LOCK_NO_WAIT(锁),TBB使用的这种类似于TIMED_WAIT的方法,通过pause指令让线程暂停一会后,再重试。从网上得到的资料,pause指令在这种spin_lock模式的阻塞中,可以提高25倍效率。

 

 

保证共享资源访问的一致性,及使用new placement 改善内存分配的性能

通过以上两点,TBB::concurrent_queue已经基本解决并行处理数据的问题,然而还有些细节可以提高性能。有关内存的操作永远都是性能测试中的热点(hotspot),改善性能也可以从内存管理上入手

例如,每次push操作都需要为插入的元素分配内存,malloc是一个昂贵的操作,如果能够预先分配好一大段连续内存,或者是从内存池取得一段内存呢?

 

TBB中,首先分配一个可以容纳多个对象的page,然后在具体发生push操作时,使用C++的new placement语法,再从已分配的page上取得一个对象大小的内存。

/*overide*/ virtual page *allocate_page() {

        size_t n = sizeof(page) + items_per_page*item_size;

        page *p = reinterpret_cast<page*>(my_allocator.allocate( n ));

        if( !p ) internal_throw_exception();

        return p;

    }

此外,concurrent_queue利用了TBB库的成果:atomic<>来声明一些原子变量,如tail_counter.

 

结论:


 

测试表明,并行度达到50%(图上半部分的蓝色柱),时间上也比STL:queue加锁的版本快了2-3倍。

 

concurrent_queue是一个典型的高性能并行容器实现,相信concurrent_haspmap和其它的容器,内存池及算法,都可以采用类似的方法去提高性能。只不过,仍然有些对concurrent_queue不解的地方:

 

为什么不采用Lock-Free的算法直接实现一个micro_queue?而是仍然使用一种类似spin_lock的做法去同步线程?

 

不过,目前的方案很有可能是Intel开发人员的折中选择,队列负载均衡,再加上预分配内存后,push入队操作应该很快,发生碰撞的机率并不大。毕竟Lock-Free还不够足够成熟。


关于作者:曾任职于Alcatel-Lucent,Nokia,从事高速数据处理,移动网络系统软件开发等。对Linux Kernel,多核编程饶有兴趣。


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/softarts/archive/2009/05/05/4152960.aspx

这篇关于TBB:concurrent_queue 高性能的奥秘的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/461523

相关文章

MySQL高性能优化规范

前言:      笔者最近上班途中突然想丰富下自己的数据库优化技能。于是在查阅了多篇文章后,总结出了这篇! 数据库命令规范 所有数据库对象名称必须使用小写字母并用下划线分割 所有数据库对象名称禁止使用mysql保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来) 数据库对象的命名要能做到见名识意,并且最后不要超过32个字符 临时库表必须以tmp_为前缀并以日期为后缀,备份

C++——stack、queue的实现及deque的介绍

目录 1.stack与queue的实现 1.1stack的实现  1.2 queue的实现 2.重温vector、list、stack、queue的介绍 2.1 STL标准库中stack和queue的底层结构  3.deque的简单介绍 3.1为什么选择deque作为stack和queue的底层默认容器  3.2 STL中对stack与queue的模拟实现 ①stack模拟实现

ActiveMQ—Queue与Topic区别

Queue与Topic区别 转自:http://blog.csdn.net/qq_21033663/article/details/52458305 队列(Queue)和主题(Topic)是JMS支持的两种消息传递模型:         1、点对点(point-to-point,简称PTP)Queue消息传递模型:         通过该消息传递模型,一个应用程序(即消息生产者)可以

构建高性能WEB之HTTP首部优化

0x00 前言 在讨论浏览器优化之前,首先我们先分析下从客户端发起一个HTTP请求到用户接收到响应之间,都发生了什么?知己知彼,才能百战不殆。这也是作为一个WEB开发者,为什么一定要深入学习TCP/IP等网络知识。 0x01 到底发生什么了? 当用户发起一个HTTP请求时,首先客户端将与服务端之间建立TCP连接,成功建立连接后,服务端将对请求进行处理,并对客户端做出响应,响应内容一般包括响应

Nginx高性能分析

Nginx 是一个免费的,开源的,高性能的 HTTP 服务器和反向代理,以及 IMAP / POP3 代理服务器。Nginx 以其高性能,稳定性,丰富的功能,简单的配置和低资源消耗而闻名。本文从底层原理分析 Nginx 为什么这么快! Nginx 的进程模型 Nginx 服务器,正常运行过程中: 多进程:一个 Master 进程、多个 Worker 进程。Master 进程:管理 Work

云原生之高性能web服务器学习(持续更新中)

高性能web服务器 1 Web服务器的基础介绍1.1 Web服务介绍1.1.1 Apache介绍1.1.2 Nginx-高性能的 Web 服务端 2 Nginx架构与安装2.1 Nginx概述2.1.1 Nginx 功能介绍2.1.2 基础特性2.1.3 Web 服务相关的功能 2.2 Nginx 架构和进程2.2.1 架构2.2.2 Ngnix进程结构 2.3 Nginx 模块介绍2.4

Java-数据结构-栈和队列-Stack和Queue (o゚▽゚)o

文本目录: ❄️一、栈(Stack):     ▶ 1、栈的概念:   ▶ 2、栈的使用和自实现:      ☑ 1)、Stack():       ☑ 2)、push(E e):      ☑ 3)、empty():         ☑ 4)、peek(E e):        ☑ 5)、pop(E e):       ☑ 6)、size(E e):  ▶ 3、栈自实现的总代

在亚马逊云科技上利用Graviton4代芯片构建高性能Java应用(上篇)

简介 在AI迅猛发展的时代,芯片算力对于模型性能起到了至关重要的作用。一款能够同时兼具高性能和低成本的芯片,能够帮助开发者快速构建性能稳定的生成式AI应用,同时降低开发成本。今天小李哥将介绍亚马逊推出的4代高性能计算处理器Gravition,带大家了解如何利用Graviton芯片为Java生成式AI应用提高性能、优化成本。 本篇文章将介绍如何在云平台上创建Graviton芯片服务器,并在Gra

驾驭冰雪 安全无忧,韩泰高性能冬季轮胎新品上市

- 韩泰轮胎推出冬季轮胎新产品Winter i*cept iZ3和SUV专用的Winter i*cept iZ3 X - 新轮胎采用了V型花纹,冰雪路面安全性极佳,而且具有操控性好、续航里程长的优点 - 新轮胎在位于北极圈以北300km的韩泰轮胎芬兰伊瓦洛测试场进行了严苛测试,确保极寒条件的安全性 2024年8月,韩泰轮胎正式在中国市场推出新一代高性能冬季轮胎Winter i*cept

高性能计算应用优化之代码实现调优(一)

本章将介绍代码实现过程中使用到的调优方法。在软件开发早期,开发者更多关注代码功能的实现,对代码的性能关注较少,随着代码规模增加,不合理的代码实现方法所带来的性能包袱逐渐凸显。因此,需要对原有代码实现进行优化,如修改不合理的访存顺序,使代码更易于被编译器优化等。 浮点数运算 浮点数运算是科学计算中开销最大的部分之一,特别是双精度除法,合理地设计实现浮点数运算环节可以显著提高程序的性能。 由于单