[Rubydotnet-developer] Performance
Status: Alpha
Brought to you by:
thomas
|
From: Tim S. <ti...@ih...> - 2004-10-29 08:31:12
|
I've done some performance tests.
- The bridges that SaltyPickle folks wrote, and the one I wrote, use a
Hashtable of ids.
- Thomas' bridge uses GCHandle.
The Hashtable bridges force pointer equality comparison with Object#==
rather than the default Object#Equals which can be overridden by
subclasses.
The reason we do this is that we think "if two objects happen to be the
same when they're created, and we give them the same id, then one is
mutated the other will also be mutated. That would be bad."
Now imagine what happens when you do
require 'dotnet'
r = DotNet.new('somestring')
1_000_000.times {
r.ToUpper
}
Each string returned by ToUpper() has a different pointer, so is
different wrt Object#==. But GetHashCode() returns the same value for
each!
This makes our Hashtable lookup linear instead of constant. So many
collisions.
Of course some of these values will be garbage collected as we go along,
but my tests showed we have up to several thousand strings in the
Hashtable at once. (It cycles small ... thousand .. small etc. as the gc
is invoked.)
This makes performance baaad.
One solution is to recognise that strings (and integers and ...) are not
mutable, so do a special case for strings - use Equals in that case.
A better solution is to note that the implementation of Equals in
standard .NET classes is always == for mutable objects. (I think...)
Therefore Equals should always be used by the hashtable.
Alternatively, use GCHandle like Thomas did. The nice thing about this
is it makes handling garbage collection very easy. Simply have the Ruby
free function for the proxy object call GCHandle.Free().
Hashtable with Equals can actually perform about 1.5 times the speed of
using GCHandles in the artificial benchmark above. (Where we're getting
the same value returned over and over again.)
And now for some numbers...
The first set of numbers is from my head and may be a wrong:
my bridge was getting ~500 calls per second, the other two were a bit
faster, maybe 800 or 1500.
I made two changes to my bridge:
1) Made some things in my DotNet::Instance initialisation "lazy".
(Getting FullName, AssemblyID, creating DotNet::Class.) A big win since
the above example was never calling the result of `r.ToUpper'.
2) The Hashtable change. Either to use Equals or GCHandle.
And now for the nice numbers...
Using Hashtable with Equals: ~86,000 calls per second.
Using GCHandle: ~58,000 calls per second.
I actually think GCHandle is better because
- Simpler to code
- This benchmark is in someways the best case for Hashtable with
Equals. I suspect GCHandle will perform better on average in real
life.
|