【问题标题】:Rust vs Go concurrent webserver, why is Rust slow here?Rust vs Go 并发网络服务器,为什么这里 Rust 慢?
【发布时间】:2020-11-26 18:34:54
【问题描述】:

我正在尝试对 Rust 书中的 multi-threaded webserver example 进行一些基准测试,为了进行比较,我在 Go 中构建了类似的东西,并使用 ApacheBench 运行了一个基准测试。虽然这是一个简单的例子,但差异太大了。 Go Web 服务器做同样的事情要快 10 倍。由于我期望 Rust 更快或处于相同水平,因此我尝试使用 futures 和 smol 进行多次修订(尽管我的目标是仅使用标准库比较实现)但结果几乎相同。这里的任何人都可以建议对 Rust 实现进行更改以使其在不使用大量线程数的情况下更快吗?

这是我使用的代码:https://github.com/deepu105/concurrency-benchmarks

tokio-http 版本最慢,其他 3 个 rust 版本的结果几乎相同

以下是基准:

Rust(8 个线程,100 个线程的数字更接近 Go):

❯ ab -c 100 -n 1000 http://localhost:8080/
This is ApacheBench, Version 2.3 <$Revision: 1879490 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests


Server Software:        
Server Hostname:        localhost
Server Port:            8080

Document Path:          /
Document Length:        176 bytes

