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
=
try
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
;
None
with
Found
(i,
img)
->
Some
(i,
img)
;;
# let
load_table
filename
table
=
if
table.
name
=
filename
then
()
(* the image is the current image *)
else
match
search_table
filename
table
with
Some
(i,
img)
->
(* the image found becomes the current image *)
table.
current
<-
img
;
table.
name
<-
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
;
table.
name
<-
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
1
0
"IMAGES/animfond.caa"
;;
val t : table_of_images =
{size=10; ind=0; name="IMAGES/animfond.caa";
current=
[|[|7372452; 7372452; 7372452; 7372452; 7372452; 7372452; 7372452;
7372452; 7372452; 7372452; 7372452; 7372452; 7505571; 7505571; ...|];
...|];
cache=...}
# load_table
"IMAGES/anim.caa"
t
;;
- : unit = ()
# print_table
t
;;
IMAGES/anim.caa [] [] [] [] [] [] [] [] [] - : unit = ()
This cache technique can be adapted to various applications.