Timing async functions in rust

huangapple go评论76阅读模式
英文:

Timing async functions in rust

问题

I would like to time async function calls in Rust. The crate tracing_timing does not appear adequate since it does not support async function calls. What I would like is that after the end of the running of the program I obtain statistics over the runtime use
一些 features 和代码一起的一个经验性解决方案是

    if cfg!(feature = "timings") {
        let before = SystemTime::now();
        let result = self.get_data(key).await;
        let after = SystemTime::now();
        println!("|get_data|={:?}", after.duration_since(before));
        result
    } else {
        self.get_data(key).await
    }

它适用于这个目的。这个 feature 在 Rust 中非常类似于 C++ 中的 #ifdef TIMINGS,并且定时信息会被写入一个日志文件,可以在运行后通过类似 Python 脚本查看。但这个解决方案有点直接借鉴了 C++,肯定 Rust 有一些更符合惯例的解决方案。

什么是 Rust 的符合惯例的解决方案?在哪里可以找到关于如何跟踪 Rust 代码和识别慢点的信息。有许多不同的 crates,它们之间的交互可能会很复杂。

英文:

I would like to time async function calls in Rust. The crate tracing_timing does not appear adequate since it does not support async function calls. What I would like is that after the end of the running of the program I obtain statistics over the runtime use
One solution which empirically works is to have some features with some code like

    if cfg!(feature = "timings") {
        let before = SystemTime::now();
        let result = self.get_data(key).await;
        let after = SystemTime::now();
        println!("|get_data|={:?}", after.duration_since(before));
        result
    } else {
        self.get_data(key).await
    }

It works well and fine for the purpose. The feature is very similar to a #ifdef TIMINGS in C++ and the timing information is written to a log file that can be looked at after the run by Python scripts of similar. But that solution is kind of direct adaptation of C++ and surely Rust must have some more idiomatic solutions.

What would be such a idiomatic rust solution? What would be the source of information on how to trace rust code and identify slow points. There are many different crates and their interaction can be complicated.

答案1

得分: 3

以下是您要翻译的内容:

There's tons of ways to do this, depending on which frameworks you're using. It also depends on what you want to time.

From scratch

If you want to know the duration between calling await and returning, like your example, you can easily create a wrapper function that does that.

pub async fn time_async<F, O>(f: F) -> (O, Duration)
where
    F: Future<Output = O>,
{
    let start = Instant::now();
    let out = f.await;
    let duration = start elapsed();
    (out, duration)
}

You should use Instant for finding durations. SystemTime is useful for knowing when something happens, but since it can change forwards or backwards at any moment (from your program's perspective), it's not suitable for finding how long something takes.

Then you can log it however you like.

pub async fn log_time_async<F, D, O>(f: F, name: D) -> O
where
    F: Future<Output = O>,
    D: Display,
{
    if cfg!(feature = "timings") {
        let (out, duration) = time_async(f).await;
        println!("|{name}|={duration:?}");
        out
    } else {
        f.await
    }
}

The ecosystem

tracing

I believe tracing's enter method would give you the above behavior when used naively. However, I don't think subscribers are usually set up to handle the overlapping spans this produces (you can have numerous instances of the function awaiting at the same time), so you would have to test that it works for your case.

pub async fn trace_async<F, O>(f: F, span: Span) -> O
where
    F: Future<Output = O>,
{
    let guard = span.enter();
    let out = f.await;
    drop(guard);
    out
}

If you want to know how much executor time a future takes—in other words, how much time is spent polling the future—then you need a more complex solution. Fortunately, tracing has a method just for that: instrument, as well as a macro.

// With the method
pub async fn trace_exec<F, O>(f: F, span: Span) -> O
where
    F: Future<Output = O>,
{
    f.instrument(span).await
}

// Or directly on the function
#[tracing::instrument]
pub async fn something_to_time() {
    something_slow().await;
}

These events can then be interpreted by a subscriber, of which there are many.

log

For logging, tracing can communicate with log. Details on that are in the documentation.

tokio

Tokio has a framework for logging called console that uses instrument spans to track active and idle time, but it's executor-specific (tokio) and fairly new.

All the code in playground

我已翻译好您提供的内容。

英文:

There's tons of ways to do this, depending on which frameworks you're using. It also depends on what you want to time.

From scratch

If you want to know the duration between calling await and returning, like your example, you can easily create a wrapper function that does that.

pub async fn time_async&lt;F, O&gt;(f: F) -&gt; (O, Duration)
where
    F: Future&lt;Output = O&gt;,
{
    let start = Instant::now();
    let out = f.await;
    let duration = start.elapsed();
    (out, duration)
}

You should use Instant for finding durations. SystemTime is useful for knowing when something happens, but since it can change forwards or backwards at any moment (from your program's perspective), it's not suitable for finding how long something takes.

Then you can log it however you like.

pub async fn log_time_async&lt;F, D, O&gt;(f: F, name: D) -&gt; O
where
    F: Future&lt;Output = O&gt;,
    D: Display,
{
    if cfg!(feature = &quot;timings&quot;) {
        let (out, duration) = time_async(f).await;
        println!(&quot;|{name}|={duration:?}&quot;);
        out
    } else {
        f.await
    }
}

The ecosystem

tracing

I believe tracing's enter method would give you the above behavior when used naively. However, I don't think subscribers are usually set up to handle the overlapping spans this produces (you can have numerous instances of the function awaiting at the same time), so you would have to test that it works for your case.

pub async fn trace_async&lt;F, O&gt;(f: F, span: Span) -&gt; O
where
    F: Future&lt;Output = O&gt;,
{
    let guard = span.enter();
    let out = f.await;
    drop(guard);
    out
}

If you want to know how much executor time a future takes—in other words, how much time is spent polling the future—then you need a more complex solution. Fortunately, tracing has a method just for that: instrument, as well as a macro.

// With the method
pub async fn trace_exec&lt;F, O&gt;(f: F, span: Span) -&gt; O
where
    F: Future&lt;Output = O&gt;,
{
    f.instrument(span).await
}

// Or directly on the function
#[tracing::instrument]
pub async fn something_to_time() {
    something_slow().await;
}

These events can then be interpreted by a subscriber, of which there are many.

log

For logging, tracing can communicate with log. Details on that are in the documentation.

tokio

Tokio has a framework for logging called console that uses instrument spans to track active and idle time, but it's executor-specific (tokio) and fairly new.

All the code in playground

huangapple
  • 本文由 发表于 2023年4月11日 03:00:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/75979890.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定