Concurrency Level:      100
Time taken for tests:   26.027 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      195000 bytes
HTML transferred:       176000 bytes
Requests per second:    38.42 [#/sec] (mean)
Time per request:       2602.703 [ms] (mean)
Time per request:       26.027 [ms] (mean, across all concurrent requests)
Transfer rate:          7.32 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    2   2.9      1      16
Processing:     4 2304 1082.5   2001    5996
Waiting:        0 2303 1082.7   2001    5996
Total:          4 2307 1082.1   2002    5997

Percentage of the requests served within a certain time (ms)
  50%   2002
  66%   2008
  75%   2018
  80%   3984
  90%   3997
  95%   4002
  98%   4005
  99%   5983
 100%   5997 (longest request)

去:

ab -c 100 -n 1000 http://localhost:8080/
This is ApacheBench, Version 2.3 <$Revision: 1879490 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests


Server Software:        
Server Hostname:        localhost
Server Port:            8080

Document Path:          /
Document Length:        174 bytes

Concurrency Level:      100
Time taken for tests:   2.102 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      291000 bytes
HTML transferred:       174000 bytes
Requests per second:    475.84 [#/sec] (mean)
Time per request:       210.156 [ms] (mean)
Time per request:       2.102 [ms] (mean, across all concurrent requests)
Transfer rate:          135.22 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    2   1.4      2       5
Processing:     0  203 599.8      3    2008
Waiting:        0  202 600.0      2    2008
Total:          0  205 599.8      5    2013

Percentage of the requests served within a certain time (ms)
  50%      5
  66%      7
  75%      8
  80%      8
  90%   2000
  95%   2003
  98%   2005
  99%   2010
 100%   2013 (longest request)

【问题讨论】:

  • tokio_minihttp 只是一个概念证明,没有维护。书中的多线程 Web 服务器一章只是一个例子。 Go 标准库有一个很好的修改和优化的 http 实现。为了获得平等的比较,我会尝试使用 Hyper 或 Actix-Web。
  • 帖子不错,但基准很差。值得更新或关闭/删除。
  • @mh-cbon 什么意思,请详细说明一下
  • 因为尽管有些人会认为 ab 不是基准测试的正确工具,但至少该帖子包含清晰且可重复的内容。虽然因为 rust 版本不如应有的好,但它具有误导性。苹果和梨的比较。我在下面看到了你的答案。这改善了这一点。
  • 拜托这不是 SO 的重点,它是一个问题而不是一个陈述。如果 Rust 版本尽可能好,我就不必问这个问题了。

标签: concurrency rust webserver


【解决方案1】:

我只比较了您的“rustws”和 Go 版本。在 Go 中,您有无限的 goroutine(即使您将它们全部限制为只有一个 CPU 核心),而在 rustws 中,您创建一个具有 8 个线程的线程池。

由于您的请求处理程序每​​ 10 个请求休眠 2 秒,因此您将 rustws 版本限制为每秒 80/2 = 40 个请求,这就是您在 ab 结果中看到的。 Go 不会受到这种任意瓶颈的影响,因此它会向您显示单个 CPU 内核上的最大处理能力。

【讨论】:

  • 以下是我能想到的可以帮助解决给定瓶颈的方法:1. 异步代码 2. 线程 3. 进程。您可以将线程限制增加到 1000 之类的巨大值,然后看看情况如何。不过,线程不会像 goroutine 或异步代码那样高效。
  • 没错,实际上,根据 Reddit 上的评论,我能够从 rustws_async 示例 github.com/deepu105/concurrency-benchmarks/blob/main/… 中获得相同的性能
【解决方案2】:

我终于能够使用 async_std 库在 Rust 中获得类似的结果

❯ ab -c 100 -n 1000 http://localhost:8080/
This is ApacheBench, Version 2.3 <$Revision: 1879490 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests


Server Software:        
Server Hostname:        localhost
Server Port:            8080

Document Path:          /
Document Length:        176 bytes

Concurrency Level:      100
Time taken for tests:   2.094 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      195000 bytes
HTML transferred:       176000 bytes
Requests per second:    477.47 [#/sec] (mean)
Time per request:       209.439 [ms] (mean)
Time per request:       2.094 [ms] (mean, across all concurrent requests)
Transfer rate:          90.92 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    2   1.7      2       7
Processing:     0  202 599.7      2    2002
Waiting:        0  201 600.1      1    2002
Total:          0  205 599.7      5    2007

Percentage of the requests served within a certain time (ms)
  50%      5
  66%      6
  75%      9
  80%      9
  90%   2000
  95%   2003
  98%   2004
  99%   2006
 100%   2007 (longest request)

这里是实现

use async_std::net::TcpListener;
use async_std::net::TcpStream;
use async_std::prelude::*;
use async_std::task;
use std::fs;
use std::time::Duration;

#[async_std::main]
async fn main() {
    let mut count = 0;

    let listener = TcpListener::bind("127.0.0.1:8080").await.unwrap(); // set listen port

    loop {
        count = count + 1;
        let count_n = Box::new(count);
        let (stream, _) = listener.accept().await.unwrap();
        task::spawn(handle_connection(stream, count_n)); // spawn a new task to handle the connection
    }
}

async fn handle_connection(mut stream: TcpStream, count: Box<i64>) {
    // Read the first 1024 bytes of data from the stream
    let mut buffer = [0; 1024];
    stream.read(&mut buffer).await.unwrap();

    // add 2 second delay to every 10th request
    if (*count % 10) == 0 {
        println!("Adding delay. Count: {}", count);
        task::sleep(Duration::from_secs(2)).await;
    }

    let contents = fs::read_to_string("hello.html").unwrap(); // read html file

    let response = format!("{}{}", "HTTP/1.1 200 OK\r\n\r\n", contents);
    stream.write(response.as_bytes()).await.unwrap(); // write response
    stream.flush().await.unwrap();
}

【讨论】:

  • 当运行 10k 个请求时,Rust 异步实现总体上快了几秒钟。
  • 这个 rust 版本应该比 Go 版本更快,因为它并没有真正做同样的事情。它只是接受 TCP 连接并将文件写回,它并不像 Go 版本中那样代表真正的网络服务器。这种比较并不公平。
  • 那么如果我在 Rust 中使用像 hyper 这样的 HTTP 库会更公平吗?或者你可以为 Go 提出更好的建议?
  • 我用 Go 写了一个 TCP 版本,让它更公平。可能会有一个小的改进。我将不得不运行 ab 基准测试几次来确认github.com/deepu105/concurrency-benchmarks/blob/main/gows_tcp/…
  • 对于 10000 个请求和 100 个并发请求,http 版本比纯 TCP 版本快 0.1 秒,但仍然比 Rust 慢 0.1 秒
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-22
  • 1970-01-01
  • 2021-01-08
  • 2011-05-14
  • 1970-01-01
相关资源
最近更新 更多