Revision: 4528 http://sourceforge.net/p/vexi/code/4528 Author: mkpg2 Date: 2013-05-26 10:24:01 +0000 (Sun, 26 May 2013) Log Message: ----------- Improve Memory Performance. - Use org.ibex.js.util.Hash instead of java.util.HashMap for js object.
Modified Paths: -------------- branches/vexi4/org.vexi-core.devtools/src/main/java/org/vexi/instrument/memoryhist/MemoryHist.java branches/vexi4/org.vexi-core.main/src/main/jpp/org/vexi/core/Box.jpp branches/vexi4/org.vexi-library.js/meta/module.xml branches/vexi4/org.vexi-library.js/src/main/java/org/ibex/js/JSArrayLike.java branches/vexi4/org.vexi-library.js/src/main/java/org/ibex/js/JSU.java branches/vexi4/org.vexi-library.js/src/main/jpp/org/ibex/js/Interpreter.jpp branches/vexi4/org.vexi-library.js/src/main/jpp/org/ibex/js/JS.jpp branches/vexi4/org.vexi-library.js/src/main/jpp/org/ibex/js/JSArray.jpp branches/vexi4/org.vexi-library.js/src/test/java/test/js/exec/array/TestArray.java branches/vexi4/org.vexi-library.js/src/test/java/test/js/exec/general/typeof.js Added Paths: ----------- branches/vexi4/org.vexi-library.js/src/poke/java/poke/ branches/vexi4/org.vexi-library.js/src/poke/java/poke/hash/ branches/vexi4/org.vexi-library.js/src/poke/java/poke/hash/PokeQuadraticProbing.java branches/vexi4/org.vexi-library.js/src/poke/java/poke/hash/StressTestHash.java Property Changed: ---------------- branches/vexi4/org.vexi-library.js/ Modified: branches/vexi4/org.vexi-core.devtools/src/main/java/org/vexi/instrument/memoryhist/MemoryHist.java =================================================================== --- branches/vexi4/org.vexi-core.devtools/src/main/java/org/vexi/instrument/memoryhist/MemoryHist.java 2013-05-26 10:16:43 UTC (rev 4527) +++ branches/vexi4/org.vexi-core.devtools/src/main/java/org/vexi/instrument/memoryhist/MemoryHist.java 2013-05-26 10:24:01 UTC (rev 4528) @@ -8,6 +8,7 @@ import javax.swing.*; import org.ibex.js.*; +import org.ibex.util.Basket; // FIXME - move devl js implementation out of here public class MemoryHist extends JS.Immutable implements Instr.Memory { @@ -58,7 +59,7 @@ return s; } - private void hist_inc(SortedMap<Integer,Integer> hist, Map hash){ + private void hist_inc(SortedMap<Integer,Integer> hist, Basket.Map hash){ Integer size = hash==null?0:hash.size(); Integer count = hist.get(size); if(count==null) count = 0; Modified: branches/vexi4/org.vexi-core.main/src/main/jpp/org/vexi/core/Box.jpp =================================================================== --- branches/vexi4/org.vexi-core.main/src/main/jpp/org/vexi/core/Box.jpp 2013-05-26 10:16:43 UTC (rev 4527) +++ branches/vexi4/org.vexi-core.main/src/main/jpp/org/vexi/core/Box.jpp 2013-05-26 10:24:01 UTC (rev 4528) @@ -3327,5 +3327,5 @@ // JSArrayLike public JS getElement(int i) throws JSExn { return get(i); } public JS[] toArray() throws JSExn { throw new JSExn("Cannot convert box to an array"); } - public int size() throws JSExn { return JSU.toInt(getAndTriggerTraps(SC_numchildren)); } + public int length() throws JSExn { return JSU.toInt(getAndTriggerTraps(SC_numchildren)); } } Index: branches/vexi4/org.vexi-library.js =================================================================== --- branches/vexi4/org.vexi-library.js 2013-05-26 10:16:43 UTC (rev 4527) +++ branches/vexi4/org.vexi-library.js 2013-05-26 10:24:01 UTC (rev 4528) Property changes on: branches/vexi4/org.vexi-library.js ___________________________________________________________________ Modified: svn:ignore ## -26,3 +26,105 ## release .configuration + +.configuration + +gen + +.classpath + +.project + +.settings + +.configuration + +gen + +.classpath + +.project + +.settings + +old + +.configuration + +gen + +.classpath + +.project + +.settings + +.configuration + +gen + +.classpath + +.project + +.settings + +.configuration + +gen + +.classpath + +.project + +.settings + +.configuration + +gen + +.classpath + +.project + +.settings + +.configuration + +gen + +.classpath + +.project + +.settings + +.configuration + +gen + +.classpath + +.project + +.settings + +.configuration + +gen + +.classpath + +.project + +.settings + +.configuration + +gen + +.classpath + +.project + +.settings Modified: branches/vexi4/org.vexi-library.js/meta/module.xml =================================================================== --- branches/vexi4/org.vexi-library.js/meta/module.xml 2013-05-26 10:16:43 UTC (rev 4527) +++ branches/vexi4/org.vexi-library.js/meta/module.xml 2013-05-26 10:24:01 UTC (rev 4528) @@ -4,6 +4,7 @@ <artifact name="jpp.zip" /> <dependencies> + <system name="java.jre" tag="1.5"/> <dependency source="local" name="library.value"/> <dependency source="local" name="library.net"/> <dependency source="local" name="library.io"/> Modified: branches/vexi4/org.vexi-library.js/src/main/java/org/ibex/js/JSArrayLike.java =================================================================== --- branches/vexi4/org.vexi-library.js/src/main/java/org/ibex/js/JSArrayLike.java 2013-05-26 10:16:43 UTC (rev 4527) +++ branches/vexi4/org.vexi-library.js/src/main/java/org/ibex/js/JSArrayLike.java 2013-05-26 10:24:01 UTC (rev 4528) @@ -3,7 +3,9 @@ public interface JSArrayLike extends JS { public JS[] toArray() throws JSExn; public JS getElement(int i) throws JSExn; - public int size() throws JSExn; + public int length() throws JSExn; + + static public class Keys extends JS.Keys { JSArrayLike keysof; public Keys(JSArrayLike obj) { @@ -11,15 +13,15 @@ this.keysof = obj; } public boolean hasKey(JS key) throws JSExn { - return JSU.isInt(key) && (JSU.toInt(key)< keysof.size()); + return JSU.isInt(key) && (JSU.toInt(key)< keysof.length()); } public Enumeration iterator() throws JSExn { return new Enumeration(null) { private int n = 0; - public boolean _hasNext() throws JSExn { return n < keysof.size(); } + public boolean _hasNext() throws JSExn { return n < keysof.length(); } public JS _next() { return JSU.N(n++); } }; } - public int size() throws JSExn { return keysof.size(); } + public int size() throws JSExn { return keysof.length(); } } } Modified: branches/vexi4/org.vexi-library.js/src/main/java/org/ibex/js/JSU.java =================================================================== --- branches/vexi4/org.vexi-library.js/src/main/java/org/ibex/js/JSU.java 2013-05-26 10:16:43 UTC (rev 4527) +++ branches/vexi4/org.vexi-library.js/src/main/java/org/ibex/js/JSU.java 2013-05-26 10:24:01 UTC (rev 4528) @@ -166,7 +166,7 @@ static public JS constructorsOf(JS arg){ JSArray r = new JSArray(); if(!(arg instanceof JS.Obj)) return r; - for(ConstructorList l = ((JS.Obj)arg).constructors;l!=null;l=l.next){ + for(ConstructorList l = ((JS.Obj)arg).getConstructorList();l!=null;l=l.next){ r.add(l.value); } return r; Modified: branches/vexi4/org.vexi-library.js/src/main/jpp/org/ibex/js/Interpreter.jpp =================================================================== --- branches/vexi4/org.vexi-library.js/src/main/jpp/org/ibex/js/Interpreter.jpp 2013-05-26 10:16:43 UTC (rev 4527) +++ branches/vexi4/org.vexi-library.js/src/main/jpp/org/ibex/js/Interpreter.jpp 2013-05-26 10:24:01 UTC (rev 4528) @@ -800,7 +800,7 @@ } public JS.Keys keys() throws JSExn { return new JSArrayLike.Keys(this);} public JS[] toArray() { return args;} - public int size(){ return args.length; } + public int length(){ return args.length; } } static class TrapArgs extends JS.Immutable { Modified: branches/vexi4/org.vexi-library.js/src/main/jpp/org/ibex/js/JS.jpp =================================================================== --- branches/vexi4/org.vexi-library.js/src/main/jpp/org/ibex/js/JS.jpp 2013-05-26 10:16:43 UTC (rev 4527) +++ branches/vexi4/org.vexi-library.js/src/main/jpp/org/ibex/js/JS.jpp 2013-05-26 10:24:01 UTC (rev 4528) @@ -4,7 +4,14 @@ package org.ibex.js; -import java.util.*; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Set; + +import org.ibex.js.util.Hash; +import org.ibex.js.util.MetaKey; import org.vexi.util.BasicTree; /*@PAGE(concept=Traps) @@ -284,7 +291,7 @@ /** Returns a 'keys object'. */ public JS.Keys keys() throws JSExn; - public Set keySet() throws JSExn; + public JS[] keysList() throws JSExn; /** Return the value associated with the given key. */ public JS get(JS key) throws JSExn; @@ -335,7 +342,7 @@ public JS unclone() { return this; } public Keys keys() throws JSExn { throw new JSExn(type()+" has no key set"); } - public Set keySet() throws JSExn { throw new JSExn(type()+" has no key set"); } + public JS[] keysList() throws JSExn { throw new JSExn(type()+" has no key set"); } public JS get(JS key) throws JSExn { //#noswitch - leave, needed for jsdoc /*@PAGE(varname=Immutable,humanname=Immutable JS Object) @@ -431,12 +438,20 @@ this.clonee = (Cloneable)clonee; } + private void addAll(Set s, JS[] arr){ + for(JS a: arr) s.add(a); + } + private Set<JS> asSet(JS[] arr){ + Set r = new HashSet(arr.length); + addAll(r, arr); + return r; + } public Keys keys() throws JSExn { - final Set ourkeys = keySet(); + final Set ourkeys = asSet(keysList()); final Set allkeys = new HashSet(ourkeys); JS c = this.clonee; while (true) { - allkeys.addAll(c.keySet()); + addAll(allkeys, c.keysList()); if (c instanceof Clone) c = ((Clone)c).clonee; else break; } @@ -517,30 +532,29 @@ /** Standard JS object implementation that can store arbitrarily named properties * and can have traps dynamically placed upon properties. */ - public static class Obj implements Cloneable, Copyable { + public static class Obj extends Hash implements Cloneable, Copyable { static public JS.Constructor Constructor = new JS.Constructor("Object") { public JS new_(JS[] args) throws JSExn { return new JS.Obj(); } }; static public ConstructorList base = new ConstructorList(Constructor,null); - - + + // contains traps + // and meta properties + private Hash other = null; protected ConstructorList constructors = null; - // a map of values against properties stored on this object - private Map props = null; - // a map of traps against properties stored on this object - private Map traps = null; public Obj() { this(base); } public Obj(ConstructorList constructor) { - if (Instr.memory!=null) { + super(0); + if (Instr.memory!=null) { Instr.memory.register(this); } - constructors = constructor; + setConstructorList(constructor); } protected void finalize() throws Throwable { @@ -548,31 +562,37 @@ Instr.memory.unregister(this); } } + public Object getOther(Object key){ + if(other==null) return null; + return other.get(key); + } - // HACK - neat, but still a hack. At least until the semantics are clear - // should we mirror, or should it just be a straight copy? - //protected Obj mirror() { - // if (props==null) props = new HashMap(4); - // if (traps==null) traps = new HashMap(4); - // Obj r = new Obj(); - // r.traps = traps; - // r.props = props; - // return r; - //} + public void putOther(Object key, Object obj){ + if(other==null){ + other = new Hash(1); + } + other.put(key, obj); + } + + public ConstructorList getConstructorList(){ return constructors; } + private void setConstructorList(ConstructorList cl){ constructors = cl; } + + + public JS copy() throws JSExn { if (getClass()!=Obj.class) { throw new JSExn("copy not supported for: "+getClass().getName()); } JS.Obj r = new JS.Obj(); - if (traps!=null && traps.size()>0) { - Iterator I = traps.keySet().iterator(); - while (I.hasNext()) { - JS key = (JS)I.next(); - Trap t = (Trap) traps.get(key); - r.addTrap(key, (JS)t); - } - } +// if (traps!=null && traps.size()>0) { +// Iterator I = traps.keySet().iterator(); +// while (I.hasNext()) { +// JS key = (JS)I.next(); +// Trap t = (Trap) traps.get(key); +// r.addTrap(key, (JS)t); +// } +// } Enumeration E = keys().iterator(); while (E.hasNext()) { JS key = E.next(); @@ -586,15 +606,15 @@ if (getClass()!=Obj.class) { throw new JSExn("deepcopy not supported for: "+getClass().getName()); } - if (traps!=null && traps.size()>0) { - Iterator I = traps.keySet().iterator(); - while (I.hasNext()) { - JS key = (JS)I.next(); - Trap t = (Trap) traps.get(key); - if (t.findRead()!=null) - throw new JSExn("cannot deepcopy object with read traps: "+JSU.toString(key)); - } - } +// if (traps!=null && traps.size()>0) { +// Iterator I = traps.keySet().iterator(); +// while (I.hasNext()) { +// JS key = (JS)I.next(); +// Trap t = (Trap) traps.get(key); +// if (t.findRead()!=null) +// throw new JSExn("cannot deepcopy object with read traps: "+JSU.toString(key)); +// } +// } JS.Obj r = new JS.Obj(); Enumeration E = keys().iterator(); while (E.hasNext()) { @@ -604,10 +624,10 @@ } return r; } - public Set keySet() throws JSExn { - return props!=null? - props.keySet(): - new HashSet(); // SHOULD use a constant here + public JS[] keysList() throws JSExn { + JS[] r = new JS[size()]; + listKeys(r); + return r; } public JS unclone() { return this; } public String[] getFormalArgs() { return EMPTY_STRING_ARRAY; } @@ -624,8 +644,9 @@ } public boolean instanceOf(JS constructor) { - // HACK sort this out. Type system based on constructors not finalised yet - for (ConstructorList cl=constructors; cl!=null; cl=cl.next) { + ConstructorList cl = getConstructorList(); + // HACK sort this out. Type system based on constructors not finalised yet + for (; cl!=null; cl=cl.next) { if (cl.value==constructor) { return true; } @@ -633,7 +654,8 @@ return constructor==this; } public void addConstructor(JS c) { - constructors = new ConstructorList(c,constructors); + ConstructorList cl0 = getConstructorList(); + setConstructorList(new ConstructorList(c,cl0)); } public Keys keys() throws JSExn { @@ -641,7 +663,7 @@ public Enumeration iterator() throws JSExn { return new Enumeration(null) { private int cur = 0; - private Object[] keys = props==null?EMPTY_JS_ARRAY:props.keySet().toArray(); + private Object[] keys = Obj.this.keysList(); public boolean _hasNext() { return cur < keys.length; } public JS _next() throws JSExn { if (cur >= keys.length) { @@ -696,62 +718,42 @@ * */ return getSafe(key); } - final public JS getSafe(JS key) { // workaround, avoid JSExn in signature - if (props==null) { - return null; - } - return (JS)props.get(key); + final public JS getSafe(JS key) { // workaround, avoid JSExn in signature + return (JS)super.get(key); } /** Store a value against a key on the object. */ public void put(JS key, JS value) throws JSExn { putSafe(key, value); } public void putSafe(JS key, JS value) { // workaround, avoid JSExn in signature - // FEATURE: MEMORY OPTIMIZE? - // if (props==null) props = new SmallMap(); - // else if (props.size()>4) props = new HashMap(props); - if (props==null) { - props = new HashMap(4); - } - props.put(key, value); + super.put(key, value); } protected boolean hasKey(JS key) { // SHOULD decide, do trapped properties have keys? Perhaps needs some // mechanism, e.g. setting a property on the trap function. - if (props==null) return false; - return props.containsKey(key); + return super.containsKey(key); } /** Removes a key and any value associated with it. */ protected void remove(JS key) { - if (props==null) return; - props.remove(key); + super.remove(key); } - private int size() { return props==null ? 0 : props.size(); } - /** Gets a trap for the given key */ public Trap getTrap(JS key) { - if (traps==null) { - return null; - } - return (Trap)traps.get(key); + return (Trap)getOther(key); } /** Places a trap on the given key. */ public void putTrap(JS key, Trap t) { - // FEATURE: MEMORY OPTIMIZE? - // if (traps==null) traps = new SmallMap(); - // else if(traps.size()>4) traps = new HashMap(traps); - if (traps==null) traps = new HashMap(4); - traps.put(key, t); + putOther(key, t); } /** Places the given function as a trap on the given key, first validating the function. */ public void addTrap(JS key, JS f) throws JSExn { TrapHolder.validateFunction(f); // ensure a trap is only be applied once to an object - Trap t0 = traps==null?null:(Trap)traps.get(key); + Trap t0 = getTrap(key); for (Trap t=t0; t != null; t = t.next()) { // REMARK: we are only interested in this JS object // although other JS implementations may cause trap @@ -766,7 +768,7 @@ /** Removes the trap represented by the given function from the given key. * If there is no trap represented by the function for the key, it does nothing */ public void delTrap(JS key, JS f) throws JSExn { - Trap t = traps==null?null:(Trap)traps.get(key); + Trap t = getTrap(key); if (t==null) { return; } @@ -775,7 +777,7 @@ t = t.next(); // no traps left on this object if (t==null || !t.target().equals(this)) { - traps.remove(key); + other.remove(key); } else { putTrap(key, t); } @@ -795,8 +797,9 @@ } private String stringPrefix(){ - if(constructors==null) return "object"; - String r = JSU.toString(constructors.value); + ConstructorList cl = getConstructorList(); + if(cl==null) return "object"; + String r = JSU.toString(cl.value); if(r.lastIndexOf("$")!=-1){ r = r.substring(0,r.lastIndexOf("$")); } @@ -813,8 +816,8 @@ return stringPrefix()+ "$" + Integer.toHexString(hashCode()); } - public Map getPropsMap() { return traps; } - public Map getTrapsMap() { return props; } + public Map getPropsMap() { return this; } + public Map getTrapsMap() { return other; } } /** An implementation of JS that supports a meta trap on all properties */ @@ -1141,7 +1144,7 @@ } public JS getElement(int i) throws JSExn { return get(JSU.N(i)); } - public int size() throws JSExn { return bt.treeSize(); } + public int length() throws JSExn { return bt.treeSize(); } public JS[] toArray() throws JSExn { throw new JSExn("Cannot convert proxylist to an array"); } public JS.Keys keys() throws JSExn { return new JSArrayLike.Keys(this); } Modified: branches/vexi4/org.vexi-library.js/src/main/jpp/org/ibex/js/JSArray.jpp =================================================================== --- branches/vexi4/org.vexi-library.js/src/main/jpp/org/ibex/js/JSArray.jpp 2013-05-26 10:16:43 UTC (rev 4527) +++ branches/vexi4/org.vexi-library.js/src/main/jpp/org/ibex/js/JSArray.jpp 2013-05-26 10:24:01 UTC (rev 4528) @@ -25,7 +25,7 @@ public JS type() { return SC_array; } public JS unclone() { return this; } public JS.Keys keys() throws JSExn { return new JSArrayLike.Keys(this); } - public Set keySet() throws JSExn { throw new RuntimeException("!"); } + public JS[] keysList() throws JSExn { throw new RuntimeException("!"); } public boolean isTruthy(){ return true; } public JS get(JS key) throws JSExn { @@ -245,7 +245,7 @@ } private void concat_fill(JSArrayLike arr) throws JSExn { - for (int i=0; i<arr.size(); i++) { + for (int i=0; i<arr.length(); i++) { add(arr.getElement(i)); } } @@ -275,7 +275,7 @@ throw new JSExn("Arg "+i+" not an []"); } JSArrayLike a = ((JSArrayLike)args[i]); - l += a.size(); + l += a.length(); } JSArray r = new JSArray(l); r.concat_fill(this); @@ -372,6 +372,7 @@ return r; } + public int length(){ return size(); } public JS getElement(int i) throws JSExn { return (JS) get(i); } public JS[] toArray() { JS[] r = new JS[size()]; Added: branches/vexi4/org.vexi-library.js/src/poke/java/poke/hash/PokeQuadraticProbing.java =================================================================== --- branches/vexi4/org.vexi-library.js/src/poke/java/poke/hash/PokeQuadraticProbing.java (rev 0) +++ branches/vexi4/org.vexi-library.js/src/poke/java/poke/hash/PokeQuadraticProbing.java 2013-05-26 10:24:01 UTC (rev 4528) @@ -0,0 +1,42 @@ +package poke.hash; + +import java.util.HashMap; +import java.util.Map; + +public class PokeQuadraticProbing { + static void scan(int n){ + System.err.println("-------"+n+"---------"); + for(int hash=0; hash<n; hash++){ + int probe=hash; + int inc=1; + Map visited = new HashMap(n); + int counter =0; + while(true){ + if(visited.get(probe)!=null){ + break; + } + + visited.put(probe, counter++); + + probe = probe + inc % n; + inc = inc + 1; + } + + System.err.println(visited.size()); + } + + + } + + static void run(){ + int size = 2; + for(int i=0; i<10; i++){ + scan(size); + size = size*2; + } + } + + static public void main(String[] args) { + run(); + } +} Property changes on: branches/vexi4/org.vexi-library.js/src/poke/java/poke/hash/PokeQuadraticProbing.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Added: branches/vexi4/org.vexi-library.js/src/poke/java/poke/hash/StressTestHash.java =================================================================== --- branches/vexi4/org.vexi-library.js/src/poke/java/poke/hash/StressTestHash.java (rev 0) +++ branches/vexi4/org.vexi-library.js/src/poke/java/poke/hash/StressTestHash.java 2013-05-26 10:24:01 UTC (rev 4528) @@ -0,0 +1,29 @@ +package poke.hash; + +import java.util.HashMap; +import java.util.Random; + +import org.ibex.js.util.Hash; +import static junit.framework.Assert.*; +public class StressTestHash { + static public void main(String[] args) { + stressFill(); + } + static public void stressFill(){ + System.err.println("-- Stress Fill Hash --"); + HashMap expected = new HashMap(); + Hash hash = new Hash(); + + Random rand = new Random(); + for(int i=0; i<1000000; i++){ + Integer key = rand.nextInt(100000); + Object value = key; + + expected.put(key, value); + hash.put(key, value); + } + System.err.println(hash.size()); + assertEquals(expected.size(), hash.size()); + + } +} Property changes on: branches/vexi4/org.vexi-library.js/src/poke/java/poke/hash/StressTestHash.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Modified: branches/vexi4/org.vexi-library.js/src/test/java/test/js/exec/array/TestArray.java =================================================================== --- branches/vexi4/org.vexi-library.js/src/test/java/test/js/exec/array/TestArray.java 2013-05-26 10:16:43 UTC (rev 4527) +++ branches/vexi4/org.vexi-library.js/src/test/java/test/js/exec/array/TestArray.java 2013-05-26 10:24:01 UTC (rev 4528) @@ -15,7 +15,7 @@ public static void main(String[] args) throws Throwable { JSTestSuite jts = new JSTestSuite(TestArray.class); - TestCase t = jts.createTestCase(jts.getResourceDirs(), "array_index.js"); + TestCase t = jts.createTestCase(jts.getResourceDirs(), "wierd_sort.js"); t.runBare(); } } Modified: branches/vexi4/org.vexi-library.js/src/test/java/test/js/exec/general/typeof.js =================================================================== --- branches/vexi4/org.vexi-library.js/src/test/java/test/js/exec/general/typeof.js 2013-05-26 10:16:43 UTC (rev 4527) +++ branches/vexi4/org.vexi-library.js/src/test/java/test/js/exec/general/typeof.js 2013-05-26 10:24:01 UTC (rev 4528) @@ -18,8 +18,6 @@ var c = true; assert(typeof(c)=="boolean"); -var d = sys.date(); -assert(typeof(d)=="date"); try{throw "foo"; }catch(d){ This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. ------------------------------------------------------------------------------ Try New Relic Now & We'll Send You this Cool Shirt New Relic is the only SaaS-based application performance monitoring service that delivers powerful full stack analytics. Optimize and monitor your browser, app, & servers with just a few lines of code. Try New Relic and get this awesome Nerd Life shirt! http://p.sf.net/sfu/newrelic_d2d_may _______________________________________________ Vexi-svn mailing list Vexi-svn@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/vexi-svn