#Is eio the way to go?

1 messages ยท Page 1 of 1 (latest)

normal hatch
#

Looks like a very heavily invested library. Are we ok then with objects for IO (I'm fairly neutral). What about the capability things?
Anyone have experience with this?

teal fulcrum
#

How is this possible ? 0 reaction ?

#

If object become mainstream they will get improvement. Is it part of the API or internal change that can be changed later ?

normal hatch
#

I sincerely hope objects are improved. It does seem that with buffering, the impact isn't bad.

#

For example, if a new type of object was added that had to derive from a class and didn't rely on row-polymorphism, it would immediately solve the performance problems.

sour apex
#

No direct experience really, but it seems to be flexible enough to get a remarkably fast and simple implementation for cohttp

viscid cypress
#

No direct experience yet but as a heavy user of async io a lot of things look sane to me. I am a bit less convinced by the io_uring backend but it is purely internal so not a great issue

normal hatch
#

nope. like C++ objects

autumn tartan
viscid cypress
#

Using it a lot from c (caveat debian 11 kernel so not bleeding edge Linux), performance of io_uring is tricky. The interface is great, but wether using it is worth it depends on the usecase

#

On network io it is not really faster than epoll, and you better still use poll via uring anyway to match performance. On disk io, it can be great (reached some crazy numbers like 100Gb/s from single core in c). But it can be less great. If you don't batch enough blocking io can be a lot faster. Depends on the disk model too. Same for fs name operation. Blocking is faster in a lot of cases. Though then you would have to compare it with thread pool so maybe it is always worth

autumn tartan
#

when you say "still use poll via uring", you mean that doing N io_uring_submits is worse than setting N pollfd and doing one poll(2) ?

#

I'm surprised how epoll can more or less match uring, sure, for one connection yes, but while I'm in userland handling whatever one connection maybe, other N can be ready and the copytouser() is already done when I get back to them

#

would be nice if you could share your experiences with uring on an issue there in ocaml-uring, we can learn something

fluid dune
#

@joris you en always use luv back-end if you want

#

I think eio looks very interesting, and you don't even have to touch objects to use it. All methods have a function to call them, and provide better error messages if you misuse them

#

Batching is important but eio now has both buffered readers and writers (similar to angstrom and faraday)

halcyon granite
fluid dune
#

heh, yeah

#

I also looked at (part of) the code, and it looks pretty good. Very modern.

autumn tartan
#

I'd like to get a libevent backend at some point, and maybe add uring to libevent so eio only talks to libevent in the future

#

but that's a long pipe for now

fluid dune
#

I think it's nice that eio builds on different backends from the start

autumn tartan
#

but then you have to maintain N backends :/

fluid dune
#

sure, but they're separate libraries right?

#

I imagine luv would cover 90% of the cases

autumn tartan
#

yes, but we have one scheduler for each

#

and luv is unmaintained, and libuv is realllllly bloated

fluid dune
#

right. Is it impossible to change, that there's a scheduler for each?

#

can it be functorized or parametrized?

old galleon
#

I've found async's approach a bit nicer in this respect. The core scheduler is independent of how i/o is done, and was a lot easier to extend for non async_unix backends, while still being able to leverage the high precision timer, and scheduler from async_kernel.

autumn tartan
#

sure, but it's more complexity than just talking to one thing

#

libuv is like 100, we need like ~5

#

(disclaimer I also just really dislike libuv)

old galleon
#

Not sure if I'll get around to this at any point, but if you are interested you could take https://github.com/anuragsoni/poll out for a spin for potentially using with eio. No timers here, but it is a really thin wrapper that abstracts over kqueue, epoll and wepoll

autumn tartan
#

that's interesting

#

thin wrapper rings the right bells ๐Ÿ˜„

fluid dune
#

@autumn tartan I've seen that eio has no API for subprocesses yet

#

would that bring libuv to, not 5, but 10? 20? :)

autumn tartan
#

I'm not sure an API for that would likely be wanted (but I know nothing)

fluid dune
#

I know that Unix's subprocess capabilities are one of the pain points for me. When starting subprocesses, I'd like to:

  • communicate on stdin/err/out as it goes
  • kill it with a signal
  • wait for it to return, get the return code
  • (optionally) stop it after a timeout, which might be doable using structured concurrency + kill?
#

there's an issue opened for it.

old galleon
#

I need to improve the linux backend a little more as I don't use timerfd for timeouts at the moments. As a result the the precision for timeouts on linux is milliseconds, whereas kqueue can do nanoseconds

autumn tartan
fluid dune
#

