kotlin 扩展类的功能_跟踪Kotlin悬挂功能的性能

kotlin 扩展类的功能

Using Firebase Performance Monitoring to track the performance of a plain-ol’ function (Java or Kotlin) is drop-dead simple using the @AddTrace annotation:

使用@AddTrace 批注,使用Firebase Performance Monitoring跟踪简单功能的性能(Java或Kotlin)非常简单:

@AddTrace("some-trace-name")
fun myBoringFunction() {
println("nothing to see here!")
}

Wait a few moments, and you’ll see some-trace-name in the Firebase Console!

稍等片刻,您将在Firebase控制台中看到some-trace-name

But what if you want to track the performance of a suspend function? Unfortunately, this is not as straightforward.

但是,如果您想跟踪suspend功能的性能怎么办? 不幸的是,这并不是那么简单。

尝试#1:悬疑 (Attempt #1: Suspending Disbelief)

Let’s say you have some uncomplicated suspending function, like so:

假设您有一些简单的暂停功能,如下所示:

suspend fun myAsyncFunction() : Int {
val result = 1 + 1
delay(100L)
println("something to see here: $result")
return result
}

A naive attempt to track this function’s performance would, as you might expect, to use @AddTrace:

如您所料,天真的尝试跟踪此函数的性能将使用@AddTrace

@AddTrace("some-other-trace-name")
suspend fun myAsyncFunction() : Int { ... }

Just eyeballing it, it should work, right? Even if you compile, the firebase-perf Gradle plugin won’t complain, and the Firebase Performance Monitoring library won’t crash your app. So maybe everything will Just Work™️? 🤞🏽

只是盯着它,它应该起作用,对吗? 即使您进行编译, firebase-perf Gradle插件也不会抱怨,并且Firebase Performance Monitoring库不会使您的应用程序崩溃。 所以也许一切都会正常工作吗? 🤞🏽

Plot Twist: it won’t work.

剧情扭曲:它将不起作用。

To understand why it doesn’t work, you’ll need to understand two things:

要了解为什么它不起作用,您需要了解两件事:

  1. How the Kotlin compiler produces a suspending state machine

    Kotlin编译器如何产生挂起状态机
  2. How the firebase-perf Gradle plugin instruments the @AddTrace annotation into an actual Trace invocation

    @AddTrace firebase-perf Gradle插件如何将@AddTrace批注检测到实际的Trace调用中

For #1, Manuel Vivo has a great article going into the nuts and bolts of what the suspend keyword actually means, and if you’re not familiar with the process, I recommend checking it out. For our purposes, we’ll simplify, and say that the Kotlin compiler will rewrite your suspend method signature, appending a Continuation<T> parameter, which allows it to build the state machine.

对于#1, Manuel Vivo撰写了一篇很棒的文章 ,深入探讨了suspend关键字的实际含义 ,如果您不熟悉此过程,建议您进行检查。 就我们的目的而言,我们将进行简化,并说Kotlin编译器将重写您的suspend挂方法签名,并附加一个Continuation<T>参数,该参数允许它构建状态机。

For #2, I don’t work at Google, so I’m not entirely sure how the @AddTrace instrumentation works under the hood. But I’ve got some guesses!

对于#2,我不在Google工作,所以我不确定@AddTrace工作原理。 但是我有一些猜测!

Let’s take our previous example and “compile” it. We’ll make 3 changes:

让我们以前面的示例进行“编译”。 我们将进行3个更改:

  1. Simplify the “guts” of the coroutine state machine

    简化协程状态机的“胆量”
  2. Add some “fake” Perf traces

    添加一些“假” Perf痕迹
  3. “Decompile” everything into Kotlin source code, because nobody wants to read Kotlin JVM bytecode!

    将所有内容“反编译”为Kotlin源代码,因为没人愿意读取Kotlin JVM字节码!
fun myAsyncFun(cont : Continuation) {
val perf = FirebasePerformance.getInstance()
val trace = perf.newTrace("some-other-trace-name")
trace.start() val result = 1 + 1
//begin: coroutine state machine
when(cont.label){
0 -> {
//will call myAsyncFun with cont.label=1
DelayKt.delay(100, cont)
}
1 -> {
println("something to see here: $result")
//hands control+result back to caller
cont.resume(result, ...)
}
}
//end: coroutine state machine
trace.end()
}

The coroutine’s state machine is muddying the waters — it’s no wonder Firebase’s automated instrumentation can’t figure out where to put the trace.end()! What’s a performance-minded developer to do?

协程的状态机使水变得浑浊–难怪Firebase的自动化仪器无法弄清楚trace.end()放置trace.end() ! 注重性能的开发人员该做什么?

