< 返回版块

豆沙饼 is Louys 发表于 2022-11-04 09:30

Tags:tokio

问题在于

let mut buf = vec![0; 1024];
vs
let mut buf = [0;1024];

的区别, 我理解是[u8;1024]即非vec!,他们有性能差别吗? tokio的task管理里边,当task挂起让出线程权,或者恢复task重新运行。 vec![0;1024] 与 [0;1024]是否有差别? 是否使用[0;1024],会使task任务的体积更加大?影响task 在线程间的转移,或者任务挂起,或者任务恢复?

use tokio::io::{self, AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;

#[tokio::main]
async fn main() -> io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:6142").await?;
    loop {
        let (mut socket, _) = listener.accept().await?;
        tokio::spawn(async move {
            let mut buf = vec![0; 1024]; //===疑问行=== 与[0; 1024]差别是否有性能差别?
            loop {
                match socket.read(&mut buf).await {
                    Ok(0) => return,
                    Ok(n) => {
                        if socket.write_all(&buf[..n]).await.is_err() {return;}
                    }
                    Err(_) => {return;}
                }
            }
        });
    }
}

突然觉得自己这个问题有点傻,按照rust的设计是要高性能,task在线程间移动应该是以引用的形式移动的, 因此我觉得vec![0;1024] 与 [0;1024]性能上是一样的

评论区

写评论
苦瓜小仔 2022-11-04 20:30

这中间的细节我不是特别清楚,所以也不要以我的说法为准。

苦瓜小仔 2022-11-04 20:23

task 在线程间移动应该是以引用的形式移动的

准确说应该是“指针”,因为“引用”在 Rust 中特指 &&mut。当然,引用属于“指针类型”。

通常把 Futures 放置在堆上,所以这里的 [u8; 1024] 或许不在栈上,而是在堆上内联,类似于 Pin<Box<Task1>>

struct Task1 {
  buf: [u8; 1024]
  ...
}

Box<[u8; 1024]> 相比于 Box<Vec<u8>> 带来的少许优势,比如更少的边界检查(因为长度不可变)、直接性(因为前者的指针直接指向数据,而后者还要经过 Vec 背后的指针)。


对原始回答纠正一下: 第一条是“移动” [u8; 1024] 的成本,而不是 “复制”;Vec 的大小始终为 24 bytes,而不是 bits。。。

aj3n 2022-11-04 18:41

只针对这个问题:

突然觉得自己这个问题有点傻,按照rust的设计是要高性能,task在线程间移动应该是以引用的形式移动的, 因此我觉得vec![0;1024] 与 [0;1024]性能上是一样的

一个task开始运行以后只能以(Pin<SomePtr>)的形式存在的,所以是的在不同线程间调度是以引用的形式移动的;

作者 豆沙饼 is Louys 2022-11-04 11:30

谢谢大哥慷慨回复

👇
苦瓜小仔: 异步编程很少把这么大的数组当作缓冲 —— 不是因为数组性能不好,而是因为它没法满足需求。

此外,性能考量来自于你对你的代码的 benchmark。

当然栈上的数据会比堆在缓存上更优,读取和修改更快,但你还有更重要的一些问题没有考虑:

  1. 复制 [u8; 1024] 的成本:Rust 的移动语义实质上是位拷贝(当然这可以被优化掉),但无论怎样,携带这么大的数据的任务的体积会更加大(当然有可能任务不被移动),而此时 Vec 的大小始终为 24 bits,移动的成本相比而言很便宜
  2. 复制数组和 Vec 意味着也要复制连续存储的数据(导致内存增加、分配增加),在异步编程充满 : 'static bound 的情况下,不得不考虑支持多所有权的数据结构(不通过引用来访问同一块数据),比如 Arc<[T]> 或者 Arc<Mutex<Vec>>,因为复制它,仅仅增加计数,复制和销毁的成本相比而言很便宜(但修改数据的情况下,锁会增加性能阻碍)
  3. 如果追求缓冲数据结构的零拷贝,你还有其他选择,比如 bytes
苦瓜小仔 2022-11-04 11:21

异步编程很少把这么大的数组当作缓冲 —— 不是因为数组性能不好,而是因为它没法满足需求。

此外,性能考量来自于你对你的代码的 benchmark。

当然栈上的数据会比堆在缓存上更优,读取和修改更快,但你还有更重要的一些问题没有考虑:

  1. 复制 [u8; 1024] 的成本:Rust 的移动语义实质上是位拷贝(当然这可以被优化掉),但无论怎样,携带这么大的数据的任务的体积会更加大(当然有可能任务不被移动),而此时 Vec 的大小始终为 24 bits,移动的成本相比而言很便宜
  2. 复制数组和 Vec 意味着也要复制连续存储的数据(导致内存增加、分配增加),在异步编程充满 : 'static bound 的情况下,不得不考虑支持多所有权的数据结构(不通过引用来访问同一块数据),比如 Arc<[T]> 或者 Arc<Mutex<Vec>>,因为复制它,仅仅增加计数,复制和销毁的成本相比而言很便宜(但修改数据的情况下,锁会增加性能阻碍)
  3. 如果追求缓冲数据结构的零拷贝,你还有其他选择,比如 bytes
1 共 5 条评论, 1 页