写点什么

使用 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:216562
用户头像

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

关注

评论

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

书写开源之魂|2023年活力开源贡献者、开源项目揭晓

开放原子开源基金会

开源

终端闲思录(3)- 标准三剑客的本质

蓬蒿

终端 文件描述符

万能扫描仪整合软件:ExactScan pro免激活中文版

胖墩儿不胖y

Mac软件 扫描工具

Typora+PicGo 搭建免费图床

吳先森321

经验分享

一起学Elasticsearch系列-并发控制

Java随想录

Java 大数据 elastic 检索

边缘计算技术:深度学习与人工智能的融合

熬夜磕代码、

使用极狐GitLab Triage 来自动管理 Issue 和 MR

极狐GitLab

聊点个人成长那点破事儿

6点无痛早起学习的和尚

写作 21 天技术人写作行动营

从DevOps状态报告看技术团队的文化建设

京东科技开发者

一文掌握 Kubernetes 证书

SEAL安全

Kubernetes 云原生 Kubernetes 集群 企业号12月PK榜

大咖云集,2023开放原子开发者大会助力开发者实现梦想

开放原子开源基金会

开源

一款DC-DC控制器应用方案

芯动大师

《用“开源”的方式讲开源的法律,有问必答,一问到底》——开源合规分论坛为你答疑解惑

开放原子开源基金会

开源

边缘计算的深入学习之路

Geek-yan

深度盘点:除了BRC20外 这些公链潜力铭文也值得关注

BlockChain先知

KubeWharf:构建下一代分布式操作系统的云原生力量

不会算法。

前端 JS 安全对抗原理与实践

vivo互联网技术

前端加密 JS混淆 调试和反调试

领跑 AI 时代,开放原子开发者大会——2023龙蜥操作系统大会圆满举办

开放原子开源基金会

开源

QCN6274 and QCN9274: functional differences and application areas of wireless chips

wallysSK

深度盘点:除了BRC20外 这些公链潜力铭文也值得关注

大瞿科技

开源治理与开发者运营分论坛圆满举办

开放原子开源基金会

开源

多功能数据恢复软件:Apeaksoft Data Recovery激活中文

mac大玩家j

数据恢复软件 Mac软件 Mac数据管理

推动企业数智化国产替代 用友BIP献上中国方案

用友BIP

国产替代

开源赋能汽车智能化演进分论坛圆满举办

开放原子开源基金会

开源

软件开发

Geek_8da502

荣誉 | 第七在线(7thonline)荣获STIF2023年度数智化创新典范奖

第七在线

大模型 “下沉时刻”,容联云完成“三级跳”

脑极体

AI

技术创新,照见未来 | 2023开放原子开发者大会OpenHarmony分论坛圆满举行

开放原子开源基金会

开源

感谢同行者|携手相伴前行路,共筑开源创未来

开放原子开源基金会

开源

深度盘点:除了BRC20外 这些公链潜力铭文也值得关注

石头财经

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