Previous Contents Next

Module Weak

A weak pointer is a pointer to a region which the garbage collector may reclaim at any moment. It may be surprising to speak of a value that might disappear at any moment. In fact, we must see these weak pointers as a reservoir of values that may still be available. This turns out to be particularly useful when memory resources are small compared to the elements to be saved. The classic case is the management of a memory cache: a value may be lost, but it remains directly accessible as long as it exists.

In Objective CAML one cannot directly manipulate weak pointers, only arrays of weak pointers. The Weak module defines the abstract type 'a Weak.t, corresponding to the type 'a option array, a vector of weak pointers of type 'a. The concrete type 'a option is defined as follows:
type 'a option = None | Some of 'a;;
The main functions of this module are defined in figure 9.14.

function type
create int -> 'a t
set 'a t -> int -> 'a option -> unit
get 'a t -> int -> 'a option
check 'a t -> int -> bool

Figure 9.14: Main functions of the Weak module.

The create function allocates an array of weak pointers, each initialized to None. The set function puts a value of type 'a option at a specified index. The get function returns the value contained at index n in a table of weak pointers. The returned value is then referenced, and no longer reclaimable as long as this reference exists. To verify the effective existence of a value, one uses either the check function or pattern matching on the 'a option type's patterns. The former solution does not depend on the representation choice for weak pointers.

Standard functions for sequential structures also exist: length, for the length, and fill and blit for copies of parts of the array.

Example: an Image Cache

In an image-processing application, it is not rare to work on several images. When the user moves from one image to another, the first is saved to a file, and the other is loaded from another file. In general, only the names of the latest images processed are saved. In order to avoid overly frequent disk access while at the same time not using too much memory space, we use a memory cache which contains the last images loaded. The contents of the cache may be freed if necessary. We implement this with a table of weak pointers, leaving the decision of when to free the images up to the garbage collector. To load an image we first search the cache. If the image is there, it becomes the current image. If not, its file is read.

We define a table of images in the following manner:

# type table_of_images = {
size : int;
mutable ind : int;
mutable name : string;
mutable current : Graphics.color array array;
cache : ( string * Graphics.color array array) Weak.t } ;;
The field size gives the size of the table; the field ind gives the index of the current image; the field name, the name of the current image; the field current, the current image, and the field cache contains the array of weak pointers to the images. It contains the last images loaded and their names.

The function init_table initializes the table with its first image.

# let open_image filename =
let ic = open_in filename
in let i = ((input_value ic) : Graphics.color array array)
in ( close_in ic ; i ) ;;
val open_image : string -> Graphics.color array array = <fun>

# let init_table n filename =
let i = open_image filename
in let c = Weak.create n
in Weak.set c 0 (Some (filename,i)) ;
{ size=n; ind=0; name = filename; current = i; cache = c } ;;
val init_table : int -> string -> table_of_images = <fun>

The loading of a new image saves the current image in the table and loads the new one. To do this, we must first try to find the image in the cache.

# exception Found of int * Graphics.color array array ;;
# let search_table filename table =
for i=0 to table.size-1 do
if i<>table.ind then match Weak.get table.cache i with
Some (n,img) when n=filename -> raise (Found (i,img))
| _ -> ()
done ;
with Found (i,img) -> Some (i,img) ;;

# let load_table filename table =
if = filename then () (* the image is the current image *)
match search_table filename table with
Some (i,img) ->
(* the image found becomes the current image *)
table.current <- img ; <- filename ;
table.ind <- i
| None ->
(* the image isn't in the cache, need to load it *)
(* find an empty spot in the cache *)
let i = ref 0 in
while (!i<table.size && Weak.check table.cache !i) do incr i done ;
(* if none are free, take a full slot *)
( if !i=table.size then i:=(table.ind+1) mod table.size ) ;
(* load the image here and make it the current one *)
table.current <- open_image filename ;
table.ind <- !i ; <- filename ;
Weak.set table.cache table.ind (Some (filename,table.current)) ;;
val load_table : string -> table_of_images -> unit = <fun>
The load_table function tests to see if the image requested is current. If not, it checks the cache to see if the image exists; if that fails, the function loads the image from disk. In either of the latter two cases, it makes the image become the current one.

To test this program, we use the following cache-printing function:

# let print_table table =
for i = 0 to table.size-1 do
match Weak.get table.cache ((i+table.ind) mod table.size) with
None -> print_string "[] "
| Some (n,_) -> print_string n ; print_string " "
done ;;
val print_table : table_of_images -> unit = <fun>

Then we test the following program:

# let t = init_table 10 "IMAGES/animfond.caa" ;;
val t : table_of_images =
{size=10; ind=0; name="IMAGES/animfond.caa";
[|[|7372452; 7372452; 7372452; 7372452; 7372452; 7372452; 7372452;
7372452; 7372452; 7372452; 7372452; 7372452; 7505571; 7505571; ...|];
# load_table "IMAGES/anim.caa" t ;;
- : unit = ()
# print_table t ;;
IMAGES/anim.caa [] [] [] [] [] [] [] [] [] - : unit = ()

This cache technique can be adapted to various applications.

Previous Contents Next