cpp-ipc icon indicating copy to clipboard operation
cpp-ipc copied to clipboard

receiver非首次连接sender,会增加延迟

Open iGwkang opened this issue 2 years ago • 5 comments

  1. 启动进程 A 调用 do_send()
  2. 启动进程 B 调用 do_recv(),此时打印的耗时大概是30~50us
  3. 杀死进程B
  4. 再启动进程B,此时打印的耗时大概是 2308us 左右
constexpr auto payload_size = 64 * 1024;

void do_send() {
    std::unique_ptr<ipc::channel> sender = std::make_unique<ipc::channel>("ipc");
    if (!sender->reconnect(ipc::sender))
    {
        std::cout << "reconnect ipc sender failure" << std::endl;
        exit(-1);
    }
    sender->wait_for_recv(1);

    std::string buffer(payload_size, 'A');
    while (true)
    {      
        *(uint64_t*)buffer.data() = get_system_ns();
        if (!sender->try_send(buffer.c_str(), payload_size))
        {
            std::cout << "send data failure" << std::endl;
            sender->disconnect();
            sender = nullptr;
            std::this_thread::sleep_for(std::chrono::milliseconds(5000));
            sender = std::make_unique<ipc::channel>("ipc");
            if (!sender->reconnect(ipc::sender))
            {
                std::cout << "reconnect ipc sender failure" << std::endl;
                exit(-1);
            }
            std::cout << "wait_for_recv" << std::endl;
            sender->wait_for_recv(1);
        }

        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}

void do_recv() {
    std::unique_ptr<ipc::channel> receiver = std::make_unique<ipc::channel>("ipc");

    if (!receiver->reconnect(ipc::receiver))
    {
        std::cout << "reconnect ipc receiver failure" << std::endl;
        exit(-1);
    }

    while (true)
    {
        ipc::buff_t recv = receiver->recv(1000);
        if (recv.empty())
        {
            std::cout << "recv timeout, exit..." << std::endl;
            exit(-1);
        }
   
        if (recv.size() != payload_size)
            printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!! recv size = %zd, != %d\n", recv.size(), payload_size);

        printf("ipc recv cost time %.2f us\n", (get_system_ns() - *(int64_t*)recv.data())/ 1e3);
    }
}

iGwkang avatar Apr 28 '22 03:04 iGwkang

确认了一下问题原因,recv退出的时候没有disconnet,因此send会继续发送一段时间,填满了预留的大message缓存池,导致后续连上来新的recv,send也无法利用缓存池快速发数据,而只能拆包,从而导致效率大大降低。

现有机制的解决办法:recv退出需要disconnect,这样send就知道连接断了不会继续发。

我考虑修改下现在的机制,目前有所谓的连接概念,但这其实是不太好的,ipc通道想确保qos有一些困难,而且会影响性能。我可能会把目前的连接去掉,直接在通道上收发,当消息写满了直接覆盖尚未取完的消息。 这样做缺点就是接收者可能会拿到被写了一半的数据,因为数据写入的原子性保证是需要加锁的。

后面的重构版本,我可能会放弃目前无锁队列的做法,因为对通用ipc来说,它相比加锁后的变长循环缓冲区,并没有太大优势。 想保证数据写入的原子性,不会读到写了一半的数据,现在其实也是有所谓的“锁”的,目前的代价就是写入需要等待最慢的那个读取操作。在最慢的读取进程未结束之前,哪怕其它读取进程都已经取走了所有数据,队列已空空如也,写入进程也无法写任何数据,所以现在的无锁队列本质上也只是个形式。 在希望保证数据写入的原子性的基础上,还不如直接加锁来得简单方便。

mutouyun avatar May 08 '22 13:05 mutouyun

recv 退出的时候,try_send已经失败了,我那里已经把原来的 channel 对象释放调了,这时候,已经没有对象引用这块共享内存了,那为什么原来的数据还在里面。其实我想说的是,这里有了连接的概念,其实内部是需要管理连接里面的一些状态的(类似tcp那样),如果内部能通过类似心跳的方式去检测连接是否正常,这样应该就能正确处理这些状态的吧?

你说的 recv退出需要disconnect,这个其实不能保证的,如果程序异常了,是无法保证能调用到disconnect的,这样整个共享内存一直是处于异常的一个状态

我测试了好几个ipc的库,包括 nng, nanomsg, zmq, boost ipc,我的测试结果都是不如直接用tcp本地127去通信,只有cpp-ipc优于tcp,我想主要原因是用了无锁队列,boost的ipc我自己简单写了一个,加锁实现的,性能不如直接用tcp。我其实关注的是低延迟,其次是带宽,场景是本地的音视频、文件数据转发

iGwkang avatar May 09 '22 04:05 iGwkang

嗯……主要是因为目前实现基于的是共享内存,超时了就已经说明接收者卡死或者掉线了。 目前有一个机制是接收者卡死就把它踢掉,但也造成了容易误伤的bug。

目前的bug主要是在等超时的过程中,try_send是没有失败的,这段时间的发送数据是放在单独的共享内存缓冲池里,而这个池不像用来保持连接的循环队列,是可以循环使用的,目前的设计是需要接收者配合显式回收资源的。

这个设计不太好,我今天早上想到了一些新的优化方案,不过可能得周末再研究下。

mutouyun avatar May 09 '22 06:05 mutouyun

感觉改起来有点麻烦。。我干脆在refactoring分支从头写好了。。

mutouyun avatar May 15 '22 09:05 mutouyun

好的,我最近也在准备用共享内存模拟tcp,只做一对一收发的场景,简单点

iGwkang avatar May 15 '22 11:05 iGwkang