Search code examples
ccomplex-numbersgsl

fastest way to call constant complex numbers in C


I'm using GSL in C for handling complex numbers.

I have to use complex numbers like +-1, 0, +-i a lot of times (across different functions), something like 10^9 I think (even more maybe, don't know yet), so I need a very fast way to call them.

in gsl_complex_math.h they are defined like this:

#define GSL_COMPLEX_ONE (gsl_complex_rect(1.0,0.0))

where

gsl_complex_rect (double x, double y)
{                               /* return z = x + i y */
  gsl_complex z;
  GSL_SET_COMPLEX (&z, x, y);
  return z;
}

and

#define GSL_SET_COMPLEX(zp,x,y) do {(zp)->dat[0]=(x); (zp)->dat[1]=(y);} while(0)

That looks like an awful lot of code and temporary variables declaration for my purposes, but I have exactly zero experience in evaluating code efficiency.

What if I declare a global variable in a header like global.h like this:

#if defined MAIN_PROGRAM
    #define EXTERN
#else
    #define EXTERN extern
#endif
EXTERN const gsl_complex C_U = {.dat[0] = 1., .dat[1] = 0.}

1) Should I expect an increase in performance? 2) Is the code sufficiently clean? Any traps I'm getting into? 3) Is there a better way?


Solution

    1. Premature optimisation is a BAD THING TO DO. This minor change is easy enough to make after you have a working implementation which you can benchmark (with compiler optimisations enabled!)

    2. a) Using global constants for common values is (in moderation) a clean way to code. However, if the library itself already provides such constants (or in this case, constant-looking functions), it's generally cleaner to use those (new programmers entering the project will immediately see what's going on, without needing to follow your custom definitions)

      b) Potential gotchas of global constants are:

      • Is the variable copied or referenced? (each could be faster in their own situations, usually depending on the size of the struct vs. the size of a pointer & cost of dereferencing)
      • Is the value truly const? (a badly behaved function might cast away the const-ness and try to change it, which can lead to some extended debugging sessions)
      • You may end up with worse locality of reference (values being used by a function will be further apart in memory, causing fewer cache-hits, potentially reducing performance)
    3. For objects this small, with such a low overhead of construction, there's not really a better way than the two options you're already aware of. For larger objects which take more effort to construct, a factory pattern with caching may become appropriate, but not here.


    To elaborate a little on (1):

    The code the library is using will be optimised more than you'd think;

    • do...while is a common pattern in macro definitions, and will be removed entirely by any decent compiler. It's simply a way to ensure multiple commands will still play nicely when used inside a brace-less if, among other places
    • The function may have copy elision applied to its returned value (i.e. no temporaries, and no copying), or even be fully inlined (no temporaries, no copying and no function call). Both are permitted thanks to C's as-if rules

    That means the total cost after optimisation would be memory space on the stack and 2 assignments (and with a good compiler I wouldn't be surprised if the 2 assignments could be optimised to a single assignment containing both values). With lower-levels of optimisation, it may also involve a function call.

    So the final comparison looks like this:

    • Build in-place vs. copied global constant: When fully-optimised, these are the same (tested and confirmed with Clang 3.7).
    • Build in-place vs. pointer to global constant: Depending on size of pointers in the environment, copying the pointer may be faster than creating a new object, but due to locality of reference concerns, it is likely to perform worse