精华内容
下载资源
问答
  • 强化优化深化细化
    2021-07-29 07:59:29

    以下为《信息技术优化教学设计》的无排版文字预览,完整格式请下载

    下载前请仔细阅读文字预览以及下方图片预览。图片预览是什么样的,下载的文档就是什么样的。

    信息技术优化学科教学设计方案

    作业题目:

    结合您在本次培训中选定的课程,完成一篇教学设计方案,并作为终结性成果以作业形式提交。

    1.作业要求

    (1)必须是原创,抄袭将被判定为“不合格”。

    (2)要体现信息技术的应用;字数要求500字以上。

    (3)对于优秀作品,我们会进行整理并予以展示,请各位老师认真完成并学习其他学员提交的优秀作品。

    *请下载信息技术优化教学设计(模板)编写作业,为方便批改,建议先在文档编辑word软件里编辑好,再将内容复制到答题框提交,操作不要超过20分钟。

    2.成果模板

    (1)教学设计方案模板

    教学设计方案

    课题名称

    函数单调性

    姓名

    黄柏根

    工作单位

    休宁中学

    年级学科

    高一数学

    教材版本

    人教A版

    一、教学内容分析(简要说明课题来源、学习内容、知识结构图以及学习内容的重要性)

    “函数的单调性”是函数研究的重要内容之一,是在学生学习了函数概 念的基础上所研究的函数的第一个重要性质,它揭示了函数自变量与函数值之 间的数量变化规律,反映了函数图象的增、减性,体现了数形结合的数学思想, 是学生后面学习指数函数、对数函数、三角函数、不等式等重要知识的铺垫.函 数单调性是培养高一学生逻辑推理能力的重要素材,对提高学生的数学能力有 着重要影响

    二、教学目标(从学段课程标准中找到要求,并细化为本节课的具体要求,目标要明晰、具体、可操作,并说明本课题的重难点)

    培养学生由特殊到一般,由直观到抽象的抽象概括能力;

    通过函数单调性的证明,培养学生的逻辑推理能力;

    通过函数单调性的应用,培养学生的发散思维和创新精神。

    三、学习者特征分析(学生对预备知识的掌握了解情况,学生在新课的学习方法的掌握情况,如何设计预习)

    高一学生,他们的智力发展已到了形式运演阶段,具备了一定的抽象思维 能力和演绎推理能力,所以我在授课时注重引导、启发、研究和探讨以符合这 类学生的心理发展特点,从而促进思维能力的进一步发展。

    四、教学过程(设计本课的学习环节,明确各环节的子目标)

    (一)创设情境,形成概念;(二)发现问题,探求新知 用几何画析来指导学生观察自变量x(三)精讲范例,加深理解(四)强化训练,巩固双基 (五)小结归纳,拓展深化 (六)布置作业,提高升华.

    五、教学策略选择与信息技术融合的设计(针对学习流程,设计教与学方式的变革,配置学习资源和数字化工具,设计信息技术融合点)

    教师活动

    预设学生活动

    设计意图

    让学生观察多媒体演示一次函数和二次函数的图象,引导学生从左至右看 函数的图象是如何变换

    课上小组讨论归纳

    用 ppt 的形式把这个 问题提出来,让学生自己思考,讨论。

    用几何画板来指导学生观察自变量 x 的值由小到大 变化时,函数值 y 是如何变化的.

    体会图象法:函数图象是判断函数单调性的重要方法

    由直观到抽象的演变

    用多媒体课件探讨书本第29页的例1,引导学生看图,

    让学生自己先试图尝试证明单调性。开展自评和互评活动

    由被动接受知识到主观体验

    六、教学评价设计【创建量规,向学生展示他们将被如何评价(来自教师和小组其他成员的评价,也可以创建一个自我评价表,这样学生可以用它对自己的学习进行评价】

    1、是否认真专注 2、是否积极参与 3、是否独立思考 4、是否主动探索 5、能否自由表达 6、是否善于合作 7、是否敢于否定 8、是否兴趣浓厚

    七、教学板书(本节课的教学板书。如板书中含有特殊符号、图片等内容,为方便展示,可将板书以附件或图片形式上传。)

    1.课题引入

    2.单调性定义

    3.例题

    4.作业布置

    [全文已结束,注意以上仅为全文的文字预览,不包含图片和表格以及排版]

    以上为《信息技术优化教学设计》的无排版文字预览,完整格式请下载

    下载前请仔细阅读上面文字预览以及下方图片预览。图片预览是什么样的,下载的文档就是什么样的。

    更多相关内容
  • 国外街道设计导则会根据不同的用地情况再细化分类,如居住区林荫大道、高密度住宅街等,并更强调共享街道,提倡在居住和商业区交通量小的街道进行保障人车混行的安全设计(见表6)。 表5 中国城市街道设计导则中街道...

    写在前面:

    本文通过对国内外城市街道设计导则编制和学术研究情况进行全面系统的回顾与梳理,对中国城市街道设计导则编制的理念目标、体例和主要内容等方面进行特征分析。提出街道设计导则对城市道路交通规划的独特借鉴价值,并探索两者之间的良性互动策略。

    9b4a19954d0f16c79b3d42c87e61e5b0.png

    马强

    上海同济城市规划设计研究院有限公司 城市复兴规划设计所所长 高级工程师

    街道设计导则的帕累托改进价值

    城市道路交通规划与建设一直是城市建设的重点,然而以提升机动交通通行能力和通行速度为主要目标的发展理念也一直饱受争议。虽然花费巨资不断增加道路设施,但是城市道路交通的改善效果微乎其微,城市交通陷入拥堵—缓解—拥堵—再缓解的循环反复。随着城市交通需求的井喷,道路系统建设的成本和收益也越来越不对等,边际成本显著增加。在中国城市道路交通设施建设体系中,道路桥梁投资占比一直居于主导地位,2008年之前始终占城市交通基础设施投资的80%以上,2008年以后随着城市轨道交通的投资占比增加,道路桥梁的投资占比才有所下降,但也稳定在50%以上,每年7 000亿元左右

    根据高德地图《2019年度中国主要城市交通分析报告》等数据,中国机动车全天交通运行平均车速为39.88 km·h-1,保有量超400万辆的超(特)大城市高峰时段平均车速仅为24.88 km·h-1,公共汽车平均车速为15~20 km·h-1。由此可见,以增量提速为主要目标的城市道路交通建设实际上已经陷入边际效益递减的怪圈,投入巨资所获得的缓堵增速收效甚微。同时,“车本位”导向下城市道路的空间品质并不高,以机动交通需求为主导的路权配置使得城市道路也体现出“千城一面”。

    城市道路交通系统已经出现边际成本极高的临界状态,迫切需要改弦更张,改变提高机动交通通行能力的传统导向,突出道路整体空间品质,实现由量到质的转变。如同意大利经济学家帕累托(Vilfredo Pareto)提出的帕累托改进(Pareto Improvement)定律,“在没有使任何人境况变坏的前提下,使得至少一个人变得更好”,也就是说,“适度放弃增量提速的路径、寻求道路空间品质提升”的目标,至少使得城市道路交通状况在不会更坏的前提下获得适度的改善。街道设计导则的广泛编制恰恰体现了这种新的帕累托改进路径特征,城市道路空间在交通功能之外的复合空间价值正越来越引起各个城市的关注。

    城市高质量发展背景下

    街道设计导则的兴起

    近年来,城市道路作为一种重要的城市公共空间越来越引起人们的关注。它不再是以机动车通行为主的交通载体和空间,更是城市重要的功能组织和活动空间,原有的“人—车—路”经典互动关系在道路空间内具有更加丰富的表现和内涵。道路空间在以人为本、交通宁静、美观性、舒适性等各方面都体现出复合型的新特征。国内外主要城市展开了突出城市设计视角的街道空间设计,街道设计导则也作为一个专项研究类型应运而生。

    1

    以功能复合、彰显空间为特征的街道设计导则的发展

    自20世纪起,城镇化、工业化进程加快,机动车成为道路交通基础设施和土地使用政策等方面的主要关注和服务对象,以人为本理念在以扩大(机动车)交通量为主要目标的城市道路交通规划、设计、建设、管理过程中难以得到事实上的反映。机动车不但占据了道路路权的主要空间、使得行人与骑行者的空间受到严重挤压与威胁,而且也导致城市空间扩展模式出现明显的“车本位”特点,城市的可持续发展面临严重问题。

    在此背景下,进入21世纪以来除了继续坚持公交优先发展理念之外,对道路空间的重新思考与改善也成为城市规划界日益关注的核心问题。世界各国纷纷提出以人为本、空间共享的“街道重塑计划”,街道设计导则的编制开始蓬勃发展(见图1),更加体现人本特征的街道(street)越来越多地替代了道路(road),这不仅是名词上的变化,更是内涵上的完善和转变。在经历了萌芽(1960—2000年)、发展(2001—2015年)两个阶段后,国外街道设计导则逐步趋向成熟,成为主要城市道路规划设计与城市设计双向引导的标准规程,其中2004年伦敦发布的《街景设计指南》(Streetscape Guidance)被普遍认为是全球首部完善且富影响力的街道设计导则[1]。截至2020年,据本文不完全统计,全球已发布的街道设计导则达110本以上,其中美国编制数量最多,累计达47本;一些导则甚至已经超出城市层面,成为国家政府主导编制和发布的指导性文件,如德国、日本、印度、墨西哥等(见表1)。

    7d35d3c77db6472f65a25651fb9c2f94.png

    图1 全球街道设计导则发展历程

    资料来源:根据国内外街道设计导则内容绘制。

    表1 国外主要城市街道设计导则汇总

    b8213235a4ff91ec0cbb20fbae269971.png

    1)National Association of City Transportation Officials, NACTO。

    资料来源:根据网络资料不完全统计整理。

    2

    国外城市街道设计导则技术特点

    1)突显街道的公共空间设计内涵,突破传统以交通为出发点的工程技术规划范畴。

    例如伦敦、纽约、波士顿、悉尼的街道设计导则,均着重强调街道作为公共空间的作用与价值,呼吁通过路权优化体现“人本位”的街道空间。

    2)强调基于地方性和本土化的街道空间精细设计,而非形成普适性的规范标准。

    阿布扎比、日本、洛杉矶等地[2-4],都将本地的自然和文化特征充分融入街道空间设计之中。同时,对街道设计的实施及维护细节详加指导,如伦敦《街景设计指南》详细阐述了街道设计项目的设计团队人员组成、参与环节、设计流程、各环节的阶段节点等内容[5-6],《纽约街道设计手册》对街道从设计到实施整体流程的解读亦十分详尽。

    3)基于内涵和目标的扩展,倡导多部门、多学科的参与。

    导则编制不再仅限于交通部门主导,还加入了区域委员会、规划、环境等各部门的联合参与[7],所涉学科也扩展到建筑、景观、社会学等多个方向[8]。例如《纽约街道设计手册》就汇聚了9个部门、150余名专家参与其中。

    4)动态维护导则成果。

    与时俱进更新理念和技术并根据实践反馈结果回应新的问题挑战。例如伦敦自2004年发布《街景设计指南》以来已修订3次,《纽约街道设计手册》自2009年发布以来也已修订2次。

    从以上分析可以看出,国际视野下的城市道路规划与设计的导向体现出人本化、精细化、目标复合化、动态维护化四个突出特点,充分吸取了交通宁静、场所营造、景观设施等多学科理念和技术,不再片面追求道路交通设施扩容和机动车速度提高,道路不再是一种简单的交通概念,而是一个重要的空间载体,体现出街道空间对于城市公共开放空间组织和共享的重要作用。

    中国城市街道设计导则编制热潮

    1

    高质量发展导向下的街道设计导则实践进展

    城市设计作为高品质塑造城市公共空间全要素、有效推动和体现城市高质量发展的重要工具,越来越受到重视;城市道路空间作为城市公共空间的一种重要类型,也随之引发城市设计领域更多的关注和思考。《中共中央 国务院关于进一步加强城市规划建设管理工作的若干意见》(2016年2月发布)明确提出“原则上不再建设封闭住宅小区;树立‘窄马路、密路网’的城市道路布局理念”。2017年住房城乡建设部发布的《城市设计管理办法》,直接要求重要街道、街区开展城市设计,提升街道特色和活力。2021年自然资源部发布的《国土空间规划城市设计指南》(报批稿)进一步提出城市设计是国土空间高质量发展的重要支撑,要实现公共空间的系统建设,达成美好人居环境和宜人空间场所的积极塑造,并要求对重要街道的沿街立面、建筑退线、底层功能与形态等提出较为详细的导控要求。

    街道空间设计受到前所未有的重视,激发了中国城市街道设计导则的编制热潮。2012年香港《步行街道,乐活城市》是中国第一本非官方机构编制的街道设计导则。现共有21个省市(含港澳台地区)启动了街道设计导则编制(见表2),超(特)大城市中半数以上编制了导则,2016年《上海市街道设计导则》是中国第一部完整意义上的街道设计导则。

    表2 中国城市街道设计导则汇总

    8a36e6c7285ed4a5c9bfd55c6452b83e.png

    资料来源:根据网络资料不完全统计整理。

    2

    研究综述

    中国城市街道设计导则研究起始于2004年,2016年起显著增多,相关论文数量趋势见图2。论文主题主要包括现状反思、案例研究、发展趋势、方法提议、项目实践等五个方向。随着街道设计导则的探索深化,项目实践类型的论文显著增加(见图3)。

    4396d9474133c3e0f0fece320bce9d7a.png

    图2 大巴黎地区城市物流组织示意

    资料来源:根据中国知网搜索统计整理。

    3d48c7d63afd614fbac95a08a8791a86.png

    图3 街道设计导则相关论文研究类型变化

    资料来源:根据中国知网搜索统计整理。

    总体而言,学术界认为以人为本是国外街道设计导则的关注重点[9],国内也已认识到人本主义的内涵[10],且在现阶段均贯彻了小街区、密路网的道路布局理念[9];中外城市街道设计导则在编制主体、理念、原则方面呈现较多共性[11],国外导则在统筹管理机制、完整街道的设计理念、精细化设计、重视街道公共空间属性等方面值得借鉴[12]。中国城市街道设计导则编制存在的主要问题包括上位规则失当导致的导则与规范脱节[13],未被纳入法定规划系统、内容编制混乱、部门权责不清[14],在建筑退界等诸多细节方面与现行国家标准之间存在矛盾[15]。同时,有研究对国内城市街道设计导则的未来发展做出了精细化、品质化、标准化、全方位指引等趋势判断[16]

    街道设计导则对

    城市道路的有益影响

    1

    拓展城市道路的设计目标维度

    国内外街道设计导则多强调对街道的重新界定和认知,突出功能复合和街道的空间属性。其中,国内街道设计导则的目标侧重对物质环境空间的可见提升,安全、绿色、活力、智慧是目标体系中出现最多的词汇;而国外街道设计导则的关注点更倾向于包容性、经济性以及可持续性等更为综合的目标(见表3和表4)。

    表3 国内外城市街道设计导则目标

    3eee3c8d82a748a277379c5df45f12ba.png

    资料来源:根据各地街道设计导则整理。

    表4 国内外城市街道设计导则目标词频对比

    dc62975d6aafb0b3a2df5af23e5d21e7.png

    资料来源:根据各地街道设计导则整理。

    道路向街道的转变不仅是名称的变化,更重要的是赋予道路交通功能之外更多的属性。城市道路规划与设计的未来应当体现更多的规划设计目标和设计元素,不但要重视平面设计元素的人本化,更应凸显街道空间的立体空间特征,从城市公共空间、共享空间的角度提升道路规划设计的复合追求。街道设计导则一般都包含对交通功能要素、景观环境要素、附属功能设施、沿街建筑界面等四个方面的复合引导,这都要求未来的城市道路空间要素涵盖维度的扩展。

    2

    丰富城市道路的分级分类

    无论是《城市道路交通规划设计规范》(GB 50220—95)提出的快速路、主干路、次干路、支路的四级标准,还是《城市综合交通体系规划标准》(GB/T 51328—2018)提出的三大类(干线道路、支线道路、集散道路)、四中类、八小类标准,其核心都是以机动交通通行效率为目标划定城市道路分级分类。而国内外的街道设计导则显然不是完全以这一目标作为出发点进行分类研究,分类的依据更为复合。国内街道设计导则通常依据街道两侧的用地功能、风貌特征以及交通功能等将街道分为4~6类(见表5),常见的包括生活类、商业类、景观类、交通类以及综合类,一些导则增加了特殊的类型,如办公类、工业类等。国外街道设计导则会根据不同的用地情况再细化分类,如居住区林荫大道、高密度住宅街等,并更强调共享街道,提倡在居住和商业区交通量小的街道进行保障人车混行的安全设计(见表6)。

    表5 中国城市街道设计导则中街道分类

    d74987df0af35c19785cfc053bb52f5e.png

    1)《北京城市副中心城市设计导则——街道空间设计指引》;2)《北京街道更新治理城市设计导则》;3)《上海市街道设计导则》;4)《深圳市福田区街道设计导则》;5)《深圳市罗湖区完整街道设计导则》;6)《成都市公园城市街道一体化设计导则》;7)《成都市“小街区”规划建设技术导则》;8)《成都市中心城区特色风貌街道规划建设技术导则》;9)《南京市街道设计导则(试行)》;10)《青岛市街道设计导则》;11)《玉溪市中心城区精致街道设计导则》。

    表6 国外街道设计导则中街道分类

    10930b90a41e2e4246788b2a3cf036b0.png

    1)Boston Complete Streets Design Guidelines; 2)San Francisco Better Streets Plan; 3)Streetscape Design Guidelines; 4)Urban Street Design Guide; 5)Streetscape Guidance; 6)Edinburgh Design Guidance。

    街道设计导则研究语境中的分类看似较为随意、多样,但其中的分类依据导向值得重视,即因地制宜地对不同特征的街道进行类型划分,从而更好地突出不同环境中街道的特征和多义性。可见,街道设计导则更突出街道的分类特征,而较为淡化街道的分级。相比之下,城市道路规范更突出道路的分级,而对不同场景下道路特征的分类往往考虑不多。

    同时,中国现有的街道设计导则分类中出现了对街道概念的理解误差,设计导控覆盖到了快速路、主干路等,甚至出现了交通型街道、工业类街道等分类。实际上,从“人本位”的编制理念出发,不是所有城市道路都适合纳入街道的范畴,只有功能复合度较高、人流活动较频繁、功能性强、公共空间氛围突出的道路才宜纳入街道设计导则。

    此外,关于道路分级分类的规范化与否见仁见智,多数意见建议将其规范统一化,也有意见认为保留一定因地制宜的弹性为宜。但问题的关键是,是否需要基于更加多样、均衡的出发点建立城市道路的分级分类标准,而不是以“车本位”的容量需求来划分。街道设计导则无疑具有一定的探索意义和价值,应当将这方面的研究继续加以深化。

    3

    完善城市道路空间管控要素

    街道设计导则从诞生之日起,就体现出不以工程标准为核心的研究方法,而是凸显了更多的城市设计研究手法,更加突出多元用户需求导向下的空间功能分析。中国现有街道设计导则的导控要素大致分为交通功能、景观环境、附属功能设施、沿街建筑界面四个类别。街道控制要素词频分析结果显示(见图4),照明设施、广告招牌、人行道宽度、非机动车道、公共汽车站、自行车停车设施、机动车道、交叉口等受到较高关注,国外街道设计导则的空间要素控制则更加丰富(见表7)。

    aba302dc9e9e912205e522c6993bddb7.png

    图4 中国城市街道控制要素词频分布

    资料来源:根据各地街道设计导则整理。

    表7 国外城市街道设计导则中空间要素

    cbaf17ab1ebc079b43b1e788c7d4ad6e.png

    资料来源:根据各地街道设计导则整理。

    4

    与已有标准的差异与协调

    本文梳理了与街道设计相关的12本国家标准和10本行业标准(见表8和表9)。通过对街道设计导则具体内容的研究发现,各地在导则中都不约而同出现了相当多与既有标准不一致的地方(如路缘石转弯半径、道路红线切角、机动车道宽度等)。这些不能以对错论之,相反很多研究是基于细节尺度推敲下的产物,对现行标准有一定的参考借鉴价值,也促使我们思考现行标准中过度的“车本位”和“一刀切”倾向。

    表8 国家标准中与街道设计相关的主要内容摘录

    21bda7071d14f907a58b9a62dd16175a.png

    表9 行业标准中与街道设计相关的主要内容摘录

    bf7fc3b9f073692aed235f21086617b1.png

    1)路缘石转弯半径取值。

    既有标准中转弯半径一般基于右转标准车的轴距尺寸和设计车速来确定,通常取值20~30 m(见表10),很多地方还采用交叉口展宽的办法进一步拓宽路缘石转弯半径以方便机动车通行。而很多城市在实践中采用宁静交通的理念,其街道设计导则普遍降低了路缘石转弯半径最小值(见表11和表12),例如北京、上海最小值为5 m,伦敦最小值为3 m,深圳市福田区无右转交通流的交叉口路缘石转弯半径最小值仅为0.5 m,近乎直角。有的城市为了进一步落实宁静交通理念,甚至采用缩短行人过街距离的方式对交叉口进行缩窄,例如转角路缘石加宽(Curb Extension)、设置过街安全岛(Pinch Points)、车道窄化(Chokers)。

    表10 国家标准中交叉口路缘石转弯最小半径(单位:m)

    97111071ad7c6fe1cc075056e47523bc.png

    资料来源:《城市道路交叉口规划规范》(GB 50647—2011)。

    表11 国内外城市街道设计导则路缘石转弯半径最小值对比

    2bfd831d67d683dffdb8b6120410162a.png

    表12 深圳市福田区路缘石转弯半径推荐值(单位:m)

    df27d3749ed35da4116bf7a61351b06d.png

    资料来源:《深圳市福田区街道设计导则》。

    这种突破规范值的情形虽然不合规,但恰恰反映出城市道路设计理念认知的差异。虽然这不能得到现行标准的支持,但代表了以人为本的城市可持续发展方向,值得进一步探索成熟经验。

    2)道路红线切角取值。

    从保证机动车视距三角形视线安全角度出发,进行道路红线切角的控制(见表13)。部分城市在街道设计导则中对交叉口进行宁静化处理,并根据降低的交叉口车速相应降低安全视距,所以道路红线切角取值比国家标准小(见表14)。这种处理方式在国外街道设计导则中应用较为普遍。机动车通过无交通管制的交叉口时限速至50%左右[17],可以显著减小道路红线切角的尺度,减少交叉口占地面积,将更多的公共空间给予行人,对于紧凑城市空间、增加土地利用效率也有一定作用。

    表13 国家标准中道路红线切角长度(单位:m)

    66974bde541389573fada409851a7a08.png

    资料来源:《城市道路交叉口规划规范》(GB 50647—2011)。

    表14 部分城市道路红线切角建议(单位:m)

    597f2b5835203a60a96a40fab715f25e.png

    资料来源:《南京市街道设计导则(试行)》《青岛市街道设计导则》。

    3)机动车道宽度取值。

    目前对中国城市机动车道宽度划定过宽的声音不绝于耳。多数街道设计导则建议缩减机动车道宽度,以达到降低机动车速度、提升交通安全的目的。例如《武汉市街道全要素规划设计导则》在保障设计车速安全性的前提下,对各级道路提出了基于国家标准的压缩宽度(见表15),车速小于40 km·h-1的次干路和支路,大型车道和小型车道分别从3.5 m和3.25 m压缩至3.0 m。

    表15 武汉市街道设计导则对车道宽度的说明(单位:m)

    1d13dc0f92fe3ff0826e76c5ad15299f.png

    资料来源:《武汉市街道全要素规划设计导则》。

    4)建筑后退红线距离与街道高宽比。

    在中国,建筑后退红线距离是一项强制性指标,后退值的确定主要出于市政管线接驳、防灾救灾通道、城市道路拓宽预留、街道日照、城市景观营造等角度的考虑[18],整体数值偏大,极易造成城市街道空间尺度失当。而街道设计导则从街道功能需求和整体界面宽度对步行体验影响的角度考虑建筑退界,整体数值相对较小。例如《南京市街道设计导则(试行)》提出支路界面宽度不宜大于30 m,次干路界面宽度控制在40 m以内,其建筑后退红线距离,尤其是道路两侧建筑高度在24 m以上时,明显小于《江苏省城市规划管理技术规定》中的取值(见表16和表17)。

    表16 江苏省建筑物后退道路红线的最小距离(单位:m)

    03416dabe1fbc09f169d00a2f70d5c86.png

    资料来源:《江苏省城市规划管理技术规定》。

    表17 南京市建筑物后退道路红线的最小距离(单位:m)

    7898b97a5c4a9a9a409f3c07bedfafb0.png

    资料来源:《南京市街道设计导则(试行)》。

    值得一提的是,街道宽高比控制是街道空间塑造的重要城市设计手段,目前各地街道设计导则中尚未广泛应用。日本学者芦原义信曾指出1~2的宽高比区间较为适宜[19],但该宽高比仅适用近人尺度(建筑高度≤30 m)的街道,在高层、超高层建筑的街道中,直接套用该比例退线会导致街道过宽[20]

    以上四项指标是街道设计导则与现行标准不一致较为典型的方面。虽然街道设计导则提出的相应指标相对既有标准有一定程度的突破,但背后反映出“车本位”向“人本位”转变的呼声和期望。街道设计导则倡导的小转弯半径、窄退界、小尺度交叉口、缩窄车行道、行人过街优先等设计导向的合理性已经得到普遍认可,未来应该影响规范标准做出响应和优化,并进一步形成街道设计导则与规范标准之间的良性反馈机制。

    街道设计导则与

    城市道路系统优化良性互动

    1

    进一步凸显街道设计导则的

    空间研究价值,而不是

    另编一套技术标准

    1)加强街道设计导则对街道空间的统筹与整合职能。

    以街道为对象,而非将所有道路都纳入街道空间设计的范畴。街道空间是与国土空间、交通工程、城市设计和城建管理四个维度均有关联的复合城市公共空间(见图5),街道设计导则是对街道中涉及的各类空间、各种功能、各管理部门起到统筹和整合作用的重要工具。

    28f78076d85143e8a4c30fe4f8428a5f.png

    图5 国土空间、交通工程、城市设计和城建管理四维度下的复合型空间

    资料来源:根据国内外街道设计导则实例内容绘制。

    2)突出街道设计导则的研究导向而非另起炉灶推动规范化、标准化。

    街道设计导则提出的技术指标首先要基于规范标准,其次是基于场景需求下的精准调校,不一定具有普适性,但应突出探索价值。其内容应偏重街道空间设计内涵与逻辑的解读解释,注重街道中人的体验和环境特征的体现,细致推敲公共空间和开放空间以确定各种空间关系,而不是简单地设定指标进行标准的套用。其呈现可采用街道设计指南(Design Guide/Design Standard)等白皮书形式,侧重空间要素研究,内容更具有弹性,利于不断优化完善,而不宜变成另一种技术规范;对街道设计导则中需要进一步研究的若干重点指标,可用建议值、参照值等方式反馈至相应规范标准中。

    3)将街道设计导则作为城市设计中的重要组成部分和研究领域。

    街道设计导则应不断突出城市设计导向、场所营造特色,并成为城市设计的重要组成部分。城市设计在国土空间规划体系下分为总体规划中的城市设计、详细规划中的城市设计、专项规划中的城市设计三类,每一类中均有对街道设计的要求(见图6)。因此,下一阶段街道设计导则的编制应该着重增加对接三类规划的具体指引,形成与国土空间规划全过程衔接的完整体系。例如,街道设计的理念、目标、控制要求等可以纳入总体规划层面的城市设计内容;对于街道的管控型空间指标与要素的内容,可以纳入详细规划层面的城市设计。此外,还可将街道设计导则按空间尺度纵向分类衔接,区域、城市、片区等不同层级关注不同的内容,做到纵向有序衔接与传导完善。

    f68593815b9030c3a66f3f99ce04c4a8.png

    图6 街道设计导则与国土空间规划全过程衔接

    资料来源:根据《国土空间规划城市设计指南(报批稿)》及国内外街道设计导则实例整理。

    4)采用“普适+特色”的街道设计指南指引体系。

    在街道设计导则编制已经形成基本成熟的体例与框架的情况下,建议逐步建立“普适+特色”双重属性的街道设计指南指引体系。明确规范化和普适性的内容组成,同时充分考虑在地性,鼓励对地域特色的设计目标进行拓展,并增加地方特色性的引导内容,以提升街道设计导则的地方适应性。例如,从气候适应性角度,南方炎热城市可更加关注街道的夏季通风、遮阴,北方寒冷城市可更加关注街道的防寒空间;从地区功能角度,科教区和居住区可重点强化街道的宁静化设计,而文创区或文旅街区则可以重点关注街道的文化特色展现等。街道设计导则的内容还可在整体理念的指引下,进行专业化的深化、细化、补充、扩展,并形成独立的设计专项,形成类似美国国家城市交通官员协会基于《城市街道设计指南》(Urban Street Design Guide)陆续出版的《为儿童设计街道》(Designing Streets for Kids)、《公共交通街道设计指南》(Transit Street Design Guide)、《城市自行车道设计指南》(Urban Bikeway Design Guide)、《不要忽视交叉口》(Don't Give up at the Intersection)等成果(见图7)。

    fb645adc35ab2bd07c77a55ce5f1a3d6.png

    图7 NACTO发布的街道设计导则与指南系列丛书

    资料来源:美国国家城市交通官员协会官网(https://nacto.org/)。

    2

    城市道路交通规划充分吸纳

    街道设计导则的研究价值,

    形成动态反馈与优化机制

    街道设计导则在理念导向、用户需求、空间要素、尺度优化、特色营造等方面都具备突出的特色和研究弹性,既基于交通功能又超越交通功能,对城市道路系统具有重要的参考价值,可视作城市道路交通规划设计中某些参数标定的前置研究环节。城市道路交通规划设计也应在街道设计导则的研究基础上,适度加强除交通内容之外的规划导控要素,加强规范标准对空间要素的涵盖范围,并借鉴相关经验提高控制刚性之余的导控弹性,更加关注以人为本理念的反映与落实。同时,可以将街道设计导则作为规范标准纵向传导的衔接环节,使得规范标准适度留有与各地因地制宜的弹性与接口,并充分衔接地方标准。

    写在最后

    城市街道设计导则编制工作在中国蓬勃发展,正不断凝练目标共识,形成较为稳定的体系结构。通过各地的积极探索,街道设计导则这一空间设计技术工具的研究性、灵活性、扩展性得到了充分展现。未来应加快理顺导则的传导反馈机制,既体现从上至下相关规范标准的纵向传导,又横向衔接地方相关技术管理标准。应充分发挥街道设计导则在优化城市道路空间上的特殊价值和作用,逐步提升城市街道空间成为以人为本的场所空间、功能复合的交流空间、场所营造的集成空间,使街道空间成为城市公共空间的重要组成部分。

    注释:

    ① 根据住房和城乡建设部历年《城市建设统计年鉴》中交通设施建设固定资产投资数据整理。

    ② 伦敦《街景设计指南》中路权原则强调步行优先,在具体设计中体现为通过减小转弯半径、减少路内停车位等一系列措施保障步行、骑行的空间和安全性;《纽约街道设计手册》中将公共活动作为独立章节,更强调街道作为公共场所的社会功能属性;《波士顿完整街道设计导则》强调增添活力的街道设计要素;《悉尼街道导则》对于人行道区域空间组织以及街道家具设置加入了更多提升公共空间体验的设计与经济效益的考虑。

    ③《城市设计管理办法》第十一条明确指出“重要街道、街区开展城市设计,应当根据居民生活和城市公共活动需要,统筹交通组织,合理布置交通设施、市政设施、街道家具,拓展步行活动和绿化空间,提升街道特色和活力。”

    ④ 经常出现在欧美城市中的一种缩短自行车和行人过街距离的交通安全岛。

    ⑤《为儿童设计街道》强调对重点目的地(例如学校)附近的街道设计应更加关注儿童在街道中的需求;《公共交通街道设计指南》主要针对城市街道中公共交通设施的发展提出设计指引,使得城市街道的设计与建设达到公交优先、提升公共交通服务质量等发展目标;《城市自行车道设计指南》是专门为街道中非机动车道设计的导则;《不要忽视交叉口》是解决交叉口空间中机动车流与非机动车流、人流的组织问题,提升交叉口空间非机动交通安全性的专项补充导则。

    参考文献(上滑查看全部):

    [1] Mayor of London. Streetscape Guidance 2017 Revision[R]. London: Transport for London, 2019.

    [2] 兰潇,李雯. 以多元需求平衡为导向的街道设计:以《阿布扎比街道设计手册》为例[J]. 城市交通,2014,12(2):36-49.

    Lan Xiao, Li Wen. Street Design Based on Multi-Demand Balancing: A Case Study of Urban Street Design Manual in Abu Dhabi[J]. Urban Transport of China, 2014, 12(2): 36-49.

    [3] 国土交通省都市局,国土交通省道路局. ストリートデザイン ガイドライン[R]. 日本:国土交通省,2020.

    [4] City Planning Commission. Downtown Design Guide City of Los Angeles[R]. Los Angeles: City Planning Commission, 2017.

    [5] Michael R. Gallagher,王紫瑜. 追求精细化的街道设计:《伦敦街道设计导则》解读[J]. 城市交通,2015,13(4):56-64.

    Michael R. Gallagher, Wang Ziyu. To Pursue Delicate Street Design-Discussion on Streetscape Guidance: A Guide to Better London Streets[J]. Urban Transport of China, 2015, 13(4): 56-64.

    [6] 戴冬晖,许霖峰,王耀武. 基于“全过程”管控的街道景观建设实践:伦敦街道景观指南的经验与启示[J]. 中国园林,2019,35(4):86-91.

    Dai Donghui, Xu Linfeng, Wang Yaowu. The Practice of Streetscape Construction Based on the Whole Process Control—The Experience and Enlightenment of Streetscape Guidance in London[J]. Chinese Landscape Architecture, 2019, 35(4): 86-91.

    [7] 方家,刘珺,王德,等. 对6个国外城市街道规划设计导则的解析:“如何规划步行友好的城市街道开放空间?”[J]. 风景园林,2018,25(11):33-39.

    Fang Jia, Liu Jun, Wang De, et al. Analysis of Six Guidelines for Street Planning and Design in Foreign Cities: How to Plan Pedestrian-Friendly Street Open Space?[J]. Landscape Architecture, 2018, 25(11): 33-39.

    [8] 姜洋,王悦,解建华,等. 回归以人为本的街道:世界城市街道设计导则最新发展动态及对中国城市的启示[J]. 国际城市规划,2012,27(5):65-72.

    Jiang Yang, Wang Yue, Xie Jianhua, et al. Return to Human-Oriented Streets: The New Trend of Street Design Manual Development in the World Cities and Implications for Chinese Cities[J]. Urban Planning International, 2012, 27(5): 65-72.

    [9] 唐燕,李婧. 面向实施的街道设计导则编制研究:以北京《朝阳区街道设计导则》为例[C]//中国城市规划学会. 共享与品质:2018中国城市规划年会论文集. 北京:中国建筑工业出版社,2018:236-249.

    [10] 卓玙琪. 街道设计指南与导则编制制度研究[D]. 广州:华南理工大学,2019.

    [11] 怀露,李敏稚. 中外典型街道设计导则编制动态和比较研究:以广州市天河区五山路街道改造设计为例[C]//中国城市规划学会. 活力城乡 美好人居:2019中国城市规划年会论文集. 北京:中国建筑工业出版社,2019:1377-1389.

    [12] 徐淳. 国际典型街道设计导则解读及其经验启示[J]. 江苏城市规划,2018(5):44-47.

    [13] 刘卫东,贺文霞. 以“行人优先”为导向的街道设计:《武汉光谷中心城街道设计指引》的思考[C]//中国城市规划学会. 持续发展 理性规划:2017中国城市规划年会论文集. 北京:中国建筑工业出版社,2017:24-32.

    [14] 于睿智. 街道设计导则编制方法探索:以伦敦、纽约、阿布扎比、上海为例[C]//中国城市规划学会. 共享与品质:2018中国城市规划年会论文集. 北京:中国建筑工业出版社,2018:1017-1025.

    [15] 葛岩,祁艳,唐雯,等. 街道复兴:需求导向的街道设计导则编制实践与思考[J]. 城市规划学刊,2019(2):90-98.

    Ge Yan, Qi Yan, Tang Wen, et al. Street Renaissance: Practice and Thoughts on Need-Based Street Design Guideline[J]. Urban Planning Forum, 2019(2): 90-98.

    [16] 江剑英. 从街道规划设计到城市规划设计:基于《横琴新区街道设计导则》编制的探讨[C]//中国城市规划学会. 活力城乡 美好人居:2019中国城市规划年会论文集. 北京:中国建筑工业出版社,2019:260-273.

    [17] 周嗣恩. 城市道路平面交叉口红线规划的若干思考[J]. 城市交通,2019,17(5):39-46.

    Zhou Sien. Thoughts on the Red Line Planning of Urban Intersections[J]. Urban Transport of China, 2019, 17(5): 39-46.

    [18] 朱郑炜. 进退之惑:对城市规划管理技术规定的建筑退线控制的思考[J]. 规划师,2012,28(12):109-111.

    Zhu Zhengwei. Forward or Backward: Setback Line Issue in Urban Planning Management Regulation[J]. Planners, 2012, 28(12): 109-111.

    [19] 芦原义信. 街道的美学[M]. 尹培桐,译. 武汉:华中理工大学出版社,1989.

    [20] 方智果,宋昆,叶青. 芦原义信街道宽高比理论之再思考:基于“近人尺度”视角的街道空间研究[J]. 新建筑,2014(5):136-140.

    Fang Zhiguo, Song Kun, Ye Qing. Reflection of Yoshinobu Ashihara's Research About the Proportion of Street Width to Building Height: A Study of Street Space Based on Human Scale[J]. New Architecture, 2014(5): 136-140.

    《城市交通》2021年第5期刊载文章

    作者:马强,韦笑,任冠南

    欢迎加入智能交通群!加微信号automan332,标注”加群“。

    展开全文
  • 导读:对于工程经验比较丰富的同学,并发应该也并不是陌生的概念了,但是每个人所理解的并发问题,却又往往并不统一,本文系统梳理了百度C++工程师在进行并发优化时所作的工作。全文15706字,预计阅读时间24分钟。一...

    图片

    导读:对于工程经验比较丰富的同学,并发应该也并不是陌生的概念了,但是每个人所理解的并发问题,却又往往并不统一,本文系统梳理了百度C++工程师在进行并发优化时所作的工作。

    全文15706字,预计阅读时间24分钟。

    一、背景

    简单回顾一下,一个程序的性能构成要件大概有三个,即算法复杂度、IO开销和并发能力。由于现代计算机体系结构复杂化,造成很多时候,工程师的性能优化会更集中在算法复杂度之外的另外两个方向上,即IO和并发,在之前的《百度C++工程师的那些极限优化(内存篇)》中,我们介绍了百度C++工程师工程师为了优化性能,从内存IO角度出发所做的一些优化案例。

    图片

    这次我们就再来聊一聊另外一个性能优化的方向,也就是所谓的并发优化。和IO方向类似,对于工程经验比较丰富的同学,并发应该也并不是陌生的概念了,但是每个人所理解的并发问题,却又往往并不统一。所以下面我们先回到一个更根本的问题,重新梳理一下所谓的并发优化。

    二、为什么我们需要并发?

    是的,这个问题可能有些跳跃,但是在自然地进展到如何处理各种并发问题之前,我们确实需要先停下来,回想一下为什么我们需要并发?

    这时第一个会冒出来的概念可能会是大规模,例如我们要设计大规模互联网应用,大规模机器学习系统。可是我们仔细思考一下,无论使用了那种程度的并发设计,这样的规模化系统背后,都需要成百上千的实例来支撑。也就是,如果一个设计(尤其是无状态计算服务设计)已经可以支持某种小规模业务。那么当规模扩大时,很可能手段并不是提升某个业务单元的处理能力,而是增加更多业务单元,并解决可能遇到的分布式问题。

    其实真正让并发编程变得有价值的背景,更多是业务单元本身的处理能力无法满足需求,例如一次请求处理时间过久,业务精细化导致复杂度积累提升等等问题。那么又是什么导致了近些年来,业务单元处理能力问题不足的问题呈现更加突出的趋势?

    可能下面这个统计会很说明问题:

    (https://www.karlrupp.net/2015/06/40-years-of-microprocessor-trend-data/)

    图片

    上图从一个长线角度,统计了CPU的核心指标参数趋势。从其中的晶体管数目趋势可以看出,虽然可能逐渐艰难,但是摩尔定律依然尚能维持。然而近十多年,出于控制功耗等因素的考虑,CPU的主频增长基本已经停滞,持续增加的晶体管转而用来构建了更多的核心。

    从CPU厂商角度来看,单片处理器所能提供的性能还是保持了持续提升的,但是单线程的性能增长已经显著放缓。从工程师角度来看,最大的变化是硬件红利不再能透明地转化成程序的性能提升了。随时代进步,更精准的算法,更复杂的计算需求,都在对的计算性能提出持续提升的要求。早些年,这些算力的增长需求大部分还可以通过处理器更新换代来自然解决,可是随着主频增长停滞,如果无法利用多核心来加速,程序的处理性能就会随主频一同面临增长停滞的问题。因此近些年来,是否能够充分利用多核心计算,也越来越成为高性能程序的一个标签,也只有具备了充分的多核心利用能力,才能随新型硬件演进,继续表现出指数级的性能提升。而伴随多核心多线程程序设计的普及,如何处理好程序的并发也逐渐成了工程师的一项必要技能。

    图片

    上图描述了并发加速的基本原理,首先是对原始算法的单一执行块拆分成多个能够同时运行的子任务,并设计好子任务间的协同。之后利用底层的并行执行部件能力,将多个子任务在时间上真正重叠起来,达到真正提升处理速度的目的。

    需要注意的是还有一条从下而上的反向剪头,主要表达了,为了正确高效地利用并行执行部件,往往会反向指导上层的并发设计,例如正确地数据对齐,合理的临界区实现等。虽然加速看似完全是由底层并行执行部件的能力所带来的,程序设计上只需要做到子任务拆分即可。但是现阶段,执行部件对上层还无法达到透明的程度,导致这条反向依赖对于最终的正确性和性能依然至关重要。既了解算法,又理解底层设计,并结合起来实现合理的并发改造,也就成为了工程师的一项重要技能。

    三、单线程中的并行执行

    提到并行执行部件,大家的第一个印象往往时多核心多线程技术。不过在进入到多线程之前,我们先来看看,即使是单线程的程序设计中,依然需要关注的那些并行执行能力。回过头再仔细看前文的处理器趋势图其实可以发现,虽然近年主频不再增长,甚至稳中有降,但是单线程处理性能其实还是有细微的提升的。这其实意味着,在单位时钟周期上,单核心的计算能力依然在提升,而这种提升,很大程度上就得益于单核心单线程内的细粒度并行执行能力。

    3.1 SIMD

    其中一个重要的细粒度并行能力就是SIMD(Single Instruction Multiple Data),也就是多个执行单元,同时对多个数据应用相同指令进行计算的模式。在经典分类上,一般单核心CPU被归入SISD(Single Instruction Single Data),而多核心CPU被归入MIMD(Mingle Instruction Multiple D ata),而GPU才被归入SIMD的范畴。但是现代CPU上,除了多核心的MIMD基础模型,也同时附带了细粒度SIMD计算能力。

    图片

    图片

    上图是Intel关于SIMD指令的一个示意图,通过增加更大位宽的寄存器实现在一个寄存器中,“压缩”保存多个较小位宽数据的能力。再通过增加特殊的运算指令,对寄存器中的每个小位宽的数据元素,批量完成某种相同的计算操作,例如图示中最典型的对位相加运算。以这个对位相加操作为例,CPU只需要增大寄存器,内存传输和计算部件位宽,针对这个特殊的应用场景,就提升到了8倍的计算性能。相比将核心数通用地提升到8倍大小,这种方式付出的成本是非常少的,指令流水线系统,缓存系统都做到了复用。

    从CPU发展的视角来看,为了能够在单位周期内处理更多数据,增加核心数的MIMD强化是最直观的实现路径。但是增加一套核心,就意味增加一套 完整的指令部件、流水线部件和缓存部件,而且实际应用时,还要考虑额外的核心间数据分散和聚合的传输和同步开销。一方面高昂的部件需求, 导致完整的核心扩展成本过高,另一方面,多核心间传输和同步的开销针对小数据集场景额外消耗过大,还会进一步限制应用范围。为了最大限度利用好有限的晶体管,现代CPU在塑造更多核心的同时,也在另一个维度上扩展单核心的处理和计算位宽,从而实现提升理论计算性能(核心数 * 数据宽度)的目的。

    不过提起CPU上的SIMD指令支持,有一个绕不开的话题就是和GPU的对比。CPU上早期SIMD指令集(MMX)的诞生背景,和GPU的功能定位就十分类似,专注于加速图像相关算法,近些年又随着神经网络计算的兴起,转向通用矩阵类计算加速。但是由于GPU在设计基础上就以面向密集可重复计算负载设计,指令部件、流水线部件和缓存部件等可以远比CPU简洁,也因此更容易在量级上进行扩展。这就导致,当计算密度足够大,数据的传输和同步开销被足够冲淡的情况下(这也是典型神经网络计算的的特性),CPU仅作为控制流进行指挥,而数据批量传输到GPU协同执行反而 会更简单高效。

    由于Intel自身对SIMD指令集的宣传,也集中围绕神经网络类计算来展开,而在当前工程实践经验上,主流的密集计算又以GPU实现为主。这就导致了不少CPU上SIMD指令集无用论应运而生,尤其是近两年Intel在AVX512初代型号上的降频事件,进一步强化了『CPU就应该做好CPU该做的事情』这一论调。但是单单从这一的视角来认识CPU上的SIMD指令又未免有些片面,容易忽视掉一些真正有意义的CPU上SIMD应用场景。

    图片

    对于一段程序来讲,如果将每读取单位数据,对应的纯计算复杂度大小定义为计算密度,而将算法在不同数据单元上执行的计算流的相同程度定义为模式重复度,那么可以以此将程序划分为4个象限。在大密度可重复的计算负载(典型的重型神经网络计算),和显著小密度和非重复计算负载(例如HTML树状解析)场景下,业界在CPU和GPU的选取上其实是有相对明确“最优解”的。不过对于过渡地带,计算的重复特征没有那么强,  或者运算密度没有那么大的场景下,双方的弱点都会被进一步放大。即便是规整可重复的计算负载,随着计算本身强度减小,传输和启动成本逐渐显著。另一方面,即便是不太规整可重复的计算负载,随着计算负荷加大,核心数不足也会逐渐成为瓶颈。这时候,引入SIMD的CPU和引入SIMT 的GPU间如何选择和使用,就形成了没有那么明确,见仁见智的权衡空间。

    即使排除了重型神经网络,从程序的一般特性而言,具有一定规模的重复特性也是一种普遍现象。例如从概念上讲,程序中的循环段落,都或多或少意味着批量/重复的计算负载。尽管因为掺杂着分支控制,导致重复得没有那么纯粹,但这种一定规模的细粒度重复,正是CPU上SIMD发挥独特价值的地方。例如最常见的SIMD优化其实就是memcpy,现代的memcpy实现会探测CPU所能支持的SIMD指令位宽,并尽力使用来加速内存传输。另一方面现代编译器也会利用SIMD指令来是优化对象拷贝,进行简单循环向量化等方式来进行加速。类似这样的一类优化方法偏『自动透明』,也是默默支撑着主频不变情况下,性能稍有上升的重要推手。

    可惜这类简单的自动优化能做到的事情还相当有限,为了能够充分利用CPU上的SIMD加速,现阶段还非常依赖程序层进行主动算法适应性改造,有 目的地使用,换言之,就是主动实施这种单线程内的并发改造。一个无法自动优化的例子就是《内存篇》中提到的字符串切分的优化,现阶段通过编译器分析还很难从循环 + 判断分支提取出数据并行pattern并转换成SIMD化的match&mask动作。而更为显著的是近年来一批针对SIMD指令重新设计的算法,例如Swiss Table哈希表,simdjson解析库,base64编解码库等,在各自的领域都带来了倍数级的提升,而这一类算法适应性改造,就已经完全脱离了自动透明所能触及的范围。可以预知近些年,尤其随着先进工艺下AVX512降频问题的逐渐解决,还会/也需要涌现出更多的传统基础算法的SIMD改造。而熟练运用SIMD指令优化技术,也将成为C++工程师的一项必要技能。

    3.2 OoOE

    另一个重要的单线程内并行能力就是乱序执行OoOE(Out of Order Execution)。经典教科书上的CPU流水线机制一般描述如下(经典5级RISC流水线)。

    图片

    指令简化表达为取指/译码/计算/访存/写回环节,当执行环节遇到数据依赖,以及缓存未命中等场景,就会导致整体停顿的产生。其中MEM环节的影响尤其显著,主要也是因为缓存层次的深化和多核心的共享现象,带来单次访存所需周期数参差不齐的现象越来越严重。上图中的流水线在多层缓存下的表现,可能更像下图所示:

    图片

    为了减轻停顿的影响,现代面向性能优化的CPU一般引入了乱序执行结合超标量的技术。也就是一方面,对于重点执行部件,比如计算部件,访存部件等,增加多份来支持并行。另一方面,在执行部件前引入缓冲池/队列机制,通用更长的预测执行来尽可能打满每个部件。最终从流水线模式,转向了更类似『多线程』的设计模式:

    图片

    乱序执行系统中,一般会将通过预测维护一个较长的指令序列,并构建一个指令池,通过解析指令池内的依赖关系,形成一张DAG(有向无环图) 组织的网状结构。通过对DAG关系的计算,其中依赖就绪的指令,就可以进入执行态,被提交到实际的执行部件中处理。执行部件类似多线程模型中的工作线程,根据特性细分为计算和访存两类。计算类一般有相对固定可预期的执行周期,而访存类由于指令周期差异较大,采用了异步回调的模型,通过Load/Store Buffer支持同时发起数十个访存操作。

    乱序执行系统和传统流水线模式的区别主要体现在,当一条访存指令因为Cache Miss而无法立即完成时,其后无依赖关系的指令可以插队执行(类似于多线程模型中某个线程阻塞后,OS将其挂起并调度其他线程)。插队的计算类指令可以填补空窗充分利用计算能力,而插队的访存指令通过更早启动传输,让访存停顿期尽量重叠来减小整体的停顿。因此乱序执行系统的效率,很大程度上会受到窗口内指令DAG的『扁平』程度的影响,依赖深度较浅的DAG可以提供更高的指令级并发能力,进而提供更高的执行部件利用率,以及更少的停顿周期。另一方面,由于Load/Store Buffer也有最大的容量限制,处理较大区域的内存访问负载时,将可能带来更深层穿透的访存指令尽量靠近排布,来提高访存停顿的重叠,也能够有效减少整体的停顿。

    虽然理论比较清晰,可是在实践中,仅仅从外部指标观测到的性能表现,往往难以定位乱序执行系统内部的热点。最直白的CPU利用率其实只能表达线程未受阻塞,真实在使用CPU的时间周期,但是其实并不能体现CPU内部部件真正的利用效率如何。稍微进阶一些的IPC(Instruction Per Cyc le),可以相对深入地反应一些利用效能,但是影响IPC的因素又多种多样。是指令并行度不足?还是长周期ALU计算负载大?又或者是访存停顿过久?甚至可能是分支预测失败率过高?真实程序中,这几项问题往往是并存的,而且单一地统计往往又难以统一比较,例如10次访存停顿/20次ALU 未打满/30个周期的页表遍历,到底意味着瓶颈在哪里?这个问题单一的指标往往就难以回答了。

    3.3 TMAM

    TMAM(Top-down Microarchitecture Analysis Method)是一种利用CPU内部PMU(Performance Monitoring Unit)计数器来从上至下分解定位部件瓶颈的手段。例如在最顶层,首先以标定最大指令完成速率为标准(例如Skylake上为单周期4条微指令),如果无法达到标定,则认为瓶颈在于未能充分利用部件。进一步细分以指令池为分界线,如果指令池未满,但是取指部件又无法满负荷输出微指令,就表明『前端』存在瓶颈。另一种无法达到最大指令速率的因素,是『前端』虽然在发射指令到指令池,但是因为错误的预测,最终没有产出有效结果,这类损耗则被归入『错误预测』。除此以外的问题就是因为指令池调度执行能力不足产生的反压停顿,这一类被归为『后端』瓶颈。进一步例如『后端』瓶颈还可以根 据,停顿发生时,是否伴随了ALU利用不充分,是否伴随了Load/Store Buffer满负荷等因素,继续进行分解细化,形成了一套整体的分析方法。例如针对Intel,这一过程可以通过pmu-tools来被自动完成,对于指导精细化的程序瓶颈分析和优化往往有很大帮助。

    int array\[1024\];
    for (size\_t i = 0; i < 1024; i += 2) {
     int a = array\[i\];
     int b = array\[i + 1\];
     for (size\_t j = 0; j < 1024; ++j) { 
     a = a + b;
     b = a + b;}
     array\[i\] = a;
     array\[i + 1\] = b;
    }
    

    例如这是里演示一个多轮计算斐波那契数列的过程,因为计算特征中深层循环有强指令依赖,且内层循环长度远大于常规乱序执行的指令池深度, 存在较大的计算依赖瓶颈,从工具分析也可以印证这一点。

    图片图片

    程序的IPC只有1,内部瓶颈也显示集中在『后端』内部的部件利用效率(大多时间只利用了一个port),此时乱序执行并没有发挥作用。

    int array\[1024\];
    for (size\_t i = 0; i < 1024; i += 4) {
      int a = array\[i\];
      int b = array\[i + 1\];
      int c = array\[i + 2\];
      int d = array\[i + 3\];
      for (size\_t j = 0; j < 1024; ++j) {
        a = a + b;
        b = a + b;
        c = c + d;
        d = c + d;
      }
      array\[i\] = a;
      array\[i + 1\] = b;
      array\[i + 2\] = c;
      array\[i + 3\] = d;
    }
    

    这里演示了典型的的循环展开方法,通过在指令窗口内同时进行两路无依赖计算,提高了指令并行度,通过工具分析也可以确认到效果。

    图片图片

    不过实践中,能够在寄存器上反复迭代的运算并不常见,大多情况下比较轻的计算负载,搭配比较多的访存动作会更经常遇到,像下面的这个例子:

    struct Line {     
        char data\[64\];
    };
    Line\* lines\[1024\]; // 其中乱序存放多个缓存行
    for (size\_t i = 0; i < 1024; ++i) {   
      Line\* line = lines\[i\];
      for (size\_t j = 0; j < 64; ++j) {   
        line->data\[j\] += j; 
     }
    }
    

    这是一个非连续内存上进行累加计算的例子,随外层迭代会跳跃式缓存行访问,内层循环在连续缓存行上进行无依赖的计算和访存操作。

    图片

    可以看到,这一次的瓶颈到了穿透缓存后的内存访存延迟上,但同时内存访问的带宽并没有被充分利用。这是因为指令窗口内虽然并发度不低,不过因为缓存层次系统的特性,内层循环中的多个访存指令,其实最终都是等待同一行被从内存加载到缓存。导致真正触发的底层访存压力并不足以打满传输带宽,但是程序却表现出了较大的停顿。

    for (size\_t i = 0; i < 1024; i += 2) { 
      Line\* line1 = lines\[i\];
      Line\* line2 = lines\[i + 1\];
      ...
      for (size\_t j = 0; j < 64; ++j) { 
        line1->data\[j\] += j;
        line2->data\[j\] += j;
        ...
       }
     }
    

    图片

    通过调整循环结构,在每一轮内层循环中一次性计算多行数据,可以在尽量在停顿到来的指令窗口内,让更多行出于同时从内存系统进行传输。从统计指标上也可以看出,瓶颈重心开始从穿透访存的延迟,逐步转化向访存带宽,而实际的缓存传输部件Fill    Buffer也开始出现了满负荷运作的情况。

    3.4 总结一下单线程并发

    现代CPU在遇到主频瓶颈后,除了改为增加核心数,也在单核心内逐步强化并行能力。如果说多进程多线程技术的普及,让多核心的利用技术多少不那么罕见和困难,那么单核心内的并行加速技术,因为更加黑盒(多级缓存加乱序执行),规范性不足(SIMD),相对普及度和利用率都会更差一些。虽然硬件更多的细节向应用层暴露让程序的实现更加困难,不过困难和机会往往也是伴随出现的,既然客观发展上这种复杂性增加已经无可避免,那么是否能善加利用也成了工程师进行性能优化时的一项利器。随着体系结构的进一步复杂化,可见的未来一段时间里,能否利用一些体系结构的原理和工具来进行优化,也会不可避免地成为服务端工程师的一项重要技能。

    四、多线程并发中的临界区保护

    相比单线程中的并发设计,多线程并发应该是更为工程师所熟悉的概念。如今,将计算划分到多线程执行的应用技术本身已经相对成熟了,相信各个服务端工程师都有各自熟悉的队列+线程池的小工具箱。在不做其他额外考虑的情况下,单纯的大任务分段拆分,提交线程池并回收结果可能也仅仅是几行代码就可以解决的事情了。真正的难点,其实往往不在于『拆』,而在于『合』的部分,也就是任务拆分中无法避免掉的共享数据操作环节。如果说更高的分布式层面,还可以尽可能地利用Share Nothing思想,在计算发生之前,就先尽量通过任务划分来做到尽可能充分地隔离资源。但是深入到具体的计算节点内部,如果再进行一些细粒度的拆分加速时,共享往往就难以彻底避免了。如何正确高效地处理这些无法避免的共享问题,就涉及到并发编程中的一项重要技术,临界区保护。

    4.1 什么是临界区

    图片

    算法并发改造中,一般会产生两类段落,一类是多个线程间无需交互就可以独立执行的部分,这一部分随着核心增多,可以顺利地水平扩展。而另一类是需要通过操作共享的数据来完成执行,这部分操作为了能够正确执行,无法被多个核心同时执行,只能每个线程排队通过。因此临界区内的代码,也就无法随着核心增多来扩展,往往会成为多线程程序的瓶颈点。也是因为这个特性,临界区的效率就变得至关重要,而如何保证各个线程安全地通过临界区的方法,就是临界区保护技术。

    4.1.1 Mutual Exclusion

    图片

    最基本的临界区保护方法,就是互斥技术。这是一种典型的悲观锁算法,也就是假设临界区高概率存在竞争,因此需要先利用底层提供的机制进行仲裁,成功获得所有权之后,才进入临界区运行。这种互斥算法,有一个典型的全局阻塞问题,也就是上图中,当临界区内的线程发生阻塞,或被操作系统换出时,会出现一个全局执行空窗。这个执行空窗内,不仅自身无法继续操作,未获得锁的线程也只能一同等待,造成了阻塞放大的现象。但是对于并行区,单一线程的阻塞只会影响自身,同样位于在上图中的第二次阻塞就是如此。

    由于真实发生在临界区内的阻塞往往又是不可预期的,例如发生了缺页中断,或者为了申请一块内存而要先进行一次比较复杂的内存整理。这就会让阻塞扩散的问题更加严重,很可能改为让另一个线程先进入临界区,反而可以更快顺利完成,但是现在必须所有并发参与者,都一起等待临界区持有者来完成一些并没有那么『关键』的操作。因为存在全局阻塞的可能性,采用互斥技术进行临界区保护的算法有着最低的阻塞容忍能力,一般在『非阻塞算法』领域作为典型的反面教材存在。

    4.1.2 Lock Free

    图片

    针对互斥技术中的阻塞问题,一个改良型的临界区保护算法是无锁技术。虽然叫做无锁,不过主要是取自非阻塞算法等级中的一种分类术语,本质上是一种乐观锁算法。也就是首先假设临界区不存在竞争,因此直接开始临界区的执行,但是通过良好的设计,让这段预先的执行是无冲突可回滚的。但是最终设计一个需要同步的提交操作,一般基于原子变量CAS(Compare And Swap),或者版本校验等机制完成。在提交阶段如果发生冲突,那么被仲裁为失败的各方需要对临界区预执行进行回滚,并重新发起一轮尝试。

    无锁技术和互斥技术最大的区别是,临界区核心的执行段落是可以类似并行段落一样独立进行,不过又不同于真正的并行段落,同时执行的临界区中,只有一个是真正有效的,其余最终将被仲裁为无效并回滚。但是引入了冗余的执行操作后,当临界区内再次发生阻塞时,不会像互斥算法那样在参与线程之间进行传播,转而让一个次优的线程成功提交。虽然从每个并发算法参与线程的角度,存在没有执行『实质有效』计算的段落,但是这种浪费计算的段落,一定对应着另一个参与线程执行了『有效』的计算。所以从整个算法层面,能够保证不会全局停顿,总是有一些有效的计算在运行。

    4.1.3 Wait-Free

    图片

    无锁技术主要解决了临界区内的阻塞传播问题,但是本质上,多个线程依然是排队顺序经过临界区。形象来说,有些类似交通中的三叉路口汇合, 无论是互斥还是无锁,最终都是把两条车道汇聚成了一条单车道,区别只是指挥是否高明能保证没有断流出现。可是无论如何,临界区内全局吞吐降低成串行这点是共同的缺陷。

    而Wait Free级别和无锁的主要区别也就体现在这个吞吐的问题上,在无全局停顿的基础上,Wait Free进一步保障了任意算法参与线程,都应该在有限的步骤内完成。这就和无锁技术产生了区别,不只是整体算法时时刻刻存在有效计算,每个线程视角依然是需要持续进行有效计算。这就要求了多线程在临界区内不能被细粒度地串行起来,而必须是同时都能进行有效计算。回到上面三叉路口汇聚的例子,就以为着在Wait Free级别下,最终汇聚的道路依旧需要是多车道的,以保证可以同时都能够有进展。

    图片

    虽然理论角度存在不少有Wait Free级别的算法,不过大多为概念探索,并不具备工业使用价值。主要是由于Wait Free限制了同时有进展,但是并没有描述这个进展有多快。因此进一步又提出了细分子类,以比较有实际意义的Wait-Free Population Oblivious级别来说,额外限制了每个参与线程必须要在预先可给出的明确执行周期内完成,且这个周期不能和与参与线程数相关。这一点明确拒绝了一些类似线程间协作的方案(这些方案往往引起较大的缓存竞争),以及一些需要很长很长的有限步来完成的设计。

    图片

    上图实例了一个典型的Wait Free Population Oblivious思路。进行临界区操作前,通过一个协同操作为参与线程分配独立的ticket,之后每个参与线程可以通过获取到的ticket作为标识,操作一块独立的互不干扰的工作区,并在其中完成操作。工业可用的Wait Free算法一般较难设计,例如ticket机制要求在协调动作中原子完成工作区分配,而很多数据结构是不容易做到这样的拆分的。时至今日各种数据结构上工业可用的Wait    Free算法依旧是一项持续探索中的领域。

    4.2 无锁不是万能的

    从非阻塞编程的角度看,上面的几类临界区处理方案优劣有着显著的偏序关系,即Wait Free > Lock Free > Mutual Exclusion。这主要是从阻塞适应性角度进行的衡量,原理上并不能直接对应到性能纬度。但是依然很容易给工程师造成一个普适印象,也就是『锁是很邪恶的东西,不使用锁来实现算法可以显著提高性能』,再结合广为流传的锁操作自身开销很重的认知,很多工程师在实践中会有对锁敬而远之的倾向。那么,这个指导思想是否是完全正确的?

    让我们先来一组实验:

    // 在一个cache line上进行指定步长的斐波那契计算来模拟临界区计算负载
    uint64\_t calc(uint64\_t\* sequence, size\_t size) {
        size\_t i;
        for (i = 0; i < size; ++i) {
            sequence\[(i + 1) & 7\] += sequence\[i & 7\];
        }
        return sequence\[i & 7\];
    }
    {   // Mutual Exclusion
        ::std::lock\_guard<::std::mutex> lock(mutex);
        sum += calc(sequence, workload);
    }
    {   // Lock Free / Atomic CAS
        auto current = atomic\_sum.load(::std::memory\_order\_relaxed);
        auto next = current;
        do {
            next = current + calc(sequence, workload);
        } while (!atomic\_sum.compare\_exchange\_weak(
                     current, next, ::std::memory\_order\_relaxed));
    }
    {   // Wait Free / Atomic Modify
        atomic\_sum.fetch\_add(calc(sequence, workload), ::std::memory\_order\_relaxed);
    }
    

    这里采用多线程累加作为案例,分别采用上锁后累加,累加后CAS提交,以及累加后FAA(Fetch And Add)提交三种方法对全局累加结果做临界区保护。针对不同的并发数量,以及不同的临界区负载,可以形成如下的三维曲线图。

    其中Latency项除以临界区规模进行了归一,便于形象展示临界区负载变化下的临界区保护开销趋势,因此跨不同负载等级下不具备横向可比性。Cycles项表示多线程协同完成总量为同样次数的累加,用到的CPU周期总和,总体随临界区负载变化有少量天然倾斜。100/1600两个截面图将3中算法叠加在一起展示,便于直观对比。

    图片图片图片

    图片

    图片

    从上面的数据中可以分析出这样一些信息

    1、基于FAA的Wait Free模式各方面都显著胜过其他方法;

    2、无锁算法相比互斥算法在平均吞吐上有一定优势,但是并没有达到数量级水平;

    3、无锁算法随竞争提升(临界区大小增大,或者线程增多),cpu消耗显著上升;

    基于这些信息来分析,会发现一个和之前提到的『锁性能』的常规认知相悖的点。性能的分水岭并没有出现在基于锁的互斥算法和无锁算法中间, 而是出现在同为『未使用锁』的Lock Free和Wait Free算法中间。而且从CPU消耗角度来看,对临界区比较复杂,竞争强度高的场景,甚至Lock Free因为『无效预测执行』过多反而引起了过多的消耗。这表明了锁操作本身的开销虽然稍重于原子操作,但其实也并非洪水猛兽,而真正影响性能的,是临界区被迫串行执行所带来的并行能力折损。

    因此当我们遇到临界区保护的问题时,可以先思考一下,是否可以采用Wait Free的方法来完成保护动作,如果可以的话,在性能上能够接近完全消除了临界区的效果。而在多数情况下,往往还是要采用互斥或Lock Free来进行临界区的保护。此时临界区的串行不可避免,所以充分缩减临界区的占比是共性的第一要务,而是否进一步采用Lock Free技术来减少临界区保护开销,讨论的前提也是临界区已经显著很短,不会引起过多的无效预 测。除此以外,由于Lock Free算法一般对临界区需要设计成两阶段提交,以便支持回滚撤销,因此往往需要比对应的互斥保护算法更复杂,局部性也可能更差(例如某些场景必须引入链表来替换数组)。综合来看,一般如果无法做到Wait Free,那么无需对Lock Free过度执着,充分优化临界区的互斥方法往往也足以提供和Lock Free相当的性能表现了。

    4.3 并发计数器优化案例

    从上文针对临界区保护的多种方法所做的实验,还可以发现一个现象。随着临界区逐渐减小,保护措施开销随线程数量增加而提升的趋势都预发显著,即便是设计上效率和参与线程数本应无关的Wait Free级别也是一样。这对于临界区极小的并发计数器场景,依旧会是一个显著的问题。那么我们就先从锁和原子操作的实现角度,看看这些损耗是如何导致的。

    图片

    首先给出一个典型的锁实现,左侧是锁的fast path,也就是如果在外层的原子变量操作中未发现竞争,那么其实上锁和解锁其实就只经历了一组原子变量操作。当fast  path检测到可能出现冲突时,才会进入内核,尝试进行排队等待。fast  path的存在大幅优化了低冲突场景下的锁表现,而且现代操作系统内核为了优化锁的内存开销,都提供了『Wait On Address』的功能,也就是为了支持这套排队机制,每个锁常态只需要一个整数的存储开销即可,只有在尝试等待时,才会创建和占用额外的辅助结构。

    因此实际设计中,锁可以创建很多,甚至非常多,只要能够达到足够细粒度拆解冲突的效果。这其中最典型的就是brpc中计数器框架bvar的设计。

    图片

    这是bvar中基础统计框架的设计,局部计数和全局汇聚时都通过每个tls附加的锁来进行临界区保护。因为采集周期很长,冲突可以忽略不记,因此虽然默认使用了大量的锁(统计量 * 线程数),但是并没有很大的内存消耗,而且运行开销其实很低,能够用来支持任意的汇聚操作。这个例子也能进一步体现,锁本身的消耗其实并不显著,竞争带来的软件或硬件上的串行化才是开销的核心。

    图片

    不过即使竞争很低,锁也还是会由一组原子操作实现,而当我们自己查看原子操作时,实际是由cache锁操作保护的原子指令构成,而且这个指令会在乱序执行中起到内存屏障的效果降低访存重叠的可能性。因此针对非常常用的简单计数器,在百度内部我们进行了进一步去除局部锁的改造,来试图进一步降低统计开销。

    图片

    例如对于需要同时记录次数和总和的IntRecorder,因为需要两个64位加法,曾经只能依赖锁来保证原子更新。但随着新x86机型的不断普及,在比较新的X86和ARM服务端机型上已经可以做到128bit的原子load/store,因此可以利用相应的高位宽指令和正确对齐来实现锁的去除。

    图片

    另一个例子是Percentile分位值统计,由于抽样数据是一个多元素容器,而且分位值统计需要周期清空重算,因此常规也是采用了互斥保护的方法。不过如果引入版本号机制,将清空操作转交给计数线程自己完成,将sample区域的读写完全分离。在这个基础上,就可以比较简单的做到线程安全,而且也不用引入原子修改。严格意义上,异步清空存在边界样本收集丢失的可能性,不过因为核心的蓄水池抽样算发本身也具有随机性,在监控指标统计领域已经拥有足够精度。

    图片

    除了运行时操作,线程局部变量的组织方式原先采用锁保护的链表进行管理,采用分段数据结合线程编号的方法替换后,做到空间连续化。最终整体进一步改善了计数器的性能。

    image.png

    4.4 并发队列优化案例

    另一个在多线程编程中经常出现的数据结构就是队列,为了保证可以安全地处理并发的入队和出队操作,最基础的算法是整个队列用锁来保护起来。

    图片

    这个方法的缺点是显而易见的,因为队列往往作为多线程驱动的数据中枢位置,大量的竞争下,队列操作被串行很容易影响整体计算的并行度。因此一个自然的改进点是,将队列头尾分开保护,先将生产者和消费者解耦开,只追加必要的同步操作来保证不会过度入队和出队。这也是Jave中LinkedBlockingQueue所使用的做法。

    图片

    在头尾分离之后,进一步的优化进入了两个方向。首先是因为单节点的操作具备了Lock Free化的可能,因此产生了对应的Michael & Scott无锁队列算法。业界的典型实现有Java的ConcurrentLinkedQueue,以及boost中的boost::lockfree::queue。

    图片

    而另一个方向是队列分片,即将队列拆解成多个子队列,通过领取token的方式选择子队列,而子队列内部使用传统队列算法,例如tbb:: concurrent_queue就是分片队列的典型实现。

    图片

    image.png

    对两种方式进行对比,可以发现,在强竞争下,分片队列的效果其实显著胜过单纯的无锁处理,这也是前文对于无锁技术真实效果分析的一个体现。

    除了这类通用队列,还有一个强化竞争发布,串行消费的队列也就是bthread::ExecutionQueue,它在是brpc中主要用于解决多线程竞争fd写入的问题。利用一些有趣的技巧,对多线程生产侧做到了Wait Free级别。

    图片

    整个队列只持有队尾,而无队头。在生产侧,第一步直接将新节点和当前尾指针进行原子交换,之后再将之前的队尾衔接到新节点之后。因为无论是否存在竞争,入队操作都能通过固定的两步完成,因此入队算法是Wait Free的。不过这给消费侧带来的麻烦,消费同样从一个原子交换开始,将队尾置换成nullptr,之后的消费动作就是遍历取到的单链表。但是因为生产操作分了两部完成,此时可能发现部分节点尚处于『断链』状态,由于消费者无从知晓后续节点信息,只能轮询等待生产者最终完成第二步。所以理论上,生产/消费算法其实甚至不是Lock Free的,因为如果生产者在两阶段中间被换出,那么消费者会被这个阻塞传播影响,整个消费也只能先阻塞住。但是在排队写入fd的场景下,专项优化生产并发是合理,也因此可以获得更好的执行效率。

    image.png

    不过为了能利用原子操作完成算法,bthread::ExecutionQueue引入了链表作为数据组织方式,而链表天然存在访存跳跃的问题。那么是否可以用数组来同样实现Wait Free的生产甚至消费并发呢?

    这就是babylon::ConcurrentBoundedQueue所希望解决的问题了。

    不过介绍这个队列并发原理之前,先插入一个勘误信息。其实这个队列在《内存篇》最后也简单提到过,不过当时粗略的评测显示了acquire- release等级下,即使不做cache line隔离性能也可以保障。文章发表后收到业界同好反馈,讨论发现当时的测试用例命中了Intel Write Combining 优化技术,即当仅存在唯一一个处于等待加载的缓存行时,只写动作可以无阻塞提前完成,等缓存行真实加载完毕后,再统一提交生效。但是由于内存序问题,一旦触发了第二个待加载的缓存行后,对于第一个缓存行的Write Combine就无法继续生效,只能等待第二个缓存行的写完成后,才能继续提交。原理上,Write Combine技术确实缓解了只写场景下的False Sharing,但是只能等待一个缓存行的限制在真实场景下想要针对性利用起来限制相当大。例如在队列这个典型场景下,往往会同时两路操作数据和完成标记,很可能同时处于穿透加载中,此时是无法应用Write Combine技术的。此外,能够在缓存行加载周期内,有如此充分的同行写入,可能也只有并无真实意义的评测程序才能做到。所以从结论上讲,通常意义上的多线程cache line隔离还是很有必要的。

    图片

    回到babylon::ConcurrentBoundedQueue的设计思路上,其实是将子队列拆分做到极致,将同步量粒度降低到每个数据槽位上。每个入队和出队  请求,首先利用原子自增领取一个递增的序号,之后利用循环数组的存储方式,就可以映射到一个具体的数据槽位上。根据操作是入队还是出队, 在循环数组上发生了多少次折叠,就可以在一个数据槽位上形成一个连续的版本序列。例如1号入队和5号出队都对应了1号数据槽位,而1号入队预期的版本转移是0到1,而5号出队的版本转移是2到3。这样针对同一个槽位的入队和出队也可以形成一个连续的版本变更序列,一个领到序号的具体操作,只需要明确检测版本即可确认自己当前是否可以开始操作,并通过自己的版本变更和后续的操作进行同步。

    通过同步量下放到每个元素的方式,入队和出队操作在可以除了最开始的序号领取存在原子操作级别的同步,后续都可以无干扰并行开展。而更连续的数据组织,也解决了链表存储的访存跳跃问题。生产消费双端可并发的特点,也提供了更强的泛用性,实际在MPMC(Multiple Producer Mult iple Consumer)和MPSC(Multiple Producer Single Consumer)场景下都有不错的性能表现,在具备一定小批量处理的场景下尤其显著。

    图片

    招聘信息

    欢迎出色的C++ 工程师加入百度,与大神一起成长。关注同名公众号百度Geek说,输入内推即可,我们期待你的加入!

    推荐阅读

    百度C++工程师的那些极限优化(内存篇)

    |百度大规模Service Mesh落地实践

    一种基于实时分位数计算的系统及方法

    ---------- END ----------

    百度Geek说

    百度官方技术公众号上线啦!

    技术干货 · 行业资讯 · 线上沙龙 · 行业大会

    招聘信息 · 内推信息 · 技术书籍 · 百度周边

    欢迎各位同学关注

    展开全文
  • 导读:对于工程经验比较丰富的同学,并发应该也并不是陌生的概念了,但是每个人所理解的并发问题,却又往往并不统一,本文系统梳理了百度C++工程师在进行并发优化时所作的工作。全文15706字,预...

    导读:对于工程经验比较丰富的同学,并发应该也并不是陌生的概念了,但是每个人所理解的并发问题,却又往往并不统一,本文系统梳理了百度C++工程师在进行并发优化时所作的工作。

    全文15706字,预计阅读时间24分钟。

    一、背景

    简单回顾一下,一个程序的性能构成要件大概有三个,即算法复杂度、IO开销和并发能力。由于现代计算机体系结构复杂化,造成很多时候,工程师的性能优化会更集中在算法复杂度之外的另外两个方向上,即IO和并发,在之前的《百度C++工程师的那些极限优化(内存篇)》中,我们介绍了百度C++工程师工程师为了优化性能,从内存IO角度出发所做的一些优化案例。

    这次我们就再来聊一聊另外一个性能优化的方向,也就是所谓的并发优化。和IO方向类似,对于工程经验比较丰富的同学,并发应该也并不是陌生的概念了,但是每个人所理解的并发问题,却又往往并不统一。所以下面我们先回到一个更根本的问题,重新梳理一下所谓的并发优化。

    二、为什么我们需要并发?

    是的,这个问题可能有些跳跃,但是在自然地进展到如何处理各种并发问题之前,我们确实需要先停下来,回想一下为什么我们需要并发?

    这时第一个会冒出来的概念可能会是大规模,例如我们要设计大规模互联网应用,大规模机器学习系统。可是我们仔细思考一下,无论使用了那种程度的并发设计,这样的规模化系统背后,都需要成百上千的实例来支撑。也就是,如果一个设计(尤其是无状态计算服务设计)已经可以支持某种小规模业务。那么当规模扩大时,很可能手段并不是提升某个业务单元的处理能力,而是增加更多业务单元,并解决可能遇到的分布式问题。

    其实真正让并发编程变得有价值的背景,更多是业务单元本身的处理能力无法满足需求,例如一次请求处理时间过久,业务精细化导致复杂度积累提升等等问题。那么又是什么导致了近些年来,业务单元处理能力问题不足的问题呈现更加突出的趋势?

    可能下面这个统计会很说明问题:

    (https://www.karlrupp.net/2015/06/40-years-of-microprocessor-trend-data/)

    上图从一个长线角度,统计了CPU的核心指标参数趋势。从其中的晶体管数目趋势可以看出,虽然可能逐渐艰难,但是摩尔定律依然尚能维持。然而近十多年,出于控制功耗等因素的考虑,CPU的主频增长基本已经停滞,持续增加的晶体管转而用来构建了更多的核心。

    从CPU厂商角度来看,单片处理器所能提供的性能还是保持了持续提升的,但是单线程的性能增长已经显著放缓。从工程师角度来看,最大的变化是硬件红利不再能透明地转化成程序的性能提升了。随时代进步,更精准的算法,更复杂的计算需求,都在对的计算性能提出持续提升的要求。早些年,这些算力的增长需求大部分还可以通过处理器更新换代来自然解决,可是随着主频增长停滞,如果无法利用多核心来加速,程序的处理性能就会随主频一同面临增长停滞的问题。因此近些年来,是否能够充分利用多核心计算,也越来越成为高性能程序的一个标签,也只有具备了充分的多核心利用能力,才能随新型硬件演进,继续表现出指数级的性能提升。而伴随多核心多线程程序设计的普及,如何处理好程序的并发也逐渐成了工程师的一项必要技能。

    上图描述了并发加速的基本原理,首先是对原始算法的单一执行块拆分成多个能够同时运行的子任务,并设计好子任务间的协同。之后利用底层的并行执行部件能力,将多个子任务在时间上真正重叠起来,达到真正提升处理速度的目的。

    需要注意的是还有一条从下而上的反向剪头,主要表达了,为了正确高效地利用并行执行部件,往往会反向指导上层的并发设计,例如正确地数据对齐,合理的临界区实现等。虽然加速看似完全是由底层并行执行部件的能力所带来的,程序设计上只需要做到子任务拆分即可。但是现阶段,执行部件对上层还无法达到透明的程度,导致这条反向依赖对于最终的正确性和性能依然至关重要。既了解算法,又理解底层设计,并结合起来实现合理的并发改造,也就成为了工程师的一项重要技能。

    三、单线程中的并行执行

    提到并行执行部件,大家的第一个印象往往时多核心多线程技术。不过在进入到多线程之前,我们先来看看,即使是单线程的程序设计中,依然需要关注的那些并行执行能力。回过头再仔细看前文的处理器趋势图其实可以发现,虽然近年主频不再增长,甚至稳中有降,但是单线程处理性能其实还是有细微的提升的。这其实意味着,在单位时钟周期上,单核心的计算能力依然在提升,而这种提升,很大程度上就得益于单核心单线程内的细粒度并行执行能力。

    3.1 SIMD

    其中一个重要的细粒度并行能力就是SIMD(Single Instruction Multiple Data),也就是多个执行单元,同时对多个数据应用相同指令进行计算的模式。在经典分类上,一般单核心CPU被归入SISD(Single Instruction Single Data),而多核心CPU被归入MIMD(Mingle Instruction Multiple D ata),而GPU才被归入SIMD的范畴。但是现代CPU上,除了多核心的MIMD基础模型,也同时附带了细粒度SIMD计算能力。

    上图是Intel关于SIMD指令的一个示意图,通过增加更大位宽的寄存器实现在一个寄存器中,“压缩”保存多个较小位宽数据的能力。再通过增加特殊的运算指令,对寄存器中的每个小位宽的数据元素,批量完成某种相同的计算操作,例如图示中最典型的对位相加运算。以这个对位相加操作为例,CPU只需要增大寄存器,内存传输和计算部件位宽,针对这个特殊的应用场景,就提升到了8倍的计算性能。相比将核心数通用地提升到8倍大小,这种方式付出的成本是非常少的,指令流水线系统,缓存系统都做到了复用。

    从CPU发展的视角来看,为了能够在单位周期内处理更多数据,增加核心数的MIMD强化是最直观的实现路径。但是增加一套核心,就意味增加一套 完整的指令部件、流水线部件和缓存部件,而且实际应用时,还要考虑额外的核心间数据分散和聚合的传输和同步开销。一方面高昂的部件需求, 导致完整的核心扩展成本过高,另一方面,多核心间传输和同步的开销针对小数据集场景额外消耗过大,还会进一步限制应用范围。为了最大限度利用好有限的晶体管,现代CPU在塑造更多核心的同时,也在另一个维度上扩展单核心的处理和计算位宽,从而实现提升理论计算性能(核心数 * 数据宽度)的目的。

    不过提起CPU上的SIMD指令支持,有一个绕不开的话题就是和GPU的对比。CPU上早期SIMD指令集(MMX)的诞生背景,和GPU的功能定位就十分类似,专注于加速图像相关算法,近些年又随着神经网络计算的兴起,转向通用矩阵类计算加速。但是由于GPU在设计基础上就以面向密集可重复计算负载设计,指令部件、流水线部件和缓存部件等可以远比CPU简洁,也因此更容易在量级上进行扩展。这就导致,当计算密度足够大,数据的传输和同步开销被足够冲淡的情况下(这也是典型神经网络计算的的特性),CPU仅作为控制流进行指挥,而数据批量传输到GPU协同执行反而 会更简单高效。

    由于Intel自身对SIMD指令集的宣传,也集中围绕神经网络类计算来展开,而在当前工程实践经验上,主流的密集计算又以GPU实现为主。这就导致了不少CPU上SIMD指令集无用论应运而生,尤其是近两年Intel在AVX512初代型号上的降频事件,进一步强化了『CPU就应该做好CPU该做的事情』这一论调。但是单单从这一的视角来认识CPU上的SIMD指令又未免有些片面,容易忽视掉一些真正有意义的CPU上SIMD应用场景。

    对于一段程序来讲,如果将每读取单位数据,对应的纯计算复杂度大小定义为计算密度,而将算法在不同数据单元上执行的计算流的相同程度定义为模式重复度,那么可以以此将程序划分为4个象限。在大密度可重复的计算负载(典型的重型神经网络计算),和显著小密度和非重复计算负载(例如HTML树状解析)场景下,业界在CPU和GPU的选取上其实是有相对明确“最优解”的。不过对于过渡地带,计算的重复特征没有那么强,  或者运算密度没有那么大的场景下,双方的弱点都会被进一步放大。即便是规整可重复的计算负载,随着计算本身强度减小,传输和启动成本逐渐显著。另一方面,即便是不太规整可重复的计算负载,随着计算负荷加大,核心数不足也会逐渐成为瓶颈。这时候,引入SIMD的CPU和引入SIMT 的GPU间如何选择和使用,就形成了没有那么明确,见仁见智的权衡空间。

    即使排除了重型神经网络,从程序的一般特性而言,具有一定规模的重复特性也是一种普遍现象。例如从概念上讲,程序中的循环段落,都或多或少意味着批量/重复的计算负载。尽管因为掺杂着分支控制,导致重复得没有那么纯粹,但这种一定规模的细粒度重复,正是CPU上SIMD发挥独特价值的地方。例如最常见的SIMD优化其实就是memcpy,现代的memcpy实现会探测CPU所能支持的SIMD指令位宽,并尽力使用来加速内存传输。另一方面现代编译器也会利用SIMD指令来是优化对象拷贝,进行简单循环向量化等方式来进行加速。类似这样的一类优化方法偏『自动透明』,也是默默支撑着主频不变情况下,性能稍有上升的重要推手。

    可惜这类简单的自动优化能做到的事情还相当有限,为了能够充分利用CPU上的SIMD加速,现阶段还非常依赖程序层进行主动算法适应性改造,有 目的地使用,换言之,就是主动实施这种单线程内的并发改造。一个无法自动优化的例子就是《内存篇》中提到的字符串切分的优化,现阶段通过编译器分析还很难从循环 + 判断分支提取出数据并行pattern并转换成SIMD化的match&mask动作。而更为显著的是近年来一批针对SIMD指令重新设计的算法,例如Swiss Table哈希表,simdjson解析库,base64编解码库等,在各自的领域都带来了倍数级的提升,而这一类算法适应性改造,就已经完全脱离了自动透明所能触及的范围。可以预知近些年,尤其随着先进工艺下AVX512降频问题的逐渐解决,还会/也需要涌现出更多的传统基础算法的SIMD改造。而熟练运用SIMD指令优化技术,也将成为C++工程师的一项必要技能。

    3.2 OoOE

    另一个重要的单线程内并行能力就是乱序执行OoOE(Out of Order Execution)。经典教科书上的CPU流水线机制一般描述如下(经典5级RISC流水线)。

    指令简化表达为取指/译码/计算/访存/写回环节,当执行环节遇到数据依赖,以及缓存未命中等场景,就会导致整体停顿的产生。其中MEM环节的影响尤其显著,主要也是因为缓存层次的深化和多核心的共享现象,带来单次访存所需周期数参差不齐的现象越来越严重。上图中的流水线在多层缓存下的表现,可能更像下图所示:

    为了减轻停顿的影响,现代面向性能优化的CPU一般引入了乱序执行结合超标量的技术。也就是一方面,对于重点执行部件,比如计算部件,访存部件等,增加多份来支持并行。另一方面,在执行部件前引入缓冲池/队列机制,通用更长的预测执行来尽可能打满每个部件。最终从流水线模式,转向了更类似『多线程』的设计模式:

    乱序执行系统中,一般会将通过预测维护一个较长的指令序列,并构建一个指令池,通过解析指令池内的依赖关系,形成一张DAG(有向无环图) 组织的网状结构。通过对DAG关系的计算,其中依赖就绪的指令,就可以进入执行态,被提交到实际的执行部件中处理。执行部件类似多线程模型中的工作线程,根据特性细分为计算和访存两类。计算类一般有相对固定可预期的执行周期,而访存类由于指令周期差异较大,采用了异步回调的模型,通过Load/Store Buffer支持同时发起数十个访存操作。

    乱序执行系统和传统流水线模式的区别主要体现在,当一条访存指令因为Cache Miss而无法立即完成时,其后无依赖关系的指令可以插队执行(类似于多线程模型中某个线程阻塞后,OS将其挂起并调度其他线程)。插队的计算类指令可以填补空窗充分利用计算能力,而插队的访存指令通过更早启动传输,让访存停顿期尽量重叠来减小整体的停顿。因此乱序执行系统的效率,很大程度上会受到窗口内指令DAG的『扁平』程度的影响,依赖深度较浅的DAG可以提供更高的指令级并发能力,进而提供更高的执行部件利用率,以及更少的停顿周期。另一方面,由于Load/Store Buffer也有最大的容量限制,处理较大区域的内存访问负载时,将可能带来更深层穿透的访存指令尽量靠近排布,来提高访存停顿的重叠,也能够有效减少整体的停顿。

    虽然理论比较清晰,可是在实践中,仅仅从外部指标观测到的性能表现,往往难以定位乱序执行系统内部的热点。最直白的CPU利用率其实只能表达线程未受阻塞,真实在使用CPU的时间周期,但是其实并不能体现CPU内部部件真正的利用效率如何。稍微进阶一些的IPC(Instruction Per Cyc le),可以相对深入地反应一些利用效能,但是影响IPC的因素又多种多样。是指令并行度不足?还是长周期ALU计算负载大?又或者是访存停顿过久?甚至可能是分支预测失败率过高?真实程序中,这几项问题往往是并存的,而且单一地统计往往又难以统一比较,例如10次访存停顿/20次ALU 未打满/30个周期的页表遍历,到底意味着瓶颈在哪里?这个问题单一的指标往往就难以回答了。

    3.3 TMAM

    TMAM(Top-down Microarchitecture Analysis Method)是一种利用CPU内部PMU(Performance Monitoring Unit)计数器来从上至下分解定位部件瓶颈的手段。例如在最顶层,首先以标定最大指令完成速率为标准(例如Skylake上为单周期4条微指令),如果无法达到标定,则认为瓶颈在于未能充分利用部件。进一步细分以指令池为分界线,如果指令池未满,但是取指部件又无法满负荷输出微指令,就表明『前端』存在瓶颈。另一种无法达到最大指令速率的因素,是『前端』虽然在发射指令到指令池,但是因为错误的预测,最终没有产出有效结果,这类损耗则被归入『错误预测』。除此以外的问题就是因为指令池调度执行能力不足产生的反压停顿,这一类被归为『后端』瓶颈。进一步例如『后端』瓶颈还可以根 据,停顿发生时,是否伴随了ALU利用不充分,是否伴随了Load/Store Buffer满负荷等因素,继续进行分解细化,形成了一套整体的分析方法。例如针对Intel,这一过程可以通过pmu-tools来被自动完成,对于指导精细化的程序瓶颈分析和优化往往有很大帮助。

    int array[1024];
    for (size_t i = 0; i < 1024; i += 2) {
     int a = array[i];
     int b = array[i + 1];
     for (size_t j = 0; j < 1024; ++j) { 
     a = a + b;
     b = a + b;}
     array[i] = a;
     array[i + 1] = b;
    }
    

    例如这是里演示一个多轮计算斐波那契数列的过程,因为计算特征中深层循环有强指令依赖,且内层循环长度远大于常规乱序执行的指令池深度, 存在较大的计算依赖瓶颈,从工具分析也可以印证这一点。

    程序的IPC只有1,内部瓶颈也显示集中在『后端』内部的部件利用效率(大多时间只利用了一个port),此时乱序执行并没有发挥作用。

    int array[1024];for (size_t i = 0; i < 1024; i += 4) {  int a = array[i];  int b = array[i + 1];  int c = array[i + 2];  int d = array[i + 3];  for (size_t j = 0; j < 1024; ++j) {    a = a + b;    b = a + b;    c = c + d;    d = c + d;  }  array[i] = a;  array[i + 1] = b;  array[i + 2] = c;  array[i + 3] = d;}
    

    这里演示了典型的的循环展开方法,通过在指令窗口内同时进行两路无依赖计算,提高了指令并行度,通过工具分析也可以确认到效果。

    不过实践中,能够在寄存器上反复迭代的运算并不常见,大多情况下比较轻的计算负载,搭配比较多的访存动作会更经常遇到,像下面的这个例子:

    struct Line {     
        char data[64];
    };
    Line* lines[1024]; // 其中乱序存放多个缓存行
    for (size_t i = 0; i < 1024; ++i) {   
      Line* line = lines[i];
      for (size_t j = 0; j < 64; ++j) {   
        line->data[j] += j; 
     }
    }
    

    这是一个非连续内存上进行累加计算的例子,随外层迭代会跳跃式缓存行访问,内层循环在连续缓存行上进行无依赖的计算和访存操作。

    可以看到,这一次的瓶颈到了穿透缓存后的内存访存延迟上,但同时内存访问的带宽并没有被充分利用。这是因为指令窗口内虽然并发度不低,不过因为缓存层次系统的特性,内层循环中的多个访存指令,其实最终都是等待同一行被从内存加载到缓存。导致真正触发的底层访存压力并不足以打满传输带宽,但是程序却表现出了较大的停顿。

    for (size_t i = 0; i < 1024; i += 2) { 
      Line* line1 = lines[i];
      Line* line2 = lines[i + 1];
      ...
      for (size_t j = 0; j < 64; ++j) { 
        line1->data[j] += j;
        line2->data[j] += j;
        ...
       }
     }
    

    通过调整循环结构,在每一轮内层循环中一次性计算多行数据,可以在尽量在停顿到来的指令窗口内,让更多行出于同时从内存系统进行传输。从统计指标上也可以看出,瓶颈重心开始从穿透访存的延迟,逐步转化向访存带宽,而实际的缓存传输部件Fill    Buffer也开始出现了满负荷运作的情况。

    3.4 总结一下单线程并发

    现代CPU在遇到主频瓶颈后,除了改为增加核心数,也在单核心内逐步强化并行能力。如果说多进程多线程技术的普及,让多核心的利用技术多少不那么罕见和困难,那么单核心内的并行加速技术,因为更加黑盒(多级缓存加乱序执行),规范性不足(SIMD),相对普及度和利用率都会更差一些。虽然硬件更多的细节向应用层暴露让程序的实现更加困难,不过困难和机会往往也是伴随出现的,既然客观发展上这种复杂性增加已经无可避免,那么是否能善加利用也成了工程师进行性能优化时的一项利器。随着体系结构的进一步复杂化,可见的未来一段时间里,能否利用一些体系结构的原理和工具来进行优化,也会不可避免地成为服务端工程师的一项重要技能。

    四、多线程并发中的临界区保护

    相比单线程中的并发设计,多线程并发应该是更为工程师所熟悉的概念。如今,将计算划分到多线程执行的应用技术本身已经相对成熟了,相信各个服务端工程师都有各自熟悉的队列+线程池的小工具箱。在不做其他额外考虑的情况下,单纯的大任务分段拆分,提交线程池并回收结果可能也仅仅是几行代码就可以解决的事情了。真正的难点,其实往往不在于『拆』,而在于『合』的部分,也就是任务拆分中无法避免掉的共享数据操作环节。如果说更高的分布式层面,还可以尽可能地利用Share Nothing思想,在计算发生之前,就先尽量通过任务划分来做到尽可能充分地隔离资源。但是深入到具体的计算节点内部,如果再进行一些细粒度的拆分加速时,共享往往就难以彻底避免了。如何正确高效地处理这些无法避免的共享问题,就涉及到并发编程中的一项重要技术,临界区保护。

    4.1 什么是临界区

    算法并发改造中,一般会产生两类段落,一类是多个线程间无需交互就可以独立执行的部分,这一部分随着核心增多,可以顺利地水平扩展。而另一类是需要通过操作共享的数据来完成执行,这部分操作为了能够正确执行,无法被多个核心同时执行,只能每个线程排队通过。因此临界区内的代码,也就无法随着核心增多来扩展,往往会成为多线程程序的瓶颈点。也是因为这个特性,临界区的效率就变得至关重要,而如何保证各个线程安全地通过临界区的方法,就是临界区保护技术。

    4.1.1 Mutual Exclusion

    最基本的临界区保护方法,就是互斥技术。这是一种典型的悲观锁算法,也就是假设临界区高概率存在竞争,因此需要先利用底层提供的机制进行仲裁,成功获得所有权之后,才进入临界区运行。这种互斥算法,有一个典型的全局阻塞问题,也就是上图中,当临界区内的线程发生阻塞,或被操作系统换出时,会出现一个全局执行空窗。这个执行空窗内,不仅自身无法继续操作,未获得锁的线程也只能一同等待,造成了阻塞放大的现象。但是对于并行区,单一线程的阻塞只会影响自身,同样位于在上图中的第二次阻塞就是如此。

    由于真实发生在临界区内的阻塞往往又是不可预期的,例如发生了缺页中断,或者为了申请一块内存而要先进行一次比较复杂的内存整理。这就会让阻塞扩散的问题更加严重,很可能改为让另一个线程先进入临界区,反而可以更快顺利完成,但是现在必须所有并发参与者,都一起等待临界区持有者来完成一些并没有那么『关键』的操作。因为存在全局阻塞的可能性,采用互斥技术进行临界区保护的算法有着最低的阻塞容忍能力,一般在『非阻塞算法』领域作为典型的反面教材存在。

    4.1.2 Lock Free

    针对互斥技术中的阻塞问题,一个改良型的临界区保护算法是无锁技术。虽然叫做无锁,不过主要是取自非阻塞算法等级中的一种分类术语,本质上是一种乐观锁算法。也就是首先假设临界区不存在竞争,因此直接开始临界区的执行,但是通过良好的设计,让这段预先的执行是无冲突可回滚的。但是最终设计一个需要同步的提交操作,一般基于原子变量CAS(Compare And Swap),或者版本校验等机制完成。在提交阶段如果发生冲突,那么被仲裁为失败的各方需要对临界区预执行进行回滚,并重新发起一轮尝试。

    无锁技术和互斥技术最大的区别是,临界区核心的执行段落是可以类似并行段落一样独立进行,不过又不同于真正的并行段落,同时执行的临界区中,只有一个是真正有效的,其余最终将被仲裁为无效并回滚。但是引入了冗余的执行操作后,当临界区内再次发生阻塞时,不会像互斥算法那样在参与线程之间进行传播,转而让一个次优的线程成功提交。虽然从每个并发算法参与线程的角度,存在没有执行『实质有效』计算的段落,但是这种浪费计算的段落,一定对应着另一个参与线程执行了『有效』的计算。所以从整个算法层面,能够保证不会全局停顿,总是有一些有效的计算在运行。

    4.1.3 Wait-Free


    无锁技术主要解决了临界区内的阻塞传播问题,但是本质上,多个线程依然是排队顺序经过临界区。形象来说,有些类似交通中的三叉路口汇合, 无论是互斥还是无锁,最终都是把两条车道汇聚成了一条单车道,区别只是指挥是否高明能保证没有断流出现。可是无论如何,临界区内全局吞吐降低成串行这点是共同的缺陷。

    而Wait Free级别和无锁的主要区别也就体现在这个吞吐的问题上,在无全局停顿的基础上,Wait Free进一步保障了任意算法参与线程,都应该在有限的步骤内完成。这就和无锁技术产生了区别,不只是整体算法时时刻刻存在有效计算,每个线程视角依然是需要持续进行有效计算。这就要求了多线程在临界区内不能被细粒度地串行起来,而必须是同时都能进行有效计算。回到上面三叉路口汇聚的例子,就以为着在Wait Free级别下,最终汇聚的道路依旧需要是多车道的,以保证可以同时都能够有进展。

    虽然理论角度存在不少有Wait Free级别的算法,不过大多为概念探索,并不具备工业使用价值。主要是由于Wait Free限制了同时有进展,但是并没有描述这个进展有多快。因此进一步又提出了细分子类,以比较有实际意义的Wait-Free Population Oblivious级别来说,额外限制了每个参与线程必须要在预先可给出的明确执行周期内完成,且这个周期不能和与参与线程数相关。这一点明确拒绝了一些类似线程间协作的方案(这些方案往往引起较大的缓存竞争),以及一些需要很长很长的有限步来完成的设计。

    上图实例了一个典型的Wait Free Population Oblivious思路。进行临界区操作前,通过一个协同操作为参与线程分配独立的ticket,之后每个参与线程可以通过获取到的ticket作为标识,操作一块独立的互不干扰的工作区,并在其中完成操作。工业可用的Wait Free算法一般较难设计,例如ticket机制要求在协调动作中原子完成工作区分配,而很多数据结构是不容易做到这样的拆分的。时至今日各种数据结构上工业可用的Wait    Free算法依旧是一项持续探索中的领域。

    4.2 无锁不是万能的

    从非阻塞编程的角度看,上面的几类临界区处理方案优劣有着显著的偏序关系,即Wait Free > Lock Free > Mutual Exclusion。这主要是从阻塞适应性角度进行的衡量,原理上并不能直接对应到性能纬度。但是依然很容易给工程师造成一个普适印象,也就是『锁是很邪恶的东西,不使用锁来实现算法可以显著提高性能』,再结合广为流传的锁操作自身开销很重的认知,很多工程师在实践中会有对锁敬而远之的倾向。那么,这个指导思想是否是完全正确的?

    让我们先来一组实验:

    // 在一个cache line上进行指定步长的斐波那契计算来模拟临界区计算负载
    uint64_t calc(uint64_t* sequence, size_t size) {
        size_t i;
        for (i = 0; i < size; ++i) {
            sequence[(i + 1) & 7] += sequence[i & 7];
        }
        return sequence[i & 7];
    }
    {   // Mutual Exclusion
        ::std::lock_guard<::std::mutex> lock(mutex);
        sum += calc(sequence, workload);
    }
    {   // Lock Free / Atomic CAS
        auto current = atomic_sum.load(::std::memory_order_relaxed);
        auto next = current;
        do {
            next = current + calc(sequence, workload);
        } while (!atomic_sum.compare_exchange_weak(
                     current, next, ::std::memory_order_relaxed));
    }
    {   // Wait Free / Atomic Modify
        atomic_sum.fetch_add(calc(sequence, workload), ::std::memory_order_relaxed);
    }
    

    这里采用多线程累加作为案例,分别采用上锁后累加,累加后CAS提交,以及累加后FAA(Fetch And Add)提交三种方法对全局累加结果做临界区保护。针对不同的并发数量,以及不同的临界区负载,可以形成如下的三维曲线图。

    其中Latency项除以临界区规模进行了归一,便于形象展示临界区负载变化下的临界区保护开销趋势,因此跨不同负载等级下不具备横向可比性。Cycles项表示多线程协同完成总量为同样次数的累加,用到的CPU周期总和,总体随临界区负载变化有少量天然倾斜。100/1600两个截面图将3中算法叠加在一起展示,便于直观对比。

    从上面的数据中可以分析出这样一些信息

    1、基于FAA的Wait Free模式各方面都显著胜过其他方法;

    2、无锁算法相比互斥算法在平均吞吐上有一定优势,但是并没有达到数量级水平;

    3、无锁算法随竞争提升(临界区大小增大,或者线程增多),cpu消耗显著上升;

    基于这些信息来分析,会发现一个和之前提到的『锁性能』的常规认知相悖的点。性能的分水岭并没有出现在基于锁的互斥算法和无锁算法中间, 而是出现在同为『未使用锁』的Lock Free和Wait Free算法中间。而且从CPU消耗角度来看,对临界区比较复杂,竞争强度高的场景,甚至Lock Free因为『无效预测执行』过多反而引起了过多的消耗。这表明了锁操作本身的开销虽然稍重于原子操作,但其实也并非洪水猛兽,而真正影响性能的,是临界区被迫串行执行所带来的并行能力折损。

    因此当我们遇到临界区保护的问题时,可以先思考一下,是否可以采用Wait Free的方法来完成保护动作,如果可以的话,在性能上能够接近完全消除了临界区的效果。而在多数情况下,往往还是要采用互斥或Lock Free来进行临界区的保护。此时临界区的串行不可避免,所以充分缩减临界区的占比是共性的第一要务,而是否进一步采用Lock Free技术来减少临界区保护开销,讨论的前提也是临界区已经显著很短,不会引起过多的无效预 测。除此以外,由于Lock Free算法一般对临界区需要设计成两阶段提交,以便支持回滚撤销,因此往往需要比对应的互斥保护算法更复杂,局部性也可能更差(例如某些场景必须引入链表来替换数组)。综合来看,一般如果无法做到Wait Free,那么无需对Lock Free过度执着,充分优化临界区的互斥方法往往也足以提供和Lock Free相当的性能表现了。

    4.3 并发计数器优化案例

    从上文针对临界区保护的多种方法所做的实验,还可以发现一个现象。随着临界区逐渐减小,保护措施开销随线程数量增加而提升的趋势都预发显著,即便是设计上效率和参与线程数本应无关的Wait Free级别也是一样。这对于临界区极小的并发计数器场景,依旧会是一个显著的问题。那么我们就先从锁和原子操作的实现角度,看看这些损耗是如何导致的。

    首先给出一个典型的锁实现,左侧是锁的fast path,也就是如果在外层的原子变量操作中未发现竞争,那么其实上锁和解锁其实就只经历了一组原子变量操作。当fast  path检测到可能出现冲突时,才会进入内核,尝试进行排队等待。fast  path的存在大幅优化了低冲突场景下的锁表现,而且现代操作系统内核为了优化锁的内存开销,都提供了『Wait On Address』的功能,也就是为了支持这套排队机制,每个锁常态只需要一个整数的存储开销即可,只有在尝试等待时,才会创建和占用额外的辅助结构。

    因此实际设计中,锁可以创建很多,甚至非常多,只要能够达到足够细粒度拆解冲突的效果。这其中最典型的就是brpc中计数器框架bvar的设计。

    这是bvar中基础统计框架的设计,局部计数和全局汇聚时都通过每个tls附加的锁来进行临界区保护。因为采集周期很长,冲突可以忽略不记,因此虽然默认使用了大量的锁(统计量 * 线程数),但是并没有很大的内存消耗,而且运行开销其实很低,能够用来支持任意的汇聚操作。这个例子也能进一步体现,锁本身的消耗其实并不显著,竞争带来的软件或硬件上的串行化才是开销的核心。

    不过即使竞争很低,锁也还是会由一组原子操作实现,而当我们自己查看原子操作时,实际是由cache锁操作保护的原子指令构成,而且这个指令会在乱序执行中起到内存屏障的效果降低访存重叠的可能性。因此针对非常常用的简单计数器,在百度内部我们进行了进一步去除局部锁的改造,来试图进一步降低统计开销。

    例如对于需要同时记录次数和总和的IntRecorder,因为需要两个64位加法,曾经只能依赖锁来保证原子更新。但随着新x86机型的不断普及,在比较新的X86和ARM服务端机型上已经可以做到128bit的原子load/store,因此可以利用相应的高位宽指令和正确对齐来实现锁的去除。

    另一个例子是Percentile分位值统计,由于抽样数据是一个多元素容器,而且分位值统计需要周期清空重算,因此常规也是采用了互斥保护的方法。不过如果引入版本号机制,将清空操作转交给计数线程自己完成,将sample区域的读写完全分离。在这个基础上,就可以比较简单的做到线程安全,而且也不用引入原子修改。严格意义上,异步清空存在边界样本收集丢失的可能性,不过因为核心的蓄水池抽样算发本身也具有随机性,在监控指标统计领域已经拥有足够精度。

    除了运行时操作,线程局部变量的组织方式原先采用锁保护的链表进行管理,采用分段数据结合线程编号的方法替换后,做到空间连续化。最终整体进一步改善了计数器的性能。


    local count

    get value

    bvar::IntRecorder

    16

    7

    babylon::IntRecorder

    1377

    14

    bvar::Percentile

    938

    28

    babylon::Percentile

    66

    14

    4.4 并发队列优化案例

    另一个在多线程编程中经常出现的数据结构就是队列,为了保证可以安全地处理并发的入队和出队操作,最基础的算法是整个队列用锁来保护起来。

    这个方法的缺点是显而易见的,因为队列往往作为多线程驱动的数据中枢位置,大量的竞争下,队列操作被串行很容易影响整体计算的并行度。因此一个自然的改进点是,将队列头尾分开保护,先将生产者和消费者解耦开,只追加必要的同步操作来保证不会过度入队和出队。这也是Jave中LinkedBlockingQueue所使用的做法。

    在头尾分离之后,进一步的优化进入了两个方向。首先是因为单节点的操作具备了Lock Free化的可能,因此产生了对应的Michael & Scott无锁队列算法。业界的典型实现有Java的ConcurrentLinkedQueue,以及boost中的boost::lockfree::queue。

    而另一个方向是队列分片,即将队列拆解成多个子队列,通过领取token的方式选择子队列,而子队列内部使用传统队列算法,例如tbb:: concurrent_queue就是分片队列的典型实现。


    latency

    boost::lockfree::queue

    35075

    tbb::concurrent_queue

    4614

    对两种方式进行对比,可以发现,在强竞争下,分片队列的效果其实显著胜过单纯的无锁处理,这也是前文对于无锁技术真实效果分析的一个体现。

    除了这类通用队列,还有一个强化竞争发布,串行消费的队列也就是bthread::ExecutionQueue,它在是brpc中主要用于解决多线程竞争fd写入的问题。利用一些有趣的技巧,对多线程生产侧做到了Wait Free级别。

    整个队列只持有队尾,而无队头。在生产侧,第一步直接将新节点和当前尾指针进行原子交换,之后再将之前的队尾衔接到新节点之后。因为无论是否存在竞争,入队操作都能通过固定的两步完成,因此入队算法是Wait Free的。不过这给消费侧带来的麻烦,消费同样从一个原子交换开始,将队尾置换成nullptr,之后的消费动作就是遍历取到的单链表。但是因为生产操作分了两部完成,此时可能发现部分节点尚处于『断链』状态,由于消费者无从知晓后续节点信息,只能轮询等待生产者最终完成第二步。所以理论上,生产/消费算法其实甚至不是Lock Free的,因为如果生产者在两阶段中间被换出,那么消费者会被这个阻塞传播影响,整个消费也只能先阻塞住。但是在排队写入fd的场景下,专项优化生产并发是合理,也因此可以获得更好的执行效率。


    latency

    tbb::concurrent_queue

    4614

    bthread::ExecutionQueue

    2390

    不过为了能利用原子操作完成算法,bthread::ExecutionQueue引入了链表作为数据组织方式,而链表天然存在访存跳跃的问题。那么是否可以用数组来同样实现Wait Free的生产甚至消费并发呢?

    这就是babylon::ConcurrentBoundedQueue所希望解决的问题了。

    不过介绍这个队列并发原理之前,先插入一个勘误信息。其实这个队列在《内存篇》最后也简单提到过,不过当时粗略的评测显示了acquire- release等级下,即使不做cache line隔离性能也可以保障。文章发表后收到业界同好反馈,讨论发现当时的测试用例命中了Intel Write Combining 优化技术,即当仅存在唯一一个处于等待加载的缓存行时,只写动作可以无阻塞提前完成,等缓存行真实加载完毕后,再统一提交生效。但是由于内存序问题,一旦触发了第二个待加载的缓存行后,对于第一个缓存行的Write Combine就无法继续生效,只能等待第二个缓存行的写完成后,才能继续提交。原理上,Write Combine技术确实缓解了只写场景下的False Sharing,但是只能等待一个缓存行的限制在真实场景下想要针对性利用起来限制相当大。例如在队列这个典型场景下,往往会同时两路操作数据和完成标记,很可能同时处于穿透加载中,此时是无法应用Write Combine技术的。此外,能够在缓存行加载周期内,有如此充分的同行写入,可能也只有并无真实意义的评测程序才能做到。所以从结论上讲,通常意义上的多线程cache line隔离还是很有必要的。

    回到babylon::ConcurrentBoundedQueue的设计思路上,其实是将子队列拆分做到极致,将同步量粒度降低到每个数据槽位上。每个入队和出队  请求,首先利用原子自增领取一个递增的序号,之后利用循环数组的存储方式,就可以映射到一个具体的数据槽位上。根据操作是入队还是出队, 在循环数组上发生了多少次折叠,就可以在一个数据槽位上形成一个连续的版本序列。例如1号入队和5号出队都对应了1号数据槽位,而1号入队预期的版本转移是0到1,而5号出队的版本转移是2到3。这样针对同一个槽位的入队和出队也可以形成一个连续的版本变更序列,一个领到序号的具体操作,只需要明确检测版本即可确认自己当前是否可以开始操作,并通过自己的版本变更和后续的操作进行同步。

    通过同步量下放到每个元素的方式,入队和出队操作在可以除了最开始的序号领取存在原子操作级别的同步,后续都可以无干扰并行开展。而更连续的数据组织,也解决了链表存储的访存跳跃问题。生产消费双端可并发的特点,也提供了更强的泛用性,实际在MPMC(Multiple Producer Mult iple Consumer)和MPSC(Multiple Producer Single Consumer)场景下都有不错的性能表现,在具备一定小批量处理的场景下尤其显著。

    招聘信息

    欢迎出色的C++ 工程师加入百度,与大神一起成长。

    简历投递邮箱:

    rtad-recruit@baidu.com

    参考阅读

    原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿。

    2021年GIAC调整到7月30-31日在深圳举行,点击阅读原文了解更多详情。

    展开全文
  • 其中测试驱动开发可以优化代码设计,提高代码的可测试性,建立和代码同步增长的自动化测试用例,根据迭代积累的经验和需求变化情况对计划进行不断的调整和细化。 ● 文化建设方面:实行激励机制,通过建立激励制度,...
  • 此外,StradVision在2020年成为英伟达Inception计划的合作伙伴,借助公司自有专利的深度神经网络技术,SVNet可以针对任何硬件系统进行优化。 “与StradVision的合作扩大了我们在自动驾驶领域的环境感知能力。”...
  • 我国也相继出台了《中国制造2025》《关于深化“互联网+先进制造业”发展工业互联网的指导意见》等相关政策以促进工业互联网发展,进一步体现了工业数据治理的必要性。 1.3 工业数据治理探索 工业生产制造主要包括...
  • 计算机视觉是否已经进入瓶颈期?

    千次阅读 2020-09-27 09:33:57
    作者:周博磊 ...来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 其实这个问题也是我近段时间一直在思考的问题....我的观点是:计算机视觉在人工智能和深度学习的大背景下方兴未艾....
  • 会议明确了2019年药品上市后监管的重点任务,包括强化疫苗监管,强化疫苗批签发管理,加大对疫苗生产企业检查力度,挂牌督办疫苗违法案件;强化药品抽检和不良反应监测,强化网络售药监管,强化对高风险品种监管等。...
  • 8月1日,滨州市全市推进信息共享专项行动动员会议召开。据悉,经滨州市政府同意,市政府办公室印发了《滨州市深化“一次办好”改革推进信息共享专项行动实施方案》。方案要求,按照...
  • “互联网+政务服务”推进过程中存在着线上线下融合难、体制机制不完善、网上政务服务水平弱、发展环境亟待优化等问题,有必要从体制机制、基础支撑、人文关怀、法治环境等方面深入剖析这些痛点难点堵点的诱因,继而...
  • 二多情境教学法的内涵(一)课程教学内容通过对课程教学目标细化,以“突出重点”为原则,整合优化教学体系,围绕重要知识点和核心技术,创设不同的教学情境,通过应用实例将关键知识点集成起来,融入相应教材内容和...
  • 我们应该借国家对职业教育改革的契机,按照职业教育自身的特点和规律,不断创新办学理念,围绕“突出实用,强化技能,注重实践”的思路,在当前新形势,新理念,新要求的环境下,对高职院校现有的教学观念、方法和...
  • 但在金融云环境下,传统网络技术架构受到了挑战:一方面虚拟化思想的出现,颠覆了原有的数据中心网络模型,使得传统网络技术已不足以适配云环境下产生的新场景,如虚拟机的出现要求网络颗粒度从物理机细化到了虚拟机...
  • 团队能够自我定义和优化产品和流程,自主持续改善。团队精神饱满,士气高涨,凝聚力好,战斗力强。团队成为组织的扎实基石。 真正的自组织团队需要组织的整体设计,具备三个特征: 从组织层面来说是 低耦合 。至少...
  • 热门技术强化

    2009-12-21 13:37:56
    需要学员们去深化的一些的技术如下: ajax:包括它的一些常用框架,如DWR,JQUERY等。这里面,ajax的框架学起来是非常简单的,应用学习最多不会超过三个小时,最主要的是要了解ajax的基本理论和它的应用范围。ajax...
  • 文章目录发展背景政策背景国务院办公厅关于交通运输综合行政执法有关事项的通知(国办函〔2020〕123号)交通运输部关于印发《交通运输综合行政...《关于深化交通运输综合行政执法改革的指导意见》的通知(中办发[2018...
  • 海南自贸区电信行业环境分析

    千次阅读 2021-10-14 16:50:17
    2025年)》(以下合称“《总体方案》”)、《海南自由贸易港外商投资准入特别管理措施(负面清单)》(2020年版)(以下简称“《海南负面清单》”)等法规和文件细化了海南自由贸易港在投资自由便利方面的总体方案和...
  • 摘要:[目的/意义]从已颁布政策文件中找出我国推进政务大数据发展和应用的内在逻辑, 为优化未来的政策路径提供对策建议。[方法/过程]通过政府门户网站收集189条有效政策文本, 综合运用词频分析软件和人工方式提取...
  • 原来这就是笔杆子年终总结

    千次阅读 2022-01-14 13:36:29
    1. 一、狠抓学习固根本,锤炼内功强素质,...五、注重活力发展,全面深化改革创新 六、致力环境保护,抓好美丽XX建设 七、持续改善民生,加快推进民生工程 3. 一、突出抓责任、抓落实,在强化推进措施上下功夫 二
  • 二是要完善政策尺度,深化细化研究诸如充电价格机制、设施平台标准、充电停车一体化等问题,加强中央与企业投资政策对接,促进良性高质量发展;三是要强化充电基础设施在城市规划中的定位,加强车网协同发展、公私...
  • 5G在网络安全中的地位

    千次阅读 2021-11-20 14:47:14
    开展自主无人机安全底线的调查评估,研究安全底线的设置与实现方法,增强执行的可操作性,明确监管责任、细化监管措施,落实部署安全底线保障的系统性措施,确保自主无人机在底线安全之上能够始终“受控于人”。...
  • 深化电力需求侧管理,实现对分布式资源的实时采集与科学配置。同时为并网运行后,对大电网的调频、调峰、调压等做辅助支撑,缓解电网运行压力。 可视化界面采用图扑特有设计,沿用响应式布局进行划分排版,可以兼顾...
  • “写在前面:城市中心体系是制定并落实空间发展战略的核心内容,是优化空间结构的关键策略。城市中心体系与公共交通模式紧密耦合,是大伦敦空间发展战略的核心和标志性理念。首先,梳理大伦敦城镇中心网...
  • 推进教学工作诊断与改进试点,细化诊断改进工作方案。推动教学运行二级管理能力提升,协助完善绩效评价体系和工作机制,强化学校各层级管理系统间的质量依存关系,形成全要素网络化的内部质量保证体系。 10.加强各类...
  • 逐步深化无人系统产业“放管服”,发展新一代人工智能安全生态;充分发挥多无人系统协同的优势,赋能保障和改善民生,服务构建人类命运共同体。 一、前言 多无人系统协同在给社会创造价值的过程中存在着诸多安全隐患...
  • 艾瑞咨询于6月发布的《中国跨境电商服务行业趋势报告》作出了如下判断:面对复杂且多变的外部环境,我国跨境电商的服务企业应该以跨地区跨平台的形式拓宽资深的服务矩阵,通过深化服务价值强化自身的竞争壁垒。...
  • 数据治理管理小组根据业务管理部门的需求,及时制定...第十一条信息技术部门应加强对数据的监控,定期检测数据的存储,分析数据的构成,提出数据清理优化的方案,经业务管理部门确认后,定期进行垃圾数据的清理。...

空空如也

空空如也

1 2 3 4 5 ... 16
收藏数 310
精华内容 124
热门标签
关键字:

强化优化深化细化