I mean, having subprocesses is a bit of a sine qua non, I think; almost as much as not having sockets ;)

autumn tartan
#

who needs sockets, just pipe to netcat like a grownup

#

๐Ÿ˜›

storm rampart
fluid dune
#

they make a decent point for it. The repo is full of documentation, including a rationale.md that goes into "why objects"

brazen fox
fluid dune
#

Is there a reason it's process and not t btw?

#

Otherwise, yeah, that looks nice

#

I guess it's likeUnix in the sense that methods of processneed to document what effects they have (like status will block until the process exited)

#

Taking arbitrary flow sources/sinks for redirection would be amazing though.

viscid cypress
# autumn tartan when you say "still use poll via uring", you mean that doing N io_uring_submits ...

yes on my workload, doing N io_uring_read on socket is worst than doing N io_uring non-blocking read and reverting to epoll. Which is on par to epoll. But doing straigth read without poll operation (once again on debian 11 kernel). But network is not a concern, io_uring without poll_add scales to reasonable number. I have more issues on disk io personally, where doing async io is often not worth it depending on the disks and batching you use

#

and just to be clear, it is not a criticism in eio. It looks really nice for the small tests i did. But i am a bit unsure how making every io async on the general case with the current state of io_uring and some cases falling back to kernel side threadpool is the good idea in the general case right now.

#

i think personally i will start with libuv backend

#

the thing is, it is really hardware specific. I spent 2 weeks recently converting a c++ app to io_uring from blocking reads with ~10 nvmes. Even with the gain of zero copy read with io_uring fixed buffers + direct io, the latency ended up to be higher with io_uring and the throughput lower than direct blocking reads because batching was ~4 per io_uring_enter, and the cost of processing submit+completion was dominating. Otoh when it scale to like 20 reads per submit it totally destroys blocking read

#

honnestly i don't feel smart enough to understand the performance implications of this kind of api since it interacts with so much kernel features :/

#

but then i guess, io_uring is not bad default if you would not write blocking read in your app anyway. Idk.

autumn tartan
#

and all FDs are set to nonblock, so the kernel should only be threadpooling when it's getting really overloaded

#

but the SQE->CQE back pressuring needs some work as well

#

I mean the point of the gain is to not synchronously do the copyfromuser() for each "Fiber that blocked"

viscid cypress
#

i've actually read the implementation of eio yeah and i have nothing to say about it. As far as i know io_uring eio backend is "perfect". My main concern is more about the predictability of switching to async operation everywhere because in my experience it is hard to understand how worthy it is.

I mean the point of the gain is to not synchronously do the copyfromuser() for each "Fiber that blocked"
Yes this makes perfect sense. Especially the fact that you use fixed buffer which saves one copy compared to traditional reads with unmovable buffer. Again i have no criticism about the implementation

#

i'm just not sure if on "low intensity" io code, io_uring is the right choice compared to blocking ios with threadpool which is more predictable and easier to understand. For for sure eio with io_uring probably scales better. (again i only read the code didn't try it)

autumn tartan
viscid cypress
#

and for instance i have a feeling most of lwt code in the wild is "low intensity". With not much parallelism

autumn tartan
#

I've written some really basic stuff using ocaml-uring directly and comparing against blocking normal stdlib stuff, it was mostly the same (but ofc really simple stuff that is not very expressive)

#

aye, but low intensity also means "don't care much about performance", no ?

viscid cypress
#

heh. You may have a point there. I have a lot of low intensity with strong latency requirement, but i guess in the scope of community it is not really an issue

autumn tartan
#

just write it with ocaml-dpdk and problem solved, oh shizzle we don't have one ๐Ÿ˜„

viscid cypress
#

(and btw i am using ocaml-uring in prod right now, without eio because 4.14. I guess you are the author, so i will take this opportunity to thank you. Saved me the headache to write this tool in C)

autumn tartan
#

oh I'm not the author, I just kinda got into it, I work with talex5@ and I'm trying to offload uring from him, he busy smart man, I'm simple minded former kernel dev cosplaying in ocaml ๐Ÿ˜„

#

I think anil is the original author

viscid cypress
#

oh right i heard anil talk about this a long time ago ๐Ÿ™‚ anyway it is cool to have this binding

autumn tartan
#

I like it too, though I would like to have it in provo's libevent, and then just support libevent

viscid cypress
#

libevent ? not libev ?

autumn tartan
#

I'm very biased cause I love libevent

#

yes, libevent (the one in Chrome), not libev

#

it's very popular in BSD land

viscid cypress
#

