Hello
Here is the latest Caml Weekly News, for the week of 06 to 13 September, 2005.
Archive: http://caml.inria.fr/pub/ml-archives/caml-list/2005/09/8bfffe121810ba7fc8ec9d43df0b7aef.en.html
Christophe Raffalli announced:link: third item of: http://www.lama.univ-savoie.fr/~raffalli/?page=soft&lang=en comment and idea are welcome ! ----------- MOTIVATION ----------- callbacks are problematic when writting OCaml bindings for C library. Indeed, a C function waits for a function respecting C calling conventions as argument, and OCaml functions do not respect the C conventions. Then in a library you only have a limited number of the C functions and in fact ideally, it is the user of the library that should often write the C function that needs to be passed in arguments to other C functions. This is too hard for the average user ... this is why I wrote this package Here is an example on how to use it (see the README file for a more detailled doc) -------------- in glut.mli: -------------- open Direct_callback ... val reshapeFunc: cb:(w:int->h:int->unit) callback->unit ... ------------- in a programm using lablGlut ------------- let doReshape ~w ~h = ... c_wrapper doReshape_cb for doReshape : w:int->h:int->unit ... ReshapeFunc doReshape_cb ... ------------- in glut.ml ------------- open Direct_callback ... external reshapeFunc : cb:(w:int->h:int->unit) callback->unit = "ml_glutReshapeFunc" ... ------------- in wrapglut.c ------------- ... ML_1(glutReshapeFunc,Callback2) ... which expends to ... CAMLprim value ml_glutReshapeFunc (value arg1) \ { glutReshapeFunc ((void (*)(int,int))(arg1)); return Val_unit; } ...Maas-Maarten Zeeman said and Christophe Raffalli answered:
> Sounds like a nice idea. As the author of ocaml-expat a binding I've > also worked with c -> ocaml callbacks. > > It is not really clear to me what problem you are trying to solve. It is > not very difficult for somebody writing a Ocaml binding to come up with > a solution which is easy to call an ocaml function value from c. > > What I did was create one fixed c-callback functions which: > > receive c-parameters > transform them to ocaml-values > lookup the ocaml callback "cb" in a tuple (set earlier from ocaml), This is that step I want to avoid ... not mainly for performance, but because there are some cases where you do not know which callback you should call (this was the case for one of the glut callback, I don't recall which one, and it was a bit tricky for glutTimer). It happens that in glut, the current window or current menu is properly set before calling the callback ... but there may be other library, more purely functional, with no concept of "current window" ... The other reason, is that it makes it easier to writte the bindings for the library (no thinking required and therefore less bug) ! and does not make it that much complex for the user. If you really need "dynamic" callback with arbitrary closure, then you can implement your trick above mine in OCaml. By expending my work, we could write and use bindings for a C library like glut without writing a line of C, except to convert data structure from C to OCaml if you really need that. This could divide the size of the glut binding by a factor 2 or 3 ... which is important ! > apply cb to the ocaml-values, > etc, etc > > The ocaml programmer just has to deal with normal ocaml callback > function values (which can be partially applied, unnamed, etc). These > will be stored via c in a tuple. The value pointing to this tuple is > registered as 1 global root. If you have large amounts of callbacks it > would probably work with a simple hash table too. Then you only have to > write/generate 1 c-callback function (per c-signature) which takes care > of calling an unlimited amount of caml callbacks. > > Its interesting to have the c-wrapper function generated as it would > prevent hard to debug gc problems.Bardur Arantsson replied:
> This is that step I want to avoid ... not mainly for performance, Now, I'll freely admit that I haven't tested it specifically, but I suspect performance will be worse when using register_global_root () to register callback closures instead of just using a mapping from "int" (or whatever type your callback identifier would be on the C side) to closures "stored" on the OCaml side. There was a post on this list not too long ago which exposed efficiency issues with register_global_root when registering lots and lots of roots. > but > because there are some cases where you do not know which callback you > should call (this was the case for one of the glut callback, I don't > recall which one, and it was a bit tricky for glutTimer). It happens > that in glut, the current window or current menu is properly set before > calling the callback ... but there may be other library, more purely > functional, with no concept of "current window" ... For any C library using callbacks there will *always* be *some* way to distinguish which event/object caused the callback. The reason is simple: Creating callback functions/closures on the fly is impossible(*) in C, or, in other words the callback function itself can't possibly carry enough information to distinguish callbacks from different events/objects. If there isn't enough information to distinguish what caused the callback, the callback would be pointless. (*) Well, you can generate machine code on the fly, but no sane library will force you to do this.Christophe Raffalli said and Bardur Arantsson replied:
> anyway, there should not be that many callbacks ? That depends hugely on what kind of library you're wrapping. I did a wrapper for libevent (events on file descriptors and other similar stuff like alarms, etc.) and what ended up happening was that a 1) lot of the time a relatively large amount of callbacks were registered, orm 2) callbacks would be registered/unregistered a lot. I did try using *_global_roots in my libevent wrapper, but the performance was awful until I changed it to use an (fd->callback) hashtable on the OCaml side. (In the case of file descriptors or similar objects you can try to be more clever and just use an array of callbacks and use the file descriptor as the index; I didn't bother with this in my wrapper library since the performance was OK as it was). > Moreover, I suspect (but may be wrong) that register_global_root () > is or could be optimized for closure on closed functions, and this > will be the case of most closure with my approach. From the implementation in globroots.c it would seem that register_global_root is at least O(n) in the number of roots, and that it has a large constant overhead compared to e.g. adding something to a hashtable. So it may not be the fact that a closure may keep things alive that's slowing it down, but rather just a slow implementation of register_global_root itself.Xavier Leroy then said:
> From the implementation in globroots.c it would seem that > register_global_root is at least O(n) in the number of roots, and that > it has a large constant overhead compared to e.g. adding something to a > hashtable. You should look harder. register_global_root is insertion in a skip list, which is probabilistic O(log n) with a low constant. I don't think a hash table would perform significantly better for the mix of operations we need to do on the set of global roots (insertion, deletion, and enumeration for the GC). > That depends hugely on what kind of library you're wrapping. I did a > wrapper for libevent (events on file descriptors and other similar stuff > like alarms, etc.) and what ended up happening was that a 1) lot of the > time a relatively large amount of callbacks were registered, orm 2) > callbacks would be registered/unregistered a lot. I did try using > *_global_roots in my libevent wrapper, but the performance was awful > until I changed it to use an (fd->callback) hashtable on the OCaml side. I would have been very interested in a profiling of your initial implementation. The only reason why the Caml hashtable can beat the global roots is that the latter are not generational: since the contents of registered global roots can change at any time without notifying the GC, all global roots must be scanned at every minor collection.Yaron Minsky then said:
Yeah, we ran into this problem. We had an application where we ended up registering about 200,000 global roots (most of which were callbacks.) The application spent around 15% of the CPU all the time just scanning global roots as part of minor collections. The callbacks were moved into a hashtbl,and these problems went away completely.
Here is a quick trick to help you read this CWN if you are viewing it using vim (version 6 or greater).
:set foldmethod=expr
:set foldexpr=getline(v:lnum)=~'^=\\{78}$'?'<1':1
zM
If you know of a better way, please let me know.
If you happen to miss a CWN, you can send me a message and I'll mail it to you, or go take a look at the archive or the RSS feed of the archives.
If you also wish to receive it every week by mail, you may subscribe online.