Difference between complex.real and creal(complex) in Cython

Viewed 778

To separate real part from complex parts in cython i have been generally using complex.real and complex.imag for the job. This does however generate code that is colored slight "python red" in the html-output, and i guess that I should be using creal(complex) and cimag(complex) instead.

Consider the example bellow:

cdef double complex myfun():
    cdef double complex c1,c2,c3
    c1=1.0 + 1.2j
    c2=2.2 + 13.4j
    c3=c2.real + c1*c2.imag
    c3=creal(c2) + c1*c2.imag
    c3=creal(c2) + c1*cimag(c2)
    return c2

The the assigments to c3 give:

__pyx_v_c3 = __Pyx_c_sum(__pyx_t_double_complex_from_parts(__Pyx_CREAL(__pyx_v_c2), 0), __Pyx_c_prod(__pyx_v_c1, __pyx_t_double_complex_from_parts(__Pyx_CIMAG(__pyx_v_c2), 0)));

__pyx_v_c3 = __Pyx_c_sum(__pyx_t_double_complex_from_parts(creal(__pyx_v_c2), 0), __Pyx_c_prod(__pyx_v_c1, __pyx_t_double_complex_from_parts(__Pyx_CIMAG(__pyx_v_c2), 0)));

__pyx_v_c3 = __Pyx_c_sum(__pyx_t_double_complex_from_parts(creal(__pyx_v_c2), 0), __Pyx_c_prod(__pyx_v_c1, __pyx_t_double_complex_from_parts(cimag(__pyx_v_c2), 0)));

where the first line to use the (python colored) construction __Pyx_CREAL and __Pyx_CIMAG.

Whys is this, and does it effect performance "significantly"?

2 Answers

Surely the default C library (complex.h) would work for you.

However, it seems this library wouldn't give you any significant improvement when compared to the c.real c.imag approach. By putting the code within a with nogil: block, you can check that your code already makes no call to Python APIs:

cdef double complex c1, c2, c3
with nogil:
    c1 = 1.0 + 1.2j
    c2 = 2.2 + 13.4j
    c3 = c2.real + c1*c2.imag

I use Windows 7 and Python 2.7, which doesn't have complex.h available in the builtin C library for Visual Studio Compilers 9.0 (compatible with Python 2.7). Because of that I created an equivalet pure C function to check any possible gains compared to c.real and c.imag:

cdef double mycreal(double complex dc):
    cdef double complex* dcptr = &dc
    return (<double *>dcptr)[0]

cdef double mycimag(double complex dc):
    cdef double complex* dcptr = &dc
    return (<double *>dcptr)[1]

After running the following two testing functions:

def myfun1(double complex c1, double complex c2):
    return c2.real + c1*c2.imag

def myfun2(double complex c1, double complex c2):
    return mycreal(c2) + c1*mycimag(c2)

Got the timings:

In [3]: timeit myfun1(c1, c2)
The slowest run took 17.50 times longer than the fastest. This could mean that a
n intermediate result is being cached.
10000000 loops, best of 3: 86.3 ns per loop

In [4]: timeit myfun2(c1, c2)
The slowest run took 17.24 times longer than the fastest. This could mean that a
n intermediate result is being cached.
10000000 loops, best of 3: 87.6 ns per loop

Confirming that c.real and c.imag is already fast enough.

Actually you should not expect to see any difference: __Pyx_CREAL(c) and __Pyx_CIMAG(c) can be looked up here, and there is no rocket science/black magic involved:

#if CYTHON_CCOMPLEX
  #ifdef __cplusplus
    #define __Pyx_CREAL(z) ((z).real())
    #define __Pyx_CIMAG(z) ((z).imag())
  #else
    #define __Pyx_CREAL(z) (__real__(z))
    #define __Pyx_CIMAG(z) (__imag__(z))
  #endif
#else
    #define __Pyx_CREAL(z) ((z).real)
    #define __Pyx_CIMAG(z) ((z).imag)
#endif

It basically says, these are defines and lead to a call without overhead of:

  1. std::complex<>.real() and std::complex<>.imag()if it is c++.
  2. GNU extensions __real__ and __imag__, if it is gcc or intel compiler (intel added support some time ago).

You cannot beat it, e.g. in glibc, creal uses __real__. What is left, is Microsoft compiler (CYTHON_CCOMPLEX is not defined), for that an own/special cython-class is used:

#if CYTHON_CCOMPLEX
  ....
#else
    static CYTHON_INLINE {{type}} {{type_name}}_from_parts({{real_type}} x, {{real_type}} y) {
      {{type}} z;
      z.real = x;
      z.imag = y;
      return z;
    }
#endif

Normally, one should not write his/her own complex number implementation, but there is not so much you can do wrong, if only access to the real and imaginary parts is considered. I would not vouch for other functionality, but would not expect it to be very much slower than the usual windows implementation (but would like to see the results if you have some).

As conclusion: There is no difference for gcc/intel compiler and I would not fret too long about differences for others.

Related