写点什么

使用 Async 和 Await 的代价

  • 2011-10-13
  • 本文字数:2007 字

    阅读完需:约 7 分钟

异步技术能使得应用程序的总吞吐量得到显著提升,但这并不是无偿的。异步函数往往比其同步替代方案稍慢一些,而且如果您不介意采用它还会增加相当大的内存压力的话。Stephen Toub 最近在 MSDN 杂志中一篇题为“异步性能:了解 Async 和 Await 的代价”的文章中讨论了该主题。

相对于本机 C++ 代码而言,托管代码最显著的优势之一就是运行时内联函数(inline function)[1] 的能力。CLR 的 JIT 编译器甚至可以跨程序集内联函数,从而大大降低了调用细粒度方法(OOP 程序员偏爱此类方法)的开销。不幸的是,异步调用的本质意味着不能内联委托(delegates cannot be inlined)。此外,在建立异步调用时还包括不少样板代码。因此,这导致了 Stephen 的第一条建议,“考虑粗粒度,而非细粒度(Think Chunky, Not Chatty)”[2]。就像你正在穿越某个 COM 或 p/invoke 边界一样,相对于许多的小型异步调用而言,你应该会更喜爱少数的大型异步调用。

异步模式下无需开发者显式使用 new 运算符,即可通过多种方式分配内存。如果任其发展,这些内存分配法可能导致过大的内存压力,并且由于垃圾回收器尝试跟进还会导致不必要的延迟。考虑来自 Stream 子类的这个签名及其返回语句:

复制代码
public override async Task<int> ReadAsync()<br></br>return this.Read()

此处没有展示隐式创建 Task 对象,该对象用于包装从 Read 方法中返回的整型值。在 Stephen 的文章中,他展示了如何通过缓存最近的 Task对象及重用该对象来降低内存开销。

导致意外对象分配和保留的另一原因是使用闭包(closures)。C#和 VB 中的闭包是通过匿名类来实现的,匿名类包含匿名方法,而且在方法中声明了异步函数。那些匿名函数所需的本地变量据说被“封闭”(closed over)或“提升” (lifted)到该匿名类中。当每次调用匿名类的父方法时都必须创建一个该类实例。

问题并未就此结束,仍有可能使得额外的内存分配进一步恶化。通常情况下,局部变量所引用的对象是被热切请求的,垃圾回收器(GC)一旦明确那些局部变量在当前函数中将不再被使用时就会回收它们。由于在异步函数中所使用的“局部变量”实际上是某个匿名类中的字段,因此在调用期间它们必须被保留。如果此过程耗时数秒,这对于异步调用而言是很常见的,而该匿名类可能在不经意间被晋升为垃圾回收器中更昂贵的 1 代或 2 代对象 [3]。如果这成为问题,Stephen 建议一旦不再需要那些局部变量就应显式地把它们设置为空引用。

Stephen 所讨论的第三个问题是上下文的概念,特别是同步上下文(synchronization context)和执行上下文(execution context)。他在文章中展示了库代码如何通过使用ConfigureAwait 方法故意忽略同步上下文、以及避免某些必须在执行上下文中捕获的事情来获得性能提升的办法。

译注

[1] 内联函数(inline function),在不同的编程语言中,内联函数(inline function)是指已要求编辑器对其执行内联展开( inline expansion )的函数。换言之,程序员已要求编译器将每处调用某函数的地方都插入完整的函数体,而不是生成代码以便从其定义的地方调用该函数。可以使用 C99 或 C++ 编写内联函数,例如:

复制代码
inline int max(int a, int b)
{
return (a > b) ? a : b;
}

然后,调用语句如下:

复制代码
a = max(x, y);

该语句在编译后,可能被转换成为更直接的计算:

复制代码
a = (x > y) ? x : y;

详见 Inline function

[2] 考虑粗粒度,而非细粒度(Think Chunky, Not Chatty),Chunky 与 Chatty 之争此前多见于“服务协定设计”(service contract design)。唠叨的服务(Chatty Service)趋向于返回简化信息,并使用更细粒度的操作。矮胖的服务(Chunky Service)趋向于返回复杂层次信息,并使用粗粒度的操作。换言之,二者不同之处在于,当返回同样的信息时,唠叨的服务与矮胖的服务相比则需要更多的调用,却增加了返回实际需要的适当信息的灵活性。详见 WCF service contract design