hm ok. Why so ? We were using lwt with libevent backend but had a lot of headache debugging some random segfaults and switched everything with lwt + libev and now everything is good. I must admit i know nothing about the internals of those libs

autumn tartan
#

oh I didn't even know lwt had a libevent backend

#

I've always used it directly, and we kinda had our fork diverged a bit, it was originally developed inside openbsd by provos, and then we continued using

viscid cypress
#

lwt has a nice object interface for engine, so it has a backend for whaterver you want it to have a backend for. The libevent backend is in ahrefs devkit lib, so yeah it is kind of hidden ๐Ÿฅฒ

autumn tartan
#

oh cool

#

I always liked the API and how "stupidly simple it was"

viscid cypress
#

makes sense. I never read the api. On this topic, lwt has kind of an anti partern in several part of its io api (not all)

#

where it polls before trying to read/write. Which is kind of stupid. (with many part of the lib checking if fd is readable / writable before trying to read/write)

#

i'm glad eio does not do that, it is already a huge win

autumn tartan
#

you mean I do a normal Lwt.read or whatever and after it returns the FD as readable, I check it again or ?

viscid cypress
#

yes there are cases where lwt will poll first to check it is readable before trying to read. It is not stupid as in a bug, because it happens in corner case, where lwt is not sure if the fd is a socket or a file and scheduling to a threadpool is costly, but still it is an issue in practice

#

lwt will poll first, get "available", then attempt read

fluid dune
#

You mean, it could just read and see if it gets EAGAIN?

viscid cypress
#

yes, that is the normal pattern

#

lwt mostly does that, but not for all cases

autumn tartan
#
    Lazy.force ch.blocking >>= function
    | true ->
      wait_read ch >>= fun () ->
      run_job (read_job ch.fd buf pos len)
    | false ->
      wrap_syscall Read ch (fun () -> stub_read ch.fd buf pos len)
viscid cypress
#

yes this.

autumn tartan
#

ohh I see: wait_read -> readable -> unix_readable -> poll]

viscid cypress
#

it makes perfect sense to implement this like this, it is not a bug. but in practice it is not so great.

autumn tartan
#

it's weird cause wrap_syscall() does exactly what you suggested

#

scratch that, it's doing unix_readable() as well.

viscid cypress
#

honnestly right now i just flew back from icfp and i'm a bit tired, and i never totally understood the internal invariants of lwt. But last time i checked i convinced myself it made sense.

#

but stracing services on prod, it does not seem optimal at all

autumn tartan
#

from what I see they do a POLLIN check only if it's the FD is blocking so it makes sense I think

#

I was shooting myself in the foot cause I forgot that poll(,,0) is not poll(,,INFTIM)

storm rampart
brazen fox
# fluid dune Is there a reason it's `process` and not `t` btw?

I think because the "process capability" is the t. Thinking about it more, cwd should probably be a Path.t too, although presumably once you are spawning subprocesses you can't really restrict them to a directory (I'm not familiar enough with that ๐Ÿ˜… )

teal fulcrum
#

Even with pledge ?

brazen fox
#

I didn't know about pledge, perhaps thanks!

fluid dune
#

It's going to be hard to promise that in a portable way though!

autumn tartan
#

and for this functionality it wouldn't be pledge, it would be unveil, which is like a sister subsystem of pledge

fluid dune
#

I think this is probably out of scope for eio, but maybe libraries will be able to modify the std env to bring their own process spawning? Eg on Linux using containers/cgroups/..

autumn tartan
#

maybe, Eio could also take some f() that is called after fork and before exec, so you could do something evil before exec.

teal fulcrum
autumn tartan
#

but pledge/unveil are syscalls, how would that fit ?

teal fulcrum
autumn tartan
#

ack, I think she was testing her pledge@eBPF thing with that

#

it's sad she got a lot of hate from openbsd people with that :/

#

Basically because on the article she shows a binary that fork+pledge+exec, which is kinda missing the point of pledge. I thought it was kinda ok

#

the original intend was to pledge the whole OpenBSD base, not to protect unknown things you're running

teal fulcrum
#

Ok self restrict . I won't be shocked with the first pattern either.

#

and about syscall technically we call Libc wrapper so they could inject our wrapper with LD_PRELOAD trickery (only way to escape is raw asm or setuid programs immune to that)

autumn tartan
#

I was meaning the problem is not injecting the syscall, is actually getting the semantics (which need to happen at kernel level)

teal fulcrum
#

With wrapper we could filter some argument before sending to the kernel

autumn tartan
#

the point about the binary is that she is porting something that kinda only makes sense when you control the full base, to someplace where she has no control, so I think the binary was ok.