|From:||Alan Schmitt <alan.schmitt-AT-polytechnique.org>|
|To:||lwn <lwn-AT-lwn.net>, cwn <cwn-AT-lists.idyll.org>|
|Subject:||Attn: Development Editor, Latest Caml Weekly News|
|Date:||Tue, 10 Apr 2012 17:29:31 +0200|
Hello, Here is the latest Caml Weekly News, for the week of April 03 to 10, 2012. 1) New version of the binary installer for Windows 2) Non-blocking IO interface design ======================================================================== 1) New version of the binary installer for Windows Archive: <https://sympa-roc.inria.fr/wws/arc/caml-list/2012-03/msg0...> ------------------------------------------------------------------------ ** Continuing the thread from last week, Jonathan Protzenko said and Sylvain Le Gall replied: > I've also looked into odb compatibility, and it looks like after writing > a few patches, odb now runs fine on Windows. However, Oasis-generated > setup.ml files do not work at all on windows. Any help in that area > would be highly appreciated. This has nothing to do with windows. This is related the version number of OCaml which is "4.01.0+dev0 (2012-03-12)". OASIS 0.2 doesn't handle spaces in the version number... A more classical scheme for version number with date is 4.01.0+dev0_2012-03-12. But anyway, this is fixed since OASIS 0.2.1~alpha1 and will be automatically fix in any packages hosted on the forge -- and it won't be there when your installer will use a non dev version of OCaml. BTW, the bug related to these issues is here: <https://forge.ocamlcore.org/tracker/index.php?func=detail...> I recommend to follow it. From what I have seen I am more concerned by the fact that the camlp4 findlib path contains strange char... There are some bugs in findlib on Windows with pathname, I have attached a patch to the bug. Please test and send it to Gerd Stolpmann. ======================================================================== 2) Non-blocking IO interface design Archive: <https://sympa-roc.inria.fr/wws/arc/caml-list/2012-04/msg0...> ------------------------------------------------------------------------ ** Daniel Bünzli said: This is problematic : <https://github.com/williamleferrand/xmlm> <http://ambassadortothecomputers.blogspot.com/2010/08/mixi...> To solve this problem I'm looking for a simple interface design to make my IO modules compatible with monadic concurrency libraries (lwt, async, [insert your own here]) and event based loops (select(2), poll(2), etc.). The design should have the following properties: 1. Unified interface for blocking and non-blocking mode. 2. The existence of the non-blocking mode should not significantly impact blocking mode users. 3. Input possible from in_channel, string, refillable fixed-size string buffer (non-blocking mode). 4. Output possible to out_channel, Buffer.t, flushable fixed-size string buffer (non-blocking mode). 5. No third-party IO libraries/paradigms so that the module can adapt to the one the user chooses. 6. Reasonably efficient. I looked for some time into Haskell's enumerators, pipes and other conduits but I eventually came back to a more ad-hoc approach that abstracts as follows. I'll gladly take any feedback you may have. Suppose we want to IO streams of value of `type t`. For example xmlm's signals (lexemes as they should be called) if you are familiar with that. For input (decoding) we begin with a type for input sources, decoders and a function to create them. type src = [ `Channel of in_channel | `String of string | `Manual ] type decoder val decoder : src -> decoder A [`Manual] source means that the client will provide the decoder with chunks of bytes to decode at his own pace. The function for decoding is : val decode : decoder -> [ `Await | `End | `Error of e | `Yield of t ] [decode d] is : - [`Await] iff [d] has a [`Manual] input source and awaits for more input. The client must use [decode_src] (see below) to provide it. - [`Yield v], if a value [v] of type [t] was decoded. - [`End], if the end of input was reached. - [`Error e], if an error [e] occured. If you are interested in a best-effort decoding, you can still continue to decode after the error. For [`Manual] sources the function [decode_src] is used to provide the byte chunks to read from : val decode_src : decoder -> string -> int -> int -> unit [decode_src d s k l] provides [d] with [l] bytes to read, starting at [k] in [s]. This byte range is read by calls to [decode] with [d] until `Await is returned. To signal the end of input call the function with [l = 0]. That's all what is needed for input. Just a note on the `Error case. Decoders should report any decoding errors with [`Error] to allow standard compliant decodings. However at that point they should give the opportunity to the client to continue to perform a best effort decoding. In that case [decode] should always eventually return [`End] even if [`Error]s were reported before. I think best-effort decoding on errors is a good thing: I was annoyed more than once with xmlm simply failing with `Malformed_char_stream on files produced by legacy software that gave invalid UTF-8 encodings for funky characters. Rather than fail and block the client at that point it's better to report an error and let it continue if it wishes so by replacing the invalid byte sequence with U+FFFD. For output (encoding) we begin with a type for output destinations, encoders and a function to create them. type dst = [ `Channel of out_channel | `Buffer of Buffer.t | `Manual ] type encoder val encoder : dst -> encoder A [`Manual] destination means that the client will provide to the decoder the chunks of bytes to encode to at his own pace. The function for encoding is : val encode : encoder -> [ `Await | `End | `Yield of t ] -> [ `Ok | `Partial ] [encode e v] is - [`Partial] iff [e] has a [`Manual] destination and needs more output storage. The client must use [encode_dst] (see below) to provide it and then call [encode e `Await] until [`Ok] is returned. - [`Ok] when the encoder is ready to encode a new [`Yield] or [`End]. Raises [Invalid_argument] if a [`Yield] or [`End] is encoded after a [`Partial] encode (this is done to prevent the encoder from having to bufferize [`Yield]s). For [`Manual] destinations the function [encode_dst] is used to provide the byte chunks to write to : val encode_dst : encoder -> string -> int -> int -> unit [encode_dst e s k l] provides [e] with [l] bytes to write, starting at [k] in [s]. This byte range is written by calls to [encode] with [e] until [`Partial] is returned. To know the remaining number of non-written free bytes in [s] the function [encode_dst_rem] can be used: val encode_dst_rem : encoder -> int [encoder_dst_rem e] is the remaining number of non-written, free bytes in the last buffer provided with [encode_dst]. A well-behaved encoder should always fill all the bytes it is given, except for the buffer that encodes the `End. One note on [`Manual] destinations, encoding [`End] always returns [`Partial]. The client should then as usual use [encode_dst] and continue with [`Await] until [`Ok] is returned at which point [encode_dst_rem e] is guaranteed to be the size of the last provided buffer (i.e. nothing was written, this is a good property for the client's loops, see the example code). To validate the approach and provide a blueprint for implementing the interface I implemented both a blocking codec and a (cps) non-blocking codec for a simplified grammar of s-expressions. It's available here : <http://erratique.ch/repos/nbcodec> git clone <http://erratique.ch/repos/nbcodec.git> I think that the first five points are mostly met and the cps transformation of blocking into non-blocking is relatively straightforward and remains readable in my opinion. Regarding the 6th point, using the included `setrip.native` program on 32 Mo of randomly generated s-expressions seem to indicate that: The non-blocking decoder can be at least 1.35 slower than blocking The non-blocking encoder can be at least 1.1 slower than blocking Now I don't think these "bad" numbers should be taken to dismiss the approach since in the context of a larger reactive program a blocking codec may actually incur performance and scability issues that cannot be shown by this example program. Thanks in advance for your input, Daniel P.S. Numbers above were gathered by timing these invocations : ./setrip.native -enc -unix -rseed 1067894368 > 1067894368.sexp ./setrip.native -enc -b -rseed 1067894368 > 1067894368.sexp ./setrip.native -dec -unix < 1067894368.sexp ./setrip.native -dec -b < 1067894368.sexp This does however also compare two different IO mechanisms: pervasives channels for blocking vs direct calls to Unix for non-blocking. Remove the `-unix` flag to compare the timings with the same IO mechanisms (I then get 1.45 for the decoder and still 1.1 for the encoder). ** Anil Madhavapeddy then said and Daniel Bünzli replied: > The I/O loop is being called twice for the non-blocking version, as it > receives > the `Await signal, does the Unix syscall, and then jumps into decode_src. > Presumably > a full non-blocking version would have to register with a select handler if > it > gets an EAGAIN at this point, Yes. > In terms of the number of system calls, the non-blocking one is more > efficient, > as it uses a 16KB buffer versus the 4K reads done by the blocking version. Yes, the 4K reads are a limitation of pervasives channels. For each mechanism I used the largest buffer that the OCaml runtime uses. > Looking at the two decoders in src/se.ml, it looks like the non-blocking one > allocates closures on every loop, which the blocking one doesn't. This is > so it > can store the continuation in d.k for the next loop. Yes, that's a side effect of writing in continuation passing style in general since continuations are often partially applied functions. > So to summarise, instead of storing a continuation closure, it would > probably be better > to explicitly store the state in d.k to minimise allocation? Maybe, but keep in mind that s-expressions are very simple to parse. It may be obvious in this case but depending on what you decode defining/storing the state may become complex. Cps is an easy and general way to solve the problem while keeping the whole thing reasonably readable. But do you maybe see another pattern that I don't ? > The library looks very useful by the way: I have exactly the same issue > with several > Lwt-only protocol libraries we're developing at the moment. Would love to > use yours before > the first release of them to make them more independent of the underlying > I/O mechanism... That would be nice, I'm glad if you can somehow reuse the pattern. ** Yaron Minsky then added: > Yes, that's a side effect of writing in continuation passing style in > general since continuations are often partially applied functions. I believe this particular performance issue is fixed in the upcoming 4.0 release, based on some work by OCamlPro. ======================================================================== Old cwn ------------------------------------------------------------------------ If you happen to miss a CWN, you can send me a message (firstname.lastname@example.org) and I'll mail it to you, or go take a look at the archive (<http://alan.petitepomme.net/cwn/>) or the RSS feed of the archives (<http://alan.petitepomme.net/cwn/cwn.rss>). If you also wish to receive it every week by mail, you may subscribe online at <http://lists.idyll.org/listinfo/caml-news-weekly/> . ========================================================================
Copyright © 2012, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds