tech-userlevel archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
Re: qsort_r
On Sun, Dec 08, 2013 at 10:50:15PM +0000, David Holland wrote:
> > > I have done it by having the original, non-_r functions provide a
> > > thunk for the comparison function, as this is least invasive. If we
> > > think this is too expensive, an alternative is generating a union of
> > > function pointers and making tests at the call sites; another option
> > > is to duplicate the code (hopefully with cpp rather than C&P) but that
> > > seems like a bad plan.
> >
> > I'd prefer to not have another indirect call. The only difference
> > is the definition and expanding a CMP macro differently?
>
> Yes. But I'd rather not duplicate the code...
So, to ground this a bit, the realistic options are:
starting from this (various details elided):
void
sort(void *p, size_t n, size_t sz, int (*cmp)(const void *, const void *))
{
...
r = cmp(a, b);
...
}
(1) thunks [what's in my current patch]
void
sort_r(void *p, size_t n, size_t sz,
int (*cmp)(const void *, const void *, void *), void *data)
{
...
r = cmp(a, b, data);
...
}
struct sort {
int (*cmp)(const void *, const void *);
};
static int
thunk(const void *a, const void *b, void *data)
{
return ((struct sort *)data)->cmp(a, b)
}
void
sort(void *p, size_t n, size_t sz, int (*cmp)(const void *, const void *))
{
struct sort x = { .cmp = cmp };
sort_r(p, n, sz, thunk, &x);
}
pros:
+ already written
+ noninvasive
+ reasonably tidy
+ does not duplicate the output code
cons:
- extra indirect calls
(2) union of function pointers
union sort {
int (*cmp)(const void *, const void *);
int (*cmp_r)(const void *, const void *, void *);
};
static void
sort_internal(void *p, size_t n, size_t sz, bool is_r, union sort cmpu,
void *data)
{
...
r = is_r ? cmpu.cmp(a, b) : cmpu.cmp_r(a, b, data);
...
}
void
sort(void *p, size_t n, size_t sz, int (*cmp)(const void *, const void *))
{
union sort cmpu = { .cmp = cmp };
sort_internal(p, n, sz, true, cmpu, NULL);
}
void
sort_r(void *p, size_t n, size_t sz,
int (*cmp_r)(const void *, const void *, void *), void *data)
{
union sort cmpu = { .cmp_r = cmp };
sort_internal(p, n, sz, true, cmpu, data);
}
pros:
+ not excessively invasive
+ no indirect calls
+ does not duplicate the output code
+ most likely to have unnecessary logic removed by the compiler
cons:
+ a bit ugly
(3) cloning the code with cpp
--- in sort.c ---
#ifdef MAKE_ME_REENTRANT
#define SORT sort_r
#define EXTRA , void *data
#define CMP(a, b) cmp(a, b, data)
#else
#define SORT sort
#define EXTRA
#define CMP(a, b) cmp(a, b)
#endif
void
SORT(void *p, size_t n, size_t sz,
int (*cmp)(const void *, const void * EXTRA) EXTRA)
{
...
r = CMP(a, b);
...
}
--- in sort_r.c ---
#define MAKE_ME_REENTRANT
#include sort.c
pros:
+ noninvasive
+ no runtime overhead
cons:
+ ugly
+ duplicates the output code
Of these I think if we're concerned about the overhead of the thunks
in #1, #2 is the best bet.
While with normal calling conventions sort can end up being identical
object code to sort_r, as various people have noted, I think this kind
of optimization is best left to the compiler. With #2, the compiler
has a fighting chance of being able to do this. (I haven't checked;
I'd be moderately, but not greatly, surprised if any of the compilers
we have do.)
--
David A. Holland
dholland%netbsd.org@localhost
Home |
Main Index |
Thread Index |
Old Index