Effective Java(3rd)-Item48 在使流并行时要小心

  在主流语言中,Java一直处于提供便利来简化并发编程任务的最前沿。当Java在1996年发布时,它内置了对线程的支持,包括同步和wait/notify。Java 5引入了Java .util.并发库,具有并发集合和执行器框架。Java 7引入了fork-join包,这是一个用于并行分解的高性能框架。Java 8引入了流,它可以通过对parallel方法的单个调用并行化。用Java编写并发程序变得越来越容易,但是编写正确且快速的并发程序和以前一样困难。违反安全性和活性是并发编程中的一个事实,并行流管道也不例外。
  考虑项目45中的这个项目:


image.png

  在我的机器上,这个程序立即开始打印素数,运行12.5秒才能完成。假设我天真地试图通过向流管道添加一个parallel()调用来加速它。你认为它的表现会怎样?它会加快几个百分点吗?慢几个百分点?遗憾的是,它不会打印任何东西,但是CPU使用率会飙升到90%并无限期地停留在那里(活动失败)。这个项目可能最终会终止,但我不愿意去发现;半小时后我强行把它停了下来。
  这是怎么回事?简单地说,streams库不知道如何并行化这个管道,因此试探失败。即使在最好的情况下,如果源来自Stream.iterate,或者使用中间操作限制,那么并行化管道不太可能提高其性能。这个管道必须同时处理这两个问题。更糟的是,默认的并行化策略通过假设处理一些额外的元素和丢弃任何不需要的结果没有害处来处理极限的不可预测性。在这种情况下,找到每一个梅森质数所需的时间大约是找到前一个梅森质数所需时间的两倍。因此,计算单个额外元素的成本大约等于计算前面所有元素的总和,而这个看起来没什么问题的管道使自动并行算法陷入了瘫痪。这个故事的寓意很简单:性能结果可能是灾难性的。
  通常,并行性带来的性能收益在ArrayList、HashMap、HashSet和ConcurrentHashMap实例上的流上最好;数组;int范围;和long 的范围。这些数据结构的共同之处在于,它们都可以精确而廉价地划分为任意大小的子程序,这使得在并行线程之间划分工作变得很容易。streams库用于执行此任务的抽象是spliterator,它由Stream和Iterable上的spliterator方法返回。
  所有这些数据结构的另一个重要共同点是,当按顺序处理时,它们提供了从优秀到卓越的引用位置:顺序元素引用一起存储在内存中。这些引用引用的对象在内存中可能彼此不接近,从而降低了引用的位置。引用的位置对于并行化大量操作非常重要:如果没有引用,线程将花费大量时间空闲,等待数据从内存传输到处理器的缓存中。具有最佳引用局部性的数据结构是基本数组,因为数据本身连续地存储在内存中。
  流管道终端操作的性质也影响并行执行的效率。如果在终端操作中所做的大量工作与管道的总体工作相比,并且该操作本质上是顺序的,那么并行化管道的效率将有限。并行性的最佳终端操作是缩减,即使用Stream的其中一种缩减方法或预先打包的缩减(如min、max、count和sum)组合管道中出现的所有元素。短路操作anyMatch、allMatch和noneMatch也支持并行性。流的collect方法执行的操作(称为可变约简)不适合并行性,因为组合集合的开销很昂贵。
  如果您编写自己的流、可迭代的或集合实现,并且希望获得良好的并行性能,则必须覆盖spliterator方法,并广泛地测试结果流的并行性能。编写高质量的spliterator是困难的,超出了本书的范围。
  并行化流不仅会导致性能低下,包括活动失败;它可能导致不正确的结果和不可预测的行为。安全性的失败)。安全故障可能是由于并行化使用映射器、过滤器和其他程序员提供的函数对象的管道而导致的,这些函数对象不能遵守它们的规范。流规范对这些功能对象提出了严格的要求。例如,传递给Stream reduce操作的累加器和组合器函数必须是关联的、非干扰的和无状态的。如果您违反了这些要求(第46项中讨论了其中一些要求),但按顺序运行管道,则可能会得到正确的结果;如果您将其并行化,它很可能会失败,可能是灾难性的。
  沿着这些线,值得注意的是,即使并行化的Mersenne素数程序已经运行到完成,它也不会以正确的(升序)顺序打印素数。为了保持顺序版本显示的顺序,您必须用forEachOrdered替换forEach终端操作,该操作保证按偶遇顺序遍历并行流。
  即使假设您使用的是有效的可分割源流、可并行化或廉价的终端操作以及非干扰的函数对象,您也不会从并行化中获得良好的加速,除非管道做了足够多的实际工作来抵消与并行性相关的成本。作为一个非常粗略的估计,流中的元素数量乘以每个元素执行的代码行数至少应该是100,000 [Lea14]。
  重要的是要记住,并行化流严格来说是一种性能优化。与任何优化一样,您必须在更改之前和之后测试性能,以确保这是值得做的(第67项)。理想情况下,您应该在实际的系统设置中执行测试。通常,程序中的所有并行流管道都在公共fork-join池中运行。一个行为不端的管道可能会损害系统中其他不相关部分的性能。
  如果在并行化流管道时,您可能会遇到一些困难,那是因为它们确实存在。我的一个熟人维护着一个大量使用流的数百万在线代码库,他发现只有少数几个地方并行流是有效的。这并不意味着您应该避免并行化流。在适当的环境下,只要向流管道添加一个并行调用,就可以实现处理器内核数量的近乎线性的加速。某些领域,如机器学习和数据处理,特别适合这些加速。
  作为一个简单的例子,一个流管道并行性是有效的,考虑这个函数计算π(n),质数数目小于或等于n:

image.png

  在我的机器上,需要31秒计算π(108)使用这个函数。简单地添加parallel()调用可以将时间缩短到9.2秒:


image.png

  换句话说,在我的四核计算机上,并行化计算速度提高了3.7倍。值得注意的是,这不是你如何计算π(n)为大n的值。还有更有效的算法,尤其是Lehmer公式。
  如果您要并行化一个随机数流,请从SplittableRandom实例开始,而不是ThreadLocalRandom(或者本质上已经过时的random)。SplittableRandom正是为这种用途而设计的,并且具有线性加速的潜力。hreadLocalRandom是为单线程使用而设计的,它将适应作为并行流源的功能,但不会像SplittableRandom那样快。Random对每个操作都进行同步,因此它会导致过多的并行性争用。
  总之,除非您有充分的理由相信流管道可以保持计算的正确性并提高其速度,否则甚至不要尝试并行化流管道。不适当地并行流的代价可能是程序失败或性能灾难。如果您认为并行性是合理的,那么确保您的代码在并行运行时保持正确,并在实际条件下进行仔细的性能度量。如果您的代码仍然正确,并且这些实验证实了您对性能提高的怀疑,那么,并且只有在那时并行化。
本文写于2019.7.16,历时1天

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容

  • [{"reportDate": "2018-01-23 23:28:49","fluctuateCause": n...
    加勒比海带_4bbc阅读 767评论 1 2
  • 目录: Android:Android 0.*Android 1.*Android 2.*Android 3.*A...
    敲代码的令狐葱阅读 3,846评论 0 2
  • 情绪猖狂的夜, 有多少罪孽, 没来得及降解, 欲望就妥协, 藏起胆怯, 准备好细节, 避免虚妄被撕裂。
    心跳念白阅读 176评论 0 0
  • 他叫马地,农民的儿子。 他的父亲对于土地有着别人难以理解的深厚感情,给孩子起名字,都用土地的名字来命名,以示不忘本...
    东方地秀阅读 419评论 2 3
  • 姐等了好久的《天盛长歌》昨晚终于上线啦!昨天被它刷屏了有木有? 在之前看见陈坤、倪妮、赵立新、倪大红、白敬亭等神仙...
    娱乐拆穿姐阅读 433评论 0 2