50万奖金+官方证书,深圳国际金融科技大赛正式启动,点击报名 了解详情
写点什么

如何让代码并发效率更高

  • 2013-08-13
  • 本文字数:2197 字

    阅读完需:约 7 分钟

随着计算硬件的快速发展,多核多处理器已经广泛应用于企业和个人环境中,开发人员利用多线程技术努力提高软件的计算速度,资深系统架构师 Gurudutt Kumar总结了如何让代码并发效率更高的实践经验。

Gurudutt 首先列举了几种影响软件可伸缩性的问题:

  • 效率低下的并行化:单片应用程序或软件无法有效使用可用的计算资源。您需要将应用程序组织成并行任务。在传统的不支持多线程的应用程序或软件中,我们会经常看到这个问题。这些应用程序在多核、多处理器、芯片多线程硬件上无法伸缩,并且无法实现更好的吞吐量。线程太多可能会和线程太少一样,都不会产生好的结果。
  • 串行瓶颈:在多个线程或进程之间共享数据结构的应用程序可能会有串行瓶颈。为了保持数据完整性,可能必须使用锁定和串行化技术(例如,读取锁、读写锁、写入锁、自旋锁、互斥等)将这些共享数据结构的访问串行化。设计得效率低下的锁可能会由于多个线程或进程之间的高度锁争用而导致串行瓶颈,从而尝试获取锁。这可能会潜在地降低应用程序或软件的性能。应用程序的性能可能会随着核心或处理器数量的增加而降低。
  • 对操作系统 (OS) 或运行时环境的过度依赖:您不能依赖操作系统、运行时环境或编译器来完成伸缩应用程序或软件所需的一切操作。但是,编译器和运行时环境可以帮助提供一定的优化,您不能依赖它们解决所有可伸缩性问题。例如,不能依赖 Java™ 虚拟机 (JVM) 通过自动并行来发现 Java 应用程序的最佳可伸缩的机会。
  • 工作负载的不平衡可能是一个瓶颈:工作负载的不均匀分布可能导致无法有效地利用计算资源。您可能必须将较大的任务划分成可以并行运行的较小的任务,还可能必须将串行算法更改为并行算法,以便提高性能和可伸缩性。
  • I/O 瓶颈:由于阻止磁盘输入 / 输出 (I/O) 或高网络延迟而导致的瓶颈可能会严重抑制应用程序的可伸缩性。
  • 无效的内存管理:在多核平台上,因为有很多处理单元,因此纯计算可能非常廉价,并且主要内存可能也不是问题,因为它正在变得越来越大。但是,内存带宽一直是一个瓶颈,因为所有处理器核心都贡献了一个通用的总线。无效的内存管理可能导致一些难以检测到的性能问题,比如伪共享。

以“避免内存争用”为例,Gurudut 做了解释:

在内存和缓存中,各种不同的核共享一个通用的数据区域,这需要在它们之间进行同步。当不同的核同时访问同一个数据区域时,会发生 _ 内存争用 _。在不同的核之间同步数据会因总线通信、锁定成本以及缓存缺失而有很大的性能损失。如果应用程序有多个线程,并且所有线程都更新或修改同一个内存地址,那么正如前面部分所讨论的那样,为了保持缓存一致性,可能会产生一次重大的乒乓效应。这会导致性能降低。

如何避免内存争用呢?Gurudut 认为“不要在核之间共享可写入的状态”:

  • 为了最大程度地减少内存总线通信,可以通过最小化共享位置 / 数据尽可能地减少核心交互,即使共享数据没有锁保护,而有一些硬件级别原子指令(如 Microsoft® Windows® 32 位平台上的 InterlockedExchangeAdd64)保护也是如此。
  • 减少线程之间的内存争用的一个方法是从多个线程中消除对共享内存区域的更新。例如,即便是在多个线程需要更新全局计数器或累计总数(如统计数据)时,各个线程也可以保持线程本地总数,并让全局总数仅在需要时通过一个通用的线程进行更新。因此,在共享内存区域上的争用会大大减少。
  • 趋向于减少锁争用的模式会减少内存通信,因为它是一个共享的可写入状态,该状态需要使用锁并产生争用。

对于锁争用,Gurudut 认为要从“避免”和“减少”两个方面来解决这个问题:

避免

  • 避免在数据结构中发生锁争用的方法之一是采用并发数据结构设计和无锁算法,这会消除锁以及传统的同步技巧(比如互斥)。有多种并发数据结构的设计并不需要利用同步机制,比如互斥。
  • 无锁算法的一些示例如下:
    • 使用 相对论编程 的可伸缩并发哈希表:该技巧的最简单示例是 Read Copy Update (RCU) ,它专用于 Linux 内核,大大提高了 Linux 内核的性能,并简化了 Linux 内核的代码。
    • 无锁可扩展有序分割的哈希列表:这个无锁递归可扩展哈希算法使用了无锁的链接列表,这些列表使用原子指令来修改链接的列表。
  • 在 Linux 内核中,广泛使用了每处理器变量,系统上的每个处理器都获得了自己的一个给定变量的副本。访问每处理器变量不需要使用锁,此外,因为在不同的处理器上,这些变量未在线程之间共享,因此没有伪共享或内存争用。这种技巧非常适合收集统计信息。

