5
\$\begingroup\$

I am trying to create a function that can either beat numexpr or perform comparably for the vectorized mathematical operation k * exp(ee), where k and ee are 1D arrays of complex numbers. I had tried using OpenMP in Cython directly, but I have resorted to creating a DLL in C++ and then wrapping with Cython. Here are the components (of current interest is the function multi_mlt_exp_c4). I am using Windows 10, 64 bit, Intel processor, and Python 3.x.

C++ DLL, Header:

#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport)
#include <complex>
#include <stdlib.h>

EXTERN_DLL_EXPORT void cexp_scale_c4(std::complex<float> k, std::complex<float> ee,
                                     std::complex<float>*res);
EXTERN_DLL_EXPORT void multi_mlt_exp_c4(std::complex<float>* k, std::complex<float>* ee,
                                        int sz, std::complex<float>* res, int threads);
EXTERN_DLL_EXPORT void multi_mlt_exp_c8(std::complex<double>* k, std::complex<double>* ee,
                                        int sz, std::complex<double>* res, int threads);
EXTERN_DLL_EXPORT void serial_mlt_exp_c4(std::complex<float>* k, std::complex<float>* ee,
                                         int sz, std::complex<float>* res);

C++ DLL, Source

#include "stdafx.h"
#include <stdio.h>
#include <omp.h>
#include "C_Fun_Lib.h"

void cexp_scale_c4(std::complex<float> k, std::complex<float> ee, std::complex<float>*res)
{
    *res = k*exp(ee);
}

void multi_mlt_exp_c4(std::complex<float>* k, std::complex<float>* ee, int sz, std::complex<float>* res, int threads)
{
    #pragma omp parallel num_threads(threads)
    {
        #pragma omp for
        for (int i = 0; i < sz; i++) res[i] = k[i] * exp(ee[i]);
    }
}

void multi_mlt_exp_c8(std::complex<double>* k, std::complex<double>* ee, int sz, std::complex<double>* res, int threads)
{
    #pragma omp parallel num_threads(threads)
    {
        #pragma omp for
        for (int i = 0; i < sz; i++) res[i] = k[i] * exp(ee[i]);
    }
}

void serial_mlt_exp_c4(std::complex<float>* k, std::complex<float>* ee, int sz, std::complex<float>* res)
{
    for (int i = 0; i < sz; i++) res[i] = k[i] * exp(ee[i]);
}

Here are the relevant Python / Cython components:

PXD

cdef extern from "C_Fun_Lib.h":
    void multi_mlt_exp_c4(float complex* k, float complex* ee, int sz,
                          float complex* res, int threads);
    void serial_mlt_exp_c4(float complex* k, float complex* ee, int sz,
                           float complex* res);
    void multi_mlt_exp_c8(double complex* k, double complex* ee, int sz,
                          double complex* res, int threads);

PYX

cimport csample
import numpy as np
cimport numpy as np

# Import some functionality from Python and the C stdlib
from cpython.pycapsule cimport *

from libc.stdlib cimport malloc, free

def mlt_exp_c4(np.ndarray[np.complex64_t, ndim=1] k,
               np.ndarray[np.complex64_t, ndim=1] ee, int sz,
               np.ndarray[np.complex64_t, ndim=1] res, int threads):
    csample.multi_mlt_exp_c4(&k[0], &ee[0], sz, &res[0], threads)

def mlt_exp_c4_serial(np.ndarray[np.complex64_t, ndim=1] k,
               np.ndarray[np.complex64_t, ndim=1] ee, int sz,
               np.ndarray[np.complex64_t, ndim=1] res):
    csample.serial_mlt_exp_c4(&k[0], &ee[0], sz, &res[0])

def mlt_exp_c8(np.ndarray[np.complex128_t, ndim=1] k,
               np.ndarray[np.complex128_t, ndim=1] ee, int sz,
               np.ndarray[np.complex128_t, ndim=1] res, int threads):
    csample.multi_mlt_exp_c8(&k[0], &ee[0], sz, &res[0], threads)

Setup.py

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
import numpy as np

ext_modules = [
    Extension('sample',
              ['sample.pyx'],
              language="c++",
              libraries=['C_Fun_Lib'],
              library_dirs=['.'])
    ]

setup(
    name = 'sample',
    cmdclass = {'build_ext': build_ext},
    ext_modules = ext_modules,
    include_dirs=[np.get_include()]
)

And then finally here is my timing code and results. I am specifically trying to beat Numexpr because, currently, that application up-casts complex64 arrays to complex128 arrays, and I am trying to save memory. Writing my own code gives me control over the memory, but I can't quite match the speed of Numexpr.

import sample
import numpy as np
import numexpr as ne
import time

k = np.ones(int(2.5e8), dtype='complex64')*1.1234 + 1.1234j
ee = k
sz = k.sz
res = np.zeros(int(2.5e8), dtype='complex64')  # Results

tic = time.time()
sample.mlt_exp_c4(k, ee, sz, res, 8)
toc = time.time()

print('Elasped MP: %f' % (toc - tic))

# Reset results
res = np.zeros(int(2.5e8), dtype='complex64')

tic = time.time()
res = ne.evaluate('k*exp(ee)')
toc = time.time()

print('Elasped NE: %f' % (toc - tic))

>>>>>>>
Elasped MP: 2.292317
Elasped NE: 1.184981

Things I have tried

First, out of all the things I've tried, this is my current best. I have experimented to see if using complex128s makes a difference (since I'm on a 64 bit machine). It does not. I have tried doing the complex exponential and multiplication directly and using Euler's formula instead of using the C++ built in complex type and functions... that is slower. I have tried using Cython code directly with OpenMP support... that is slower.

It may very well be that trying to match the highly optimized Numexpr code is a fool's errand. I just wanted you all to review this code to see if there were any obvious speed ups that I am missing.

\$\endgroup\$
5
  • 1
    \$\begingroup\$ how much slower are you? Is it a factor 4 (or 8) or less? And on which architecture? \$\endgroup\$
    – Walter
    Jun 12, 2016 at 11:57
  • \$\begingroup\$ The times are shown on the bottom... It is a factor of 2, alas \$\endgroup\$
    – Trekkie
    Jun 12, 2016 at 11:59
  • \$\begingroup\$ If numexpr uses SIMD instructions to implement exp(), then a factor 4 or 8 (depending on the underlying floating-point precision) is possible. AFAIK, a SIMD implementation of exp() is not publicly available, though. \$\endgroup\$
    – Walter
    Jun 12, 2016 at 11:59
  • \$\begingroup\$ The documentation for numexpr is scarce... But Googling shows that maybe it uses simd? In any event I am not familiar with simd. Sometimes I feel like I am a blind man racing on a cliff :-( acooke.org/cute/numexprFas0 \$\endgroup\$
    – Trekkie
    Jun 12, 2016 at 12:04
  • \$\begingroup\$ Also I am on Windows 10, 64 \$\endgroup\$
    – Trekkie
    Jun 12, 2016 at 12:13

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Browse other questions tagged or ask your own question.