Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

any plans on io_uring support? #401

Open
microcai opened this issue Jun 13, 2019 · 10 comments
Open

any plans on io_uring support? #401

microcai opened this issue Jun 13, 2019 · 10 comments

Comments

@microcai
Copy link

IOCP is supported, the linux new io_uring API is more like IOCP than epoll. any plans on supporting this new and really fast API? io_uring supports eventfd, socket, and regular files.

@PodnimatelPingvinov
Copy link

I've made basic support, you can find it here: https://github.com/PodnimatelPingvinov/asio/tree/io-uring It has some serious problems (including performance), which are described in readme, that's why I don't want to open PR until they are fixed.

@Eelis
Copy link

Eelis commented Jul 22, 2020

Some other possibly relevant work: https://github.com/RobertLeahy/AsioUring
(I haven't tried it, it's just the first thing that comes up when you search for "asio io_uring".)

@reddwarf69
Copy link
Contributor

@PodnimatelPingvinov I see this in your repo regarding concurrency:
io_uring consists of two independent ring buffers: submission queue (SQ) and completion queue (CQ). It is fine to post jobs to SQ and read completions from CQ concurrently. However, you can't concurrently make 2 writes to SQ or 2 reads from CQ. You can concurrently wait for completions in CQ (using io_uring_wait_cqe in multiple threads), but in such situation all waiting threads will be woken up when completion event become available. This is not what you want, that's why only one thread should wait for completions in the same time. Therefore I use io_uring in the same manner as reactor works, via existing scheduler.

Do I understand correctly that each io_context uses a single SQ/CQ pair? I don't really know io_uring that well, but I'm thinking the io_context <-> SQ/CQ pair relationship may not be the best one. Would maybe make sense to instead, for each io_context, have a SQ/CQ pair per cpu?
With each thread having its own io_uring there would be no locking issues, and there would be better cache locality. When a thread finds its own CQ is empty then it would try to steal work from another thread's CQ, so at a higher level there is still a single queue.

@vinipsmaker
Copy link
Contributor

@reddwarf69 FWIW Boost.Asio has plenty of configuration macros such as BOOST_ASIO_DISABLE_THREADS and the ones listed at https://www.boost.org/doc/libs/1_76_0/doc/html/boost_asio/overview/core/concurrency_hint.html. A similar approach could be employed here where the cheaper implementation (where the relationship 1 io_context = 1 SQ/CQ pair holds) can be used when you define the macros to inform Boost.Asio that it is safe to assume that only 1 thread will call io_context::run().

@reddwarf69
Copy link
Contributor

https://github.com/PodnimatelPingvinov/asio/tree/io-uring also says

Experimental io_uring support for Asio library. io_uring is fairly new API, which can be used only on recent Linux kernels (v5.3+)

and

Operation cancelation

io_uring supports operation cancelation only for limited cases, which are not suits well Asio API. Thus, socket and descriptor methods cancel() and release() don't cancel outstanding operations with asio::error::operation_aborted. There is also problem with close() method: currently io_uring doesn't cancel associated operations even if corresponding decriptor has been closed.

I see IORING_OP_ASYNC_CANCEL was added in Linux 5.6.
Not sure about close(), but FWIW 5.6 also added IORING_OP_CLOSE.

By the way, it looks Windows is also getting (something very similar, if no identical to) io_uring: https://windows-internals.com/i-o-rings-when-one-i-o-operation-is-not-enough/

@PodnimatelPingvinov
Copy link

Yeah, io_uring received a lot of useful functions since its initial version. Asio also changed a lot during this period (i.e. Executors). Those reasons coupled with lack of time made me not feeling ready to rework my original solution atm, sorry.

@chriskohlhoff
Copy link
Owner

There is an initial implementation of io_uring support on the io-uring-1 branch. To use, compile your program with ASIO_HAS_IO_URING defined, which will disable epoll and enable io_uring instead.

At present, it uses a single CQ/SQ which means that it works best for a single-threaded io_context. All functionality should be working except for notify_fork(), which is yet to be implemented.

It seems to be stable with a linux 5.10 kernel. However, under a 5.13 kernel, io_uring seems to behave differently and multi-threaded io_contexts can sometimes result in the OS process table filling up. I have not yet identified the problem there. Single-threaded io_contexts seem ok.

Here is a simple single-threaded benchmark. When I test it on linux 5.10 I get the following time with epoll:

real    0m1.602s
user    0m0.563s
sys     0m1.039s

and this time when using io_uring:

real    0m1.158s
user    0m0.342s
sys     0m0.807s

As you can see, the benchmark completes faster with io_uring. However, this does not include the additional CPU usage associated with io_uring's kernel worker threads, which results in higher overall CPU usage than epoll. (Note: on a 5.13 kernel, this is included in the process's CPU usage.)

#include <asio/io_context.hpp>
#include <asio/posix/stream_descriptor.hpp>
#include <cstdio>

const int iterations = 10000;
const int num_rings = 100;