[3] 1 代或 2 代对象,“代”是垃圾回收器用到的概念。提到垃圾回收器就不得不说“托管堆的简化模型”,该模型的规则如下:

  • 所有可进行垃圾回收的对象都分配在一个连续的地址空间范围(托管堆)内。
  • 堆被划分为代 (generation),以便只需查找堆的一小部分就能清除大多数垃圾。
  • 代中的对象大体上均为同龄。
  • 代的编号越高,表示堆的这一片区域所包含的对象越老——这些对象就越有可能是稳定的。最老的对象位于最低的地址内,而新的对象则创建在增加的地址内。
  • 新对象的分配指针标记了内存的已使用(已分配)内存区域和未使用(可用)内存区域之间的边界。
  • 通过删除死对象并将活对象转移到堆的低地址末尾,堆周期性地进行压缩。这就扩展了在创建新对象的图表底部的未使用区域。
  • 对象在内存中的顺序仍然是创建它们的顺序,以便于定位。
  • 在堆中,对象之间永远不会有任何空隙。
  • 只有某些可用空间是已提交的。需要时,操作系统会从“保留的”地址范围中分配更多的内存。

详见“垃圾回收器基础与性能提示”

查看英文原文: The Cost of Async and Await

2011-10-13 04:216601
用户头像

发布了 55 篇内容, 共 18.9 次阅读, 收获喜欢 1 次。

关注

评论

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

脑机接口简史——假如这篇推送是你靠意念打开的

脑极体

高承实:绘一幅区块链社会画像

CECBC

区块链

如何做Nginx安全日志分析可视化

运维研习社

nginx 4月日更 waf

PCB如何拼版

不脱发的程序猿

嵌入式 电路设计 硬件设计 4月日更 PCB打样

机器学习(二):理解线性回归与梯度下降并做简单预测

caiyongji

机器学习

「架构实战营」课堂作业-G20210698010384

张亮

区块链的环保实验,助全球提高垃圾回收

CECBC

环保

机器学习和大数据的区别和联系

大数据技术指南

机器学习 大数据 4月日更

怎么画出专业的架构图?

秋天

架构 架构师

什么是架构?怎么来理解?

秋天

架构 架构师

架构实战营 模块一作业

netspecial

架构实战营

架构实战营--模块一

永佳

架构实战营

重磅官宣:Nacos2.0发布,性能提升10倍

xcbeyond

Java 微服务 nacos 4月日更

go每日一库 [go-rate] 速率限制器

happlyfox

学习 Go 语言 4月日更

基于二叉树实现Map

Silently9527

Java 二叉树 数据结构与算法

脑机接口简史——假如这篇推送是你靠意念打开的

白洞计划

架构实战营-模块一作业

Sun

VUE2,基于vue-cli搭建创建vue项目

Chalk

Vue 大前端 4月日更

架构实战营——作业一:微信架构及学生管理架构

开拓纪

架构实战营 作业一

Linux grep 命令

一个大红包

4月日更

Wireshark数据包分析学习笔记Day25

穿过生命散发芬芳

Wireshark 数据包分析 4月日更

作业1--微信的业务架构及学生管理系统

大可

Scrum Patterns:每日Scrum(译)

Bruce Talk

敏捷开发 译文 Agile Scrum Patterns

Java最强规则引擎-ice是如何炼成的?

waitmoon

规则引擎使用 规则引擎 流程引擎

一文读懂区块链领域最新发展方向 NFT经济将成未来发展方向

CECBC

艺术品

为什么数据库字段要使用NOT NULL?

艾小仙

极客架构module 1 作业

Geek_649372

架构实战营

浅聊函数防抖与节流

程序员海军

JavaScript 大前端 防抖 节流

架构实战营 - 模块 1- 作业

请弄脏我的身体

架构实战营

聪明人的训练(五)

Changing Lin

4月日更

踩坑 MySQL 索引,看看你真的会用么?

架构精进之路

MySQL 4月日更

使用Async和Await的代价_.NET_Jonathan Allen_InfoQ精选文章