您能有效地将迭代器< T>转换为迭代器< &T>吗?

qyyhg6bp  于 2022-09-21  发布在  Unix
关注(0)|答案(2)|浏览(166)

我遇到了两个令人沮丧的问题,它来自于想要从nix-crate使用sendmmsg在Unix套接字上发送消息。

我有一些给定的消息,可能包含也可能不包含FDS。NIX做的大多数事情都是零拷贝的,这使得它有时很难使用,这让你不得不与借用检查器和类型系统作斗争,这两个问题都来自这个函数签名:

pub fn sendmmsg<'a, I, C, S>(
    fd: RawFd,
    data: impl std::iter::IntoIterator<Item=&'a SendMmsgData<'a, I, C, S>>,
    flags: MsgFlags
) -> Result<Vec<usize>>
    where
        I: AsRef<[IoSlice<'a>]> + 'a,
        C: AsRef<[ControlMessage<'a>]> + 'a,
        S: SockaddrLike + 'a

其中,SendMmsgData定义为:

pub struct SendMmsgData<'a, I, C, S>
    where
        I: AsRef<[IoSlice<'a>]>,
        C: AsRef<[ControlMessage<'a>]>,
        S: SockaddrLike + 'a
{
    pub iov: I,
    pub cmsgs: C,
    pub addr: Option<S>,
    pub _lt: std::marker::PhantomData<&'a I>,
}

这是与之连接的代码

...

# [inline]

    fn exec_write_many<M>(&mut self, messages: Vec<M>) -> Result<usize, Error>
        where
            M: SocketMessage,
    {
        let mut sent = 0;
        let mut send_message_data = vec![];
        for msg in messages.iter() {
            let mmsg_data = if msg.borrow_fds().is_empty() {
                SendMmsgData {
                    iov: [IoSlice::new(msg.borrow_buf()); 1],
                    cmsgs: vec![],
                    addr: NONE_ADDR,
                    _lt: std::marker::PhantomData::default(),
                }
            } else {
                SendMmsgData {
                    iov: [IoSlice::new(msg.borrow_buf()); 1],
                    cmsgs: vec![ControlMessage::ScmRights(msg.borrow_fds())],
                    addr: NONE_ADDR,
                    _lt: std::marker::PhantomData::default(),
                }
            };
            send_message_data.push(mmsg_data);
        }
        match nix::sys::socket::sendmmsg(self.sock_fd, &send_message_data, MsgFlags::MSG_DONTWAIT) {
            ...

这两个问题都是可控的,但都是以性能为代价的,从主要的问题开始:我想为sendmmsg提供一个以如下方式创建的迭代器:

...

# [inline]

    fn exec_write_many<M>(&mut self, messages: Vec<M>) -> Result<usize, Error>
        where
            M: SocketMessage,
    {
        let mut sent = 0;
        let sendmmsgs = messages.iter()
            .map(|msg| {
                if msg.borrow_fds().is_empty() {
                    SendMmsgData {
                        iov: [IoSlice::new(msg.borrow_buf()); 1],
                        cmsgs: vec![],
                        addr: NONE_ADDR,
                        _lt: std::marker::PhantomData::default(),
                    }
                } else {
                    SendMmsgData {
                        iov: [IoSlice::new(msg.borrow_buf()); 1],
                        cmsgs: vec![ControlMessage::ScmRights(msg.borrow_fds())],
                        addr: NONE_ADDR,
                        _lt: std::marker::PhantomData::default(),
                    }
                }
            });
        match nix::sys::socket::sendmmsg(self.sock_fd, sendmmsgs, MsgFlags::MSG_DONTWAIT) {
            ...

但是,由于SendMmsgData归迭代器所有,因此我得到了如下结果:

error[E0271]: type mismatch resolving `<[closure@socks/src/buffered_writer.rs:146:18: 162:14] as FnOnce<(&M,)>>::Output == &SendMmsgData<'_, _, _, _>`
    --> socks/src/buffered_writer.rs:163:56
     |
163  |         match nix::sys::socket::sendmmsg(self.sock_fd, sendmmsgs, MsgFlags::MSG_DONTWAIT) {
     |               --------------------------               ^^^^^^^^^ expected reference, found struct `SendMmsgData`
     |               |
     |               required by a bound introduced by this call
     |
     = note: expected reference `&SendMmsgData<'_, _, _, _>`
                   found struct `SendMmsgData<'_, [IoSlice<'_>; 1], Vec<ControlMessage<'_>>, ()>`
     = note: required because of the requirements on the impl of `Iterator` for `Map<std::slice::Iter<'_, M>, [closure@socks/src/buffered_writer.rs:146:18: 162:14]>`
note: required by a bound in `nix::sys::socket::sendmmsg`
    --> /home/gramar/.cargo/registry/src/github.com-1ecc6299db9ec823/nix-0.24.2/src/sys/socket/mod.rs:1456:40
     |
1456 |     data: impl std::iter::IntoIterator<Item=&'a SendMmsgData<'a, I, C, S>>,
     |                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `nix::sys::socket::sendmmsg`

例如,对于Options,我可以只调用as_ref()将内部的T转换为&T,但我不知道如何使用迭代器来实现这一点,因为迭代器让我分配另一个向量并循环要发送的所有消息。

第二个较小的问题是cmsgs。类型系统不允许使用数组,因为一个分支的类型为[_ ;1],另一个分支的类型为[_ ;0]。空的VEC不会导致分配,而具有一个项目的VEC将会导致分配。

这两个问题都让我遇到了同样的问题。我不知道如何创建 Package 器结构并分别实现IntoIterator<Item=&'a SendMmsgData<'a, I, C, S>>AsRef<[ControlMessage<'a>]>,因为这两个都需要我返回将在函数体中创建的引用,因为我的数据结构不是SendMmsgDataControlMessage的形式,而且这两个引用都引用了其他一些内存,在我的例子中,我必须创建一个具有自己的缓冲区和对该结构的内部引用(对self的内部引用)的结构,这会产生其他问题。

你知道我如何才能在没有额外循环/分配的情况下做到这一点吗?

PS。在测量方面,由于系统调用的工作方式,当消息没有FDS时,这样做会造成大约10%的性能影响,而在只有消息具有FDS的情况下,性能会提高2%:

if msg.borrow_fds().is_empty() {
                SendMmsgData {
                    iov: [IoSlice::new(msg.borrow_buf()); 1],
                    cmsgs: [ControlMessage::ScmRights(&[])],
                    addr: NONE_ADDR,
                    _lt: std::marker::PhantomData::default(),
                }
            } else {
                SendMmsgData {
                    iov: [IoSlice::new(msg.borrow_buf()); 1],
                    cmsgs: [ControlMessage::ScmRights(msg.borrow_fds())],
                    addr: NONE_ADDR,
                    _lt: std::marker::PhantomData::default(),
                }
            };

我可以直接使用libc板条箱,如果我不能以更好的方式解决这个问题,我将不得不这样做。

k97glaaz

k97glaaz1#

第一个问题的问题是,sendmmsg可能会在迭代器n上多次调用next,从而获得对n``SendMsgData的引用,这些引用都必须位于某个地方。因为你不知道n是什么,所以所有SendMsgData都必须住在某个地方,所以你必须在Vec中缓冲它们。这可以通过将sendmmsg的API更改为接受拥有或借用的SendMsgDatas来修复,但您显然无法控制这一点。

不过,我认为cmsgs问题是可以帮助解决的。您可以创建自己的类似Option的 Package 器,它完全驻留在堆栈上,并根据它是否包含值来实现AsRef

struct ControlMessage<'a>(std::marker::PhantomData<&'a ()>);

enum CMsgWrapper<'a> {
    Empty,
    Msg(ControlMessage<'a>),
}

impl<'a> AsRef<[ControlMessage<'a>]> for CMsgWrapper<'a> {
    fn as_ref(&self) -> &[ControlMessage<'a>] {
        match self {
            CMsgWrapper::Empty => &[],
            CMsgWrapper::Msg(cmsg) => std::slice::from_ref(cmsg),
        }
    }
}
gkl3eglg

gkl3eglg2#

就像公认的答案所说的那样,您的临时结构应该位于某个地方。在我看来,您是按值接受输入参数的,所以您可能可以修改它们。是的,我提出了一些“肮脏”的解决方案,即使对我来说也不是很好,但当性能真的很重要时,我们可以尝试一下。

因此,我们的想法是将SendMmsgData结构作为Option<SendMmsgData>放入消息参数中,并使SocketMessage特征具有fn get_send_mmsg_data(&mut self) -> &SendMmsgData

Here is example code

是的,最好是把PR变成Rust STD库,让它能接受参考文献。

相关问题