尝试2:如果尝试,最终您会成功 (Attempt #2: If you Try, Finally you’ll succeed)

The heart of the issue is that the Firebase performance instrumentation can’t query suspending Kotlin bytecode for the method entry and exit. But, as developers, we know when a suspending function will enter and exit; in fact, there’s even a Java/Kotlin paradigm to codifies this: try/finally

问题的核心是Firebase性能工具无法查询挂起的Kotlin字节码以获取方法的进入和退出。 但是,作为开发人员, 我们知道挂起函数何时进入和退出。 实际上,甚至有一个Java / Kotlin范例可以对此进行编纂: try/finally

try/finally is exactly what we need: no matter what happens inside a method body, finally will always be executed last (or, finally!) — this is a guarantee from the JVM, and you can see this reflected in any compiled Java/Kotlin bytecode. Read more here

try/finally正是我们所需要的:无论方法主体内部发生什么, finally都将总是finally执行(或finally!)—这是JVM的保证,您可以在任何已编译的Java /中看到这一点。 Kotlin字节码。 在这里阅读更多

Armed with this knowledge, and a little bit of Kotlin magic✨, we can write a little wrapper that can be used anywhere:

有了这些知识,再加上一点Kotlin的魔力,我们可以编写一个可以在任何地方使用的包装器:

inline fun <E> trace(name : String, block: (Trace) -> E): E {
val trace = startTrace(name) //creates & starts a new Trace
return try {
block(trace)
} finally {
trace.stop()
}
}

Breaking it down:

分解:

  • the inline keyword means we won’t interfere with any coroutine state machine; the entire suspending invocation will occur in the try {} block

    inline关键字表示我们不会干扰任何协程状态机; 整个挂起的调用将在try {}块中进行

  • startTrace just invokes FirebasePerformance, names the trace, and starts it.

    startTrace只是调用FirebasePerformance ,命名跟踪,然后启动它。

  • we accept a block: (Trace) -> E, so that the instrumented code can append information to the Trace, if needed

    我们接受一个block: (Trace) -> E ,以便如果需要的话,检测到的代码可以将信息追加到Trace中

  • in the finally block, we stop the trace, and return value produced by block

    finally块中,我们停止跟踪,并返回由block产生的值

  • any exceptions are propagated to the calling code, since there’s no catch defined

    由于没有定义catch ,任何异常都会传播到调用代码

Sweet; simple; and, most importantly, it works!

甜; 简单; 而且,最重要的是,它有效!

Let’s rewrite our first suspending function using our new toy:

让我们使用新玩具重新编写第一个暂停函数:

suspend fun myAsyncFunction() : Int {
val result = 1 + 1
delay(100L)
println("something to see here: $result")
return result
}

becomes:

变成:

suspend fun myAsyncFunction() : Int {
return trace("some-other-trace") {
val result = 1 + 1
delay(100L)
println("something to see here: $result")
return@trace result
}
}

This is our solution! If you want to achieve this same result without nesting your method into a lambda, you can push it into a private function:

这是我们的解决方案! 如果要在不将方法嵌套到lambda的情况下实现相同的结果,则可以将其推送到私有函数中:

suspend fun myAsyncFunction() : Int {
return trace("some-other-trace") { _myAsyncFunction() }
}private suspend fun _myAsyncFunction() : Int {
val result = 1 + 1
delay(100L)
println("something to see here: $result")
return result
}

The Firebase Performance Monitoring Console will now show the total time myAsyncFunction runs, including the time that any actual suspension takes 🎉

Firebase Performance Monitoring Console现在将显示myAsyncFunction运行的总时间,包括任何实际暂停所需的时间。

Happy coding!

编码愉快!

下一步 (Next Steps)

I’ve filed a feature request with Firebase to augment @AddTrace to support suspending functions. Let’s see what happens, maybe this blog post will be obsolete in 6 months 👀

我已经向Firebase提出了功能请求,以增强@AddTrace以支持挂起功能。 让我们看看会发生什么,也许这篇博客文章将在6个月内过时👀

Thanks to Akshay Chordiya and Doug Stevenson for reviewing the early drafts! 🙏🏾

感谢 Akshay Chordiya Doug Stevenson 审阅了初稿! 🙏🏾

Note: this story was originally published on my personal website: https://jvmname.dev/posts/2020/03/tracking-performance-in-kotlin-suspending-functions/

注意:该故事最初发布在我的个人网站上: https : //jvmname.dev/posts/2020/03/tracking-performance-in-kotlin-suspending-functions/

资料来源 额外阅读 (Sources | Extra Reading)

翻译自: https://medium.com/firebase-developers/tracking-performance-in-kotlin-suspending-functions-c81c01f87c92

kotlin 扩展类的功能

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值