减少

  • 当使用传统锁或同步技巧(如自旋锁)时,必须注意的是,不要使用单片锁或全局锁,而是将这些锁分成更细小的部分。因此,锁会保护数据结构中的某个特定区域以及较小的区域。这样多个线程就能够通过获取保护这些成员的相应锁,在同一数据结构的不同成员上并发进行操作。这种方法可以实现更多并发。
  • 甚至当软件设计中的同步机制能够实现更好的并发和减少锁争用时,也可能会由于伪共享而导致发生性能问题。例如,考虑一个哈希数据结构。如果存在一个自旋锁数组,用于保护哈希中的每个哈希桶,那么在自旋锁数组中可能会出现伪共享。两个线程在两个不同的处理器上运行,每个线程都锁定哈希中的不同哈希桶,那么当它们所需的自旋锁位于同一个缓存行上时,可能会发生伪共享。因此,在设计此类算法时需要考虑采用避免发生伪共享的通用技巧。

作者的微信公众号“老崔瞎编”,关注 IT 趋势,承载前沿、深入、有温度的内容。感兴趣的读者可以搜索 ID:laocuixiabian,或者扫描下方二维码加关注。

2013-08-13 03:156543
用户头像

发布了 501 篇内容, 共 281.0 次阅读, 收获喜欢 64 次。

关注

评论

发布
暂无评论
发现更多内容

指挥中心情指勤一体化解决方案,河北公安情指勤一体化建设

5分钟速读之Rust权威指南(二十八)RefCell<T>

wzx

rust

“半监督”、“自监督”怎么用?| 算法深度剖析与实战分享

网易易盾技术团队

AI 算法 算法实践 实践案例 深度半监督

新华三商用终端新品全系入市,重塑办公极致体验

科技热闻

从渗透测试小白到网络安全大佬的成长之路

学神来啦

Linux 运维 网络安全 渗透测试

鉴释×CSDN丨国内外操作系统生态差异在哪?

鉴释

操作系统

信息安全与网络安全的关系

网络安全学海

程序员 网络安全 安全 信息安全 渗透测试

ES6 迭代器简述

编程三昧

JavaScript 大前端 ES6 迭代器

英特尔宋继强:异构计算的关键一环,先进封装已经走向前台

E科讯

dubbogo 社区负责人于雨说

apache/dubbo-go

dubbo dubbo-go dubbogo

网络抓包实战05——深入浅出连接关闭

青春不可负,生活不可欺

区块链如何赋能智慧城市

CECBC

【MindSpore有奖活动】资讯内容宝藏多,编译安装试一波!

Geek_6cdeb6

网络抓包实战06——灵异事件的始作俑者:Reset数据包

青春不可负,生活不可欺

[译] R8 优化:方法的 Outlining 优化

Antway

6月日更

浅谈B端产品的表单元素设计

LigaAI

产品经理 UI 产品设计与思考

虚拟货币监管再加码:央行约谈部分金融机构 要求切断支付链路

CECBC

产业互联网时代的数字化转型与创新

CECBC

网络抓包实战04——深入浅出连接建立

青春不可负,生活不可欺

Redis入门五:主从复制

打工人!

redis 主从复制 6月日更

定点数与浮点数表示

若尘

浮点数 计算机组成原理 6月日更

Java的函数式接口

中原银行

Java 函数式接口 中原银行

Java线程状态与状态间的切换

wzh

Java 线程 JVM 操作系统 并发

网络攻防学习笔记 Day53

穿过生命散发芬芳

网络攻防 6月日更

Jenkins 如何与 Kubernetes 集群的 Tekton Pipeline 交互?

张晓辉

Kubernetes 云原生 jenkins Tekton CI/CD

算法有救了!GitHub上神仙项目手把手带你刷算法,Star数已破110k

Java架构师迁哥

网络抓包实战03——TCP/IP协议栈:数据包如何穿越各层协议

青春不可负,生活不可欺

一文带你了解什么是HTTP协议

网络安全学海

网络安全 安全 信息安全 HTTP 渗透测试

Java内存模型

wzh

Java JVM happens-before 并发 Java内存模型

值得收藏的15个JavaScript语句

devpoint

JavaScript array 6月日更

知乎上线1小时,5w浏览量被下架的JVM全解笔记,内容太强大

Java架构师迁哥

如何让代码并发效率更高_语言 & 开发_崔康_InfoQ精选文章