192 lines
6.9 KiB
Standard ML
192 lines
6.9 KiB
Standard ML
(*
|
|
; This is adapted from a benchmark written by John Ellis and Pete Kovac
|
|
; of Post Communications.
|
|
; It was modified by Hans Boehm of Silicon Graphics.
|
|
; It was translated into Scheme by William D Clinger of Northeastern Univ,
|
|
; and modified for compatibility with the Gambit benchmark suite.
|
|
; It was translated into Standard ML by William D Clinger.
|
|
; Last modified 6 July 1999.
|
|
;
|
|
; This is no substitute for real applications. No actual application
|
|
; is likely to behave in exactly this way. However, this benchmark was
|
|
; designed to be more representative of real applications than other
|
|
; Java GC benchmarks of which we are aware.
|
|
; It attempts to model those properties of allocation requests that
|
|
; are important to current GC techniques.
|
|
; It is designed to be used either to obtain a single overall performance
|
|
; number, or to give a more detailed estimate of how collector
|
|
; performance varies with object lifetimes. It prints the time
|
|
; required to allocate and collect balanced binary trees of various
|
|
; sizes. Smaller trees result in shorter object lifetimes. Each cycle
|
|
; allocates roughly the same amount of memory.
|
|
; Two data structures are kept around during the entire process, so
|
|
; that the measured performance is representative of applications
|
|
; that maintain some live in-memory data. One of these is a tree
|
|
; containing many pointers. The other is a large array containing
|
|
; double precision floating point numbers. Both should be of comparable
|
|
; size.
|
|
;
|
|
; The results are only really meaningful together with a specification
|
|
; of how much memory was used. It is possible to trade memory for
|
|
; better time performance. This benchmark should be run in a 32 MB
|
|
; heap, though we don't currently know how to enforce that uniformly.
|
|
|
|
; In the Java version, this routine prints the heap size and the amount
|
|
; of free memory. There is no portable way to do this in Scheme; each
|
|
; implementation needs its own version.
|
|
*)
|
|
|
|
datatype Tree = Dummy
|
|
| Node of { left: Tree ref, right: Tree ref, i: int, j: int }
|
|
|
|
fun make_empty_node () =
|
|
Node { left= ref Dummy, right= ref Dummy, i= 0, j= 0 }
|
|
|
|
fun make_node (l, r) =
|
|
Node { left= ref l, right= ref r, i= 0, j= 0 }
|
|
|
|
fun PrintDiagnostics () = ()
|
|
|
|
fun gcbench kStretchTreeDepth =
|
|
|
|
let open Int
|
|
|
|
fun expt (m:int, n:int) =
|
|
if n = 0 then 1 else m * expt (m, n - 1)
|
|
|
|
(* Nodes used by a tree of a given size *)
|
|
fun TreeSize i =
|
|
expt (2, i + 1) - 1
|
|
|
|
(* Number of iterations to use for a given tree depth *)
|
|
fun NumIters i =
|
|
(2 * (TreeSize kStretchTreeDepth)) div (TreeSize i)
|
|
|
|
(*
|
|
; Parameters are determined by kStretchTreeDepth.
|
|
; In Boehm's version the parameters were fixed as follows:
|
|
; public static final int kStretchTreeDepth = 18; // about 16Mb
|
|
; public static final int kLongLivedTreeDepth = 16; // about 4Mb
|
|
; public static final int kArraySize = 500000; // about 4Mb
|
|
; public static final int kMinTreeDepth = 4;
|
|
; public static final int kMaxTreeDepth = 16;
|
|
; In Larceny the storage numbers above would be 12 Mby, 3 Mby, 6 Mby.
|
|
*)
|
|
|
|
val kLongLivedTreeDepth = kStretchTreeDepth - 2
|
|
val kArraySize = 4 * (TreeSize kLongLivedTreeDepth)
|
|
val kMinTreeDepth = 4
|
|
val kMaxTreeDepth = kLongLivedTreeDepth
|
|
|
|
(* Build tree top down, assigning to older objects. *)
|
|
fun Populate (iDepth, Node { left=lr, right=rr, i, j }) =
|
|
if iDepth <= 0
|
|
then false
|
|
else let val iDepth = iDepth - 1
|
|
in
|
|
(
|
|
lr := make_empty_node();
|
|
rr := make_empty_node();
|
|
Populate (iDepth, !lr);
|
|
Populate (iDepth, !rr)
|
|
)
|
|
end
|
|
|
|
(* Build tree bottom-up *)
|
|
fun MakeTree iDepth =
|
|
if iDepth <= 0
|
|
then make_empty_node()
|
|
else make_node (MakeTree (iDepth - 1),
|
|
MakeTree (iDepth - 1))
|
|
|
|
fun TimeConstruction depth =
|
|
let val iNumIters = NumIters depth
|
|
in
|
|
(
|
|
print (concat ["Creating ",
|
|
toString iNumIters,
|
|
" trees of depth ",
|
|
toString depth,
|
|
"\n"]);
|
|
let fun loop i =
|
|
if i < iNumIters
|
|
then (Populate (depth, make_empty_node());
|
|
loop (i + 1))
|
|
else ()
|
|
in loop 0
|
|
end;
|
|
let fun loop i =
|
|
if i < iNumIters
|
|
then (MakeTree depth;
|
|
loop (i + 1))
|
|
else ()
|
|
in loop 0
|
|
end
|
|
)
|
|
end
|
|
|
|
fun main () =
|
|
(
|
|
print "Garbage Collector Test\n";
|
|
print (concat [" Stretching memory with a binary tree of depth ",
|
|
toString kStretchTreeDepth,
|
|
"\n"]);
|
|
PrintDiagnostics();
|
|
(* Stretch the memory space quickly *)
|
|
MakeTree kStretchTreeDepth;
|
|
|
|
(* Create a long lived object *)
|
|
print (concat[" Creating a long-lived binary tree of depth ",
|
|
toString kLongLivedTreeDepth,
|
|
"\n"]);
|
|
let val longLivedTree = make_empty_node()
|
|
in
|
|
(
|
|
Populate (kLongLivedTreeDepth, longLivedTree);
|
|
|
|
(* Create long-lived array, filling half of it *)
|
|
print (concat [" Creating a long-lived array of ",
|
|
toString kArraySize,
|
|
" inexact reals\n"]);
|
|
let open Array
|
|
val arr = array (kArraySize, 0.0)
|
|
fun loop1 i =
|
|
if i < (kArraySize div 2)
|
|
then (update (arr, i, 1.0/(Real.fromInt(i)));
|
|
loop1 (i + 1))
|
|
else ()
|
|
fun loop2 d =
|
|
if d <= kMaxTreeDepth
|
|
then (TimeConstruction d;
|
|
loop2 (d + 2))
|
|
else ()
|
|
in
|
|
(
|
|
loop1 0;
|
|
PrintDiagnostics();
|
|
|
|
loop2 kMinTreeDepth;
|
|
|
|
if (longLivedTree = Dummy)
|
|
orelse
|
|
let val n = min (1000, (length(arr) div 2) - 1)
|
|
in Real.!= (sub (arr, n), (1.0 / Real.fromInt(n)))
|
|
end
|
|
then print "Failed\n"
|
|
else ()
|
|
(* fake reference to LongLivedTree
|
|
and array to keep them from being optimized away
|
|
*)
|
|
)
|
|
end)
|
|
end;
|
|
PrintDiagnostics())
|
|
in main()
|
|
end
|
|
|
|
fun main () =
|
|
run_benchmark ("gcbench",
|
|
1,
|
|
fn () => gcbench 18,
|
|
fn (result) => true)
|