Xavier Leroy on the right:
Dinner the evening before:
| Reqs/sec | Mem usage | |
|---|---|---|
| Rails with mongrel, 1 process | 260 | 49MB |
| Rails with mongrel via nginx (rev proxy), 1 proc | 220 | ~51MB |
| Rails with mongrel, 4 processes via nginx | 430 | ~200MB |
| OCaml ocsigen (1 process) | 5800 | 4.5MB |
| lighttpd with FastCGI app in C, 20 procs | 9300 | 4.5MB |
You have to look at the usage case, malloc returning NULL is a "can't happen" condition where an exit call is arguably justified.
Returning an error from library to application when malloc returns NULL assumes:
1) error return paths exist [...]
2) applications are prepared to do something meaningful with the error
Note that the main program can use longjmp (or exceptions in some cases) to "return" back to a safe point in the program, such as a transaction checkpoint. If the main program uses pool allocators — about the only safe and sensible way to deal with C's programming model — then the program has a chance of recovering.
void (*custom_abort) () = abort;
void
lib_set_custom_abort (void (*new_abort) ())
{
custom_abort = new_abort;
}
void *
lib_malloc (int n)
{
void *data = malloc (n);
if (data == NULL) custom_abort ();
return data;
}
"I have *never* seen it being used since. To my mind they both belong in the category 'interesting, but pointless'."and
"The point is that there's nothing those languages can do that can't be done, often more easily, with the current crop of popular languages. Elegance cannot beat convenience in the workplace, or in most at any rate."and so on.
if (..) { field1 = htonl (field1); ... }. OK so that's a bit hard. Let's say you want to parse a 6 bit length field 'n' followed by an n+1 bit data field (as a 1-64 bit int). Go and write it in C now.
let bits = Bitstring.bitstring_of_file "input.data" in
bitmatch bits with
| { n : 6;
data : n+1 } -> data
set_word_size because you try to call get_word?let lines = ref [] inActually, no, I'm lying. The best solution is this:
let chan = open_in filename in
try
while true; do
lines := input_line chan :: !lines
done; []
with End_of_file ->
close_in chan;
List.rev !lines
Std.input_list chanwhich is supplied by extlib. Don't bother to duplicate functions which are already provided in commonly available libraries.
input_line throws some read error (exception). In the common case where you just exit the program, leaving the channel open is perfectly acceptable.
failwith is the standard function to raise a general error, but it's a little bit clumsy to use because it only takes a fixed string.if temp >= 100 thenIf you want to have the message contain useful debugging information, you need to use
failwith "we've reached boiling point"
sprintf to generate the fixed string, like:if temp >= 100 then(I'm assuming here that you have
failwith (sprintf "%d degC: boiling point reached" temp)
open Printf at the top of your file, something which you should almost always do so you don't need to write Printf.sprintf all the time,).failwith into a function that automatically takes a printf-like format string, and we can learn a little bit about the arcana of polymorphic types too.let failwith format = ksprintf failwith formatYou can see in the toplevel that it works:
# let failwith format = ksprintf failwith format ;;
val failwith : ('a, unit, string, 'b) format4 -> 'a = <fun>
# failwith "hello, %s" "world" ;;
Exception: Failure "hello, world".
# failwith "error code %d" 3 ;;
Exception: Failure "error code 3".
ksprintf is the key function here. Like sprintf it takes a format string and a variable number of parameters, and makes a fixed result string. Unlike sprintf it doesn't return the string, but passes it to the function which is its first parameter — in this case, the standard failwith function. So ksprintf is useful because it can turn almost any fixed string function into a printf-like function.failwith function even shorter, like this:let failwith = ksprintf failwithIf you try this, you'll find the new function works some of the time, but fails to type-check at other times. In fact, the first time you use it, it seems to "remember" the type of all the arguments, and then refuses to work if any of those types change:
# failwith "hello, %s" "world" ;;If we take a close look at the inferred types of the wrong definition, we can see why:
Exception: Failure "hello, world".
# failwith "error code %d" 3 ;;
This expression has type (int -> 'a, 'b, 'c, 'd, 'd, 'a) format6
but is here used with type
(string -> 'e, unit, string, 'e) format4 =
(string -> 'e, unit, string, string, string, 'e) format6
# let failwith = ksprintf failwith ;;
val failwith : ('_a, unit, string, '_b) format4 -> '_a = <fun>
'_a (with an underscore) is not a polymorphic type, but a single type that the compiler just hasn't been able to infer fully yet. As soon as you give it more information (eg. calling the function), the compiler infers that type into some concrete type (like string -> ... above) and won't let you change it later.In a followup, I gave some example code:
OCaml cannot just directly access C globals. At best you'd need to have a function that returns the address of the C global, _and_ the C global would need to be in a form that OCaml code could understand although that is pretty easy to arrange.
------------------------------------------------------------ test_c.c
/* Variable shared between C and OCaml. */
#include <caml/mlvalues.h>
/* On the OCaml side, this will be made to look like a structure
* containing a single int (well, provided you don't look *too*
* closely at it).
*/
value shared_var = Val_int (0);
value
get_struct_addr (value unitv)
{
/* Return the address of the 'structure'. */
return ((value) &shared_var);
}
/* Increment the shared variable. */
value
increment_it (value unitv)
{
int i = Int_val (shared_var);
i++;
shared_var = Val_int (i);
return (Val_unit);
}
----------------------------------------------------------------------
------------------------------------------------------------ test.ml
(* Variable shared between C and OCaml. *)
type var = {
shared_var : int;
}
external get_struct_addr : unit -> var = "get_struct_addr" "noalloc"
external increment_it : unit -> unit = "increment_it" "noalloc"
let var = get_struct_addr () ;;
while true do
Printf.printf "value of the variable now is %d\n%!" var.shared_var;
increment_it ();
(* OCaml isn't expecting that increment_it modifies a variable, so
* there is no guarantee that we will see the changed value next
* time around.
*)
Unix.sleep 1;
done
----------------------------------------------------------------------
$ gcc -I /usr/lib/ocaml -c test_c.c
$ ocamlopt -c test.ml
$ ocamlopt unix.cmxa test_c.o test.cmx -o test
$ ./test
value of the variable now is 0
value of the variable now is 1
value of the variable now is 2
value of the variable now is 3
value of the variable now is 4
[etc.]
then just using
external call_1 : float -> float = "call_1"
call_1. However these calls are not direct. They go via an OCaml runtime function called caml_c_call. This is a tiny bit of assembler, so the overhead isn't large, but it does use a computed jump which on many processors is quite slow.
external call_2 : float -> float = "call_2" "noalloc"
Normal "noalloc"
pushl %eax pushl %eax
movl $call_1, %eax call call_2
call caml_c_call addl $4, %esp
addl $4, %esp
...
caml_c_call:
movl (%esp), %edx
movl %edx, G(caml_last_return_address)
leal 4(%esp), %edx
movl %edx, G(caml_bottom_of_stack)
jmp *%eax
10 FOR F = 0 TO 2*PI STEP 0.1
20 PLOT SIN(F)*200+100, COS(F)*200+100
30 NEXT F
printf ("hello %s\n", name);
printf "hello %s\n" name;
f (g a b) c
g a b is a function call (in C it would be written as g (a, b)), and that f (...) c is a function call with two parameters (in C it would be written as f (g (a, b), c).)
let x = foo
x is not a variable. It's just a name which refers to foo, and there is no way to change its value. The technical term is a let-binding.x with a different value, but that doesn't change the original x or foo:
let x = foo in
let x = bar in
...
let quit = false in
while not quit do
let line = read_line () in
if line = "q" then let quit = true in ();
print_endline line
done
quit is just a different label from the outer one. In this case you would get a compiler warning because the inner quit label is never used.
val average : float -> float -> float
average which takes two parameters, both floating point numbers, and returns a floating pointer number.
val print_string : string -> unit
unit is like void in C).
val int_of_string : string -> int
val open_out_gen : open_flag list -> int -> string -> out_channel
add : int -> int -> int
(add 42) : int -> int
(add 42 2) : int
virt-uname
'uname' command, shows OS version, architecture, etc.
virt-dmesg
'dmesg' command, shows kernel messages
virt-ps
'ps' command, shows process list
virt-ps (process listings) working today. Getting the process listing out of a stuck virtual machine is immensely useful to find out what's going on with the machine. For example, did it blow up because there are too many Apache processes? Or is some other daemon causing trouble? I had an initial implementation of this working, but it was rather slow and unsatisfactory because of the all the guessing and heuristics it had to do. In the meantime, I discovered that getting the Linux kernel version is quite easy, and once you know the kernel version you immediately reduce the amount of heuristics you need by a large factor. So the new implementation should be much faster.virt-ps working in time for the demo tomorrow?
(* bitmatch-import-c ext3.c > ext3.bmpp *)
open bitmatch "ext3.bmpp"
let () =
let fd = Unix.openfile "/dev/sda1" [Unix.O_RDONLY] 0 in
let bits = Bitmatch.bitstring_of_file_descr_max fd 4096 in
bitmatch bits with
| { :ext3_super_block } ->
printf "free blocks = %ld\n" s_free_blocks_count
| { _ } ->
printf "/dev/sda1 is not an ext3 filesystem\n"
| Read-only operations | Read/write operations |
|---|---|
get hypervisor type get version get hostname get URI get num CPUs list VMs list networks get VM CPU stats | suspend VM resume VM shutdown VM destroy VM coredump VM create VM change CPU pinning |
let conn = Libvirt.Connect.connect_readonly ()
let dom = Libvirt.Domain.lookup_by_name conn "test"
...
Libvirt.Domain.destroy dom (* fail! *)
printf "Connect read-only?" ;;
let readonly_flag = read_line () = "y"
let conn = Libvirt.Connect.open readonly_flag
(* etc. *)
'a list, lists of any type, where 'a (pronounced alpha) stands for all types. Here's another example of a polymorphic type, a structure which contains a "free" field that can store any particular type:
type 'a t = { data : 'a }
# { data = "hello" } ;;
- : string t = {data = "hello"}
t an alias for the floating point type:
type t = float
type 'a t = float
# (3.0 : unit t);;
- : unit t = 3.
# (10.4 : string t);;
- : string t = 10.4
'a isn't needed, it can be set to any type (unit and string in the examples above).string t (I'll call them "stringies"):
# let add_stringies (a : string t) (b : string t) = (a +. b : string t) ;;
val add_stringies : string t -> string t -> string t = <fun>
# add_stringies (3.0 : unit t) 5.0 ;;
- : string t = 8.
unit t and string t can be freely unified with each other because the compiler knows that both are really just floats:
# ((3.0 : unit t) : string t) ;;
- : string t = 3.
add_stringies correctly, we have to hide the real type of t inside a module, like this:
module T : sig
type 'a t
val add_stringies : string t -> string t -> string t
end = struct
type 'a t = float
let add_stringies a b = a +. b
end
module Length : sig
type 'a t
val meters : float -> [`Meters] t
val feet : float -> [`Feet] t
val (+.) : 'a t -> 'a t -> 'a t
val to_float : 'a t -> float
end = struct
type 'a t = float
external meters : float -> [`Meters] t = "%identity"
external feet : float -> [`Feet] t = "%identity"
let (+.) = (+.)
external to_float : 'a t -> float = "%identity"
end
open Length
open Printf
let () =
let m1 = meters 10. in
let m2 = meters 20. in
printf "10m + 20m = %g\n" (to_float (m1 +. m2));
let f1 = feet 40. in
let f2 = feet 50. in
printf "40ft + 50ft = %g\n" (to_float (f1 +. f2));
(*printf "10m + 50ft = %g\n" (to_float (m1 +. f2)) (* error *) *)
[`Meters] t which I hope is obvious as to what it contains. It also means the error messages from the compiler will be easy to read - that's important because we are expecting to get a compiler error each time the programmer makes a mistake.meters and feet) convert floating point numbers into meters or feet respectively. But the implementation of these functions is completely efficient. They're just the identity operation (which the compiler turns into a null operation). At compile time, the values have this extra type information. But at run time the overhead evaporates completely. At run time, these are just floats.'a t -> 'a t -> 'a t. This means you can use it on two meter measurements, or two feet measurements, but you cannot mix meters and feet. Furthermore the return type is the same as the input types, so this safety cascades through all code.to_float function, but for better safety we'd probably want to define special print functions which ensure that the output indicates the correct type back to the user.[`Readonly] t and a read/write connection will have type [`Readonly|`Readwrite] t which means that it's compatible with the read-only type but has the extra read/write ability.[>`Readonly] t -> ... because they work with "read-only or greater".
module Connection : sig
type 'a t
val connect_readonly : unit -> [`Readonly] t
val connect : unit -> [`Readonly|`Readwrite] t
val status : [>`Readonly] t -> int
val destroy : [>`Readwrite] t -> unit
end = struct
type 'a t = int
let count = ref 0
let connect_readonly () = incr count; !count
let connect () = incr count; !count
let status c = c
let destroy c = ()
end
open Connection
open Printf
let () =
let conn = connect_readonly () in
printf "status = %d\n" (status conn);
(*destroy conn; (* error *) *)
let conn = connect () in
printf "status = %d\n" (status conn);
destroy conn
destroy and notice that the error is caught by the compiler.
So there are four "subtypes" (states?) of memory map, summarized in the diagram on the left.mli file). The specifics of the implementation (from the ml file) aren't important here. Note that because there are two degrees of freedom (word size and endianness), there are two phantom types attached to t:
type ('a,'b) t
of_file function makes a memory map from a file descriptor and base address. It returns a memory map with no word size or endianness, which is pretty clearly expressed in the return type:
val of_file : Unix.file_descr -> addr -> ([`NoWordsize], [`NoEndian]) t
find function searches for strings in the memory map:
val find : ('a, 'b) t -> ?start:addr -> string -> addr option
find_align which finds strings that are aligned to the word size. This function cares that word size has been set, but not endianness, and its type is therefore:
val find_align : ([`Wordsize], 'b) t -> ?start:addr -> string -> addr option
find_pointer function looks for pointers appearing in the memory map. Pointers have both endianness and word size implicitly, so this function can only be used when both have been set on the memory map. Its type is:
val find_pointer : ([`Wordsize], [`Endian]) t -> ?start:addr -> addr ->
addr option
val set_wordsize : ([`NoWordsize], 'b) t -> wordsize ->
([`Wordsize], 'b) t
val set_endian : ('a, [`NoEndian]) t -> endian ->
('a, [`Endian]) t
find_pointer until they have called both set_wordsize and set_endian exactly once (although the order they call the functions doesn't matter).find_pointer) which need it. But you can be sure that the runtime checks will never fail. An improvement to the code would be to write it so it doesn't need any runtime checks at all.
type conn_t =
| No_connection
| RO of Libvirt.ro Libvirt.Connect.t
| RW of Libvirt.rw Libvirt.Connect.t
type xml =
| Element of (string * (string * string) list * xml list)
| PCData of string
<a href="http://camltastic.blogspot.com/">Camltastic!</a>
let str = PCData "Camltastic!"
let doc = Element ("a",
["href", "http://camltastic.blogspot.com/"],
[str])
str and doc) and both have type xml.
let h = Hashtbl.create 13
let add_annot node data = Hashtbl.add h node data
let get_annot node =
try Some (Hashtbl.find h node)
with Not_found -> None
# add_annot doc "doc is annotated with this string" ;;
- : unit = ()
# get_annot doc ;;
- : string option = Some "doc is annotated with this string"
# get_annot str ;;
- : string option = None
<a>Link</a><a>Link</a> then there is no way to attach different data to the two links, because both links appear to be equal (they are equal, structurally). Attaching data to one link attaches the data to both.Weak. This module implements weak pointers. A weak pointer is like an ordinary pointer, but it doesn't "count" towards garbage collection. In other words if a value on the heap is only pointed at by weak pointers, then the garbage collector may free that value as if nothing was pointing to it.And I won't recur (pun, intended) on the subject of how easy it is to write code to process these data structures.
type xml =
| Element of (string * (string * string) list * xml list)
| PCData of string
xml type defined above is not extensible at all. In order to allow it to store styles and sizes for replaced elements we'd need to change the type definition to something like this:Unfortunately this is no use to us because all our existing code that worked on the old
type xml =
| Element of (string * (string * string) list * xml list
* style list * size option)
| PCData of string
xml type no longer works on this new type. That is no mere theoretical concern either, because the old xml type at the top wasn't just chosen at random. It is in fact the type used by Xml-Light, a useful, simplified XML parser and printer, and we cannot change this type without stopping using this library.xml type defined above, it looks similar to this:This is half-way useful, because one can now use the library functions (they assume polymorphism, so work fine), but has some serious shortcomings of its own. One is that you can't easily build up independent modules. If there is a PXP module which uses the
type 'a xml =
| Element of ('a, ...
'a for its own purpose then one cannot extend the XML document any further. If you avoid that, you can attach multiple data by using a tuple, but each module that wants to attach data had better know about all the other modules in advance and agree on a single tuple type.'b styles with 'b instantiated as size when that is needed. You'd end up with document types like this:But you can't write a function that just operates on "an XML document annotated with sizes", unless it "knows" about all the other annotations and the order in which they were applied. (Is the type
unit xml # The basic document
unit styles xml # After annotating with styles (pass 1)
size styles xml # After annotating with sizes (pass 2)
size styles xml or styles size xml if we happened to swap the two passes around?)