class pipe_ring
{
public:
  pipe_ring(asio::io_context& ctx)
    : io_context(ctx),
      read_end_1(ctx),
      write_end_1(ctx),
      read_end_2(ctx),
      write_end_2(ctx),
      read_end_3(ctx),
      write_end_3(ctx)
  {
    int fds[2];

    ::pipe(fds);
    read_end_1.assign(fds[0]);
    write_end_1.assign(fds[1]);

    ::pipe(fds);
    read_end_2.assign(fds[0]);
    write_end_2.assign(fds[1]);

    ::pipe(fds);
    read_end_3.assign(fds[0]);
    write_end_3.assign(fds[1]);
  }

  void start()
  {
    do_read(count, read_end_1, write_end_2, asio::buffer(data_1));
    do_read(count, read_end_2, write_end_3, asio::buffer(data_2));
    do_read(count, read_end_3, write_end_1, asio::buffer(data_3));
    write_end_1.write_some(asio::buffer("Hello, world!", 13));
  }

private:
  void do_read(int &count,
      asio::posix::stream_descriptor& from,
      asio::posix::stream_descriptor& to,
      asio::mutable_buffer data)
  {
    from.async_read_some(asio::buffer(data),
        [this, &count, &from, &to, data](auto ec, auto n)
        {
          if (!ec)
          {
            do_write(count, from, to, data, n);
          }
        });
  }

  void do_write(int& count,
      asio::posix::stream_descriptor& from,
      asio::posix::stream_descriptor& to,
      asio::mutable_buffer data, std::size_t n)
  {
    if (++count > iterations)
    {
      read_end_1.close();
      write_end_1.close();
      read_end_2.close();
      write_end_2.close();
      read_end_3.close();
      write_end_3.close();
      return;
    }

    to.async_write_some(asio::buffer(data, n),
        [this, &count, &from, &to, data](auto ec, auto)
        {
          if (!ec)
          {
            do_read(count, from, to, data);
          }
        });
  }

  asio::io_context& io_context;
  int count = 0;
  asio::posix::stream_descriptor read_end_1;
  asio::posix::stream_descriptor write_end_1;
  asio::posix::stream_descriptor read_end_2;
  asio::posix::stream_descriptor write_end_2;
  asio::posix::stream_descriptor read_end_3;
  asio::posix::stream_descriptor write_end_3;
  char data_1[1024];
  char data_2[1024];
  char data_3[1024];
};

int main()
{
  std::printf("pid %d\n", getpid());
  asio::io_context ctx(1);
  std::vector<std::unique_ptr<pipe_ring>> rings;
  for (int i = 0; i < num_rings; ++i)
  {
    rings.push_back(std::make_unique<pipe_ring>(ctx));
    rings.back()->start();
  }
  ctx.run();
}

@kashirin-alex
Copy link

kashirin-alex commented Nov 7, 2021

The #include <asio/detail/reactor.hpp> is seem to be missing when building with -DASIO_HAS_IO_URING -DASIO_DISABLE_EPOLL (That is with version 1.21.0) at before:

#include "asio/detail/scheduler.hpp"
while with only -DASIO_HAS_IO_URING for file-io reactor is not an issue.

with only:

#include <asio.hpp>

The build error is:

In file included from /usr/local/include/asio/detail/impl/scheduler.ipp:28,
                 from /usr/local/include/asio/detail/scheduler.hpp:238,
                 from /usr/local/include/asio/system_context.hpp:19,
                 from /usr/local/include/asio/impl/system_executor.hpp:21,
                 from /usr/local/include/asio/system_executor.hpp:682,
                 from /usr/local/include/asio/associated_executor.hpp:24,
                 from /usr/local/include/asio.hpp:19,
                 from /root/swc-db/src/cc/include/swcdb/core/comm/asio_wrap.h:33,
                 from /root/swc-db/src/cc/include/swcdb/core/comm/IoContext.h:11,
                 from /root/swc-db/src/cc/include/swcdb/core/comm/SerializedServer.h:11,
                 from /root/swc-db/src/cc/lib/swcdb/core/comm/SerializedServer.cc:8:
/usr/local/include/asio/detail/io_uring_service.hpp:297:3: error: ‘reactor’ does not name a type
  297 |   reactor& reactor_;

while it will compile with:

#define ASIO_HAS_IO_URING 1
#define ASIO_DISABLE_EPOLL 1

#include <asio/detail/reactor.hpp>
#include <asio.hpp>

@microcai
Copy link
Author

microcai commented Feb 4, 2022

using asio with io_uring enabled on all of my company production now. no issuses found so far other than the udp endpoint reference missing bug #965.

may thanks for the hard work!

@brjoha
Copy link

brjoha commented May 14, 2022

Just an observation that running the benchmark under WSL yields better results for epoll than uring...

epoll:

1.20user 1.76system 0:02.97elapsed 100%CPU (0avgtext+0avgdata 14204maxresident)k
0inputs+0outputs (0major+299minor)pagefaults 0swaps

uring:

1.10user 2.42system 0:03.66elapsed 96%CPU (0avgtext+0avgdata 12296maxresident)k
0inputs+0outputs (0major+305minor)pagefaults 0swaps

details:

$ uname -r
5.10.102.1-microsoft-standard-WSL2
$ cat /etc/os-release | grep "VERSION="
VERSION="22.04 LTS (Jammy Jellyfish)"
$ grep "#define BOOST_LIB_VERSION" /usr/include/boost/version.hpp
#define BOOST_LIB_VERSION "1_79"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants