[Mono-dev] difference in performance between mono OSX and linux?
Reimer Behrends
behrends at gmail.com
Sun Jan 22 11:39:00 UTC 2012
On 21/01/2012 19:28, Jonathan Shore wrote:
> So I am wondering whether there are differences in implementation
> between mono on these platforms that could account for a significant
> performance difference?
First of all, since your code appears to be multi-threaded, is your code
using thread-static variables extensively (including as part of a
library)? The Darwin ABI does not natively support support thread-local
storage, so Apple only supports it through pthread_get_specific() [1,2].
This makes thread-static variables comparatively slow in 2.10.
This is somewhat fixed in the current github master (and presumably will
be also fixed in 2.12). The new code attempts to disassemble
pthread_getspecific() to find out the gs register offset that the OS
uses and then uses that as a basis for generating thread-local code. The
performance difference is pretty dramatic if you use thread-static
variables a lot (caveat: if you want to experiment, from what I can
tell, it so far only properly works for the x86 target; the amd64
target, i.e. 64 bit, for some reason doesn't, so you want to build for a
32-bit host if experimenting with it).
Second, if you're running a benchmark that aggressively has multiple
threads use a single shared lock, that can lead to a form of
"thrashing", independently of the OS used. Basically, if a thread blocks
because of a contended lock, most simple lock implementation suspend the
thread (which involves an expensive kernel trap). If timing is
unfortunate, then you can waste a lot of time having threads suspending
themselves and getting immediately reawakened; the specific overhead and
circumstances where that happens vary by OS, but the effect can be very
unpretty (you can easily make a program 10x slower on most machines by
parallelizing it in a way that the architecture doesn't like). You can
recognize this scenario by using /usr/bin/time or something similar; an
otherwise CPU-bound process will have a disproportionate amount of time
allocated to system rather than user code.
A relatively simple workaround where you have this problem but expect a
critical section to only be short-lived is to repeatedly use a "try
lock" statement (such as Monitor.TryEnter()) before actually using a
lock-or-suspend type of operation. While this can be more expensive (and
potentially problematic if you have more threads than available
processors, or if you have a LOT of processors), in a lot of normal
situations it prevents unnecessary thread suspensions (essentially, it
tries to treat the lock as a spin lock and only falls back to a blocking
implementation if that seems unworkable).
Third, another cause might be that the Boehm GC is causing trouble here;
it (unavoidably) has a central lock and you say you're allocating
millions of objects. While the Boehm GC specifically tries to mitigate
the high contention scenario above (and has thread-local allocation if
enabled that largely avoids it for a lot of cases), there may still be
system-specific differences. Trying to run with --gc=sgen may help to
either identify or exclude this as a source of performance difference.
And, of course, there are a gazillion more causes why there may be a
performance difference, but these are common reasons you may encounter.
Reimer Behrends
[1] As on Linux, Darwin stores thread-local variables relative to the
segment register gs; unlike Linux, Darwin gives you no way to tell at
what offset thread-local data is/can be stored nor does it promise that
it may not totally change its implementation in a later version of the OS.
[2] There are alternative implementations of fast thread-local storage,
but most of them have their own up- and downsides.
More information about the Mono-devel-list
mailing list