<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Mac OS X
Step1:  The gcc command maps back to clang. The "real" GCC is different
Step2:  My "real" GCC command is gcc-5
You can switch the compiler by changing the CC environment variable
Step3:  <div style="margin-top
Step4:  Make some data
Step5:  Timings
Step6:  Some things are tricky
Step7:  Confusing explanation from the Cython docs
<ASSISTANT_TASK:>
Python Code:
!brew ls --versions gcc
Explanation: Mac OS X: clang does not support openmp
You have to use GCC
install from homebrew
End of explanation
!compgen -c | grep ^gcc
Explanation: The gcc command maps back to clang. The "real" GCC is different:
Let's find all executables beginning with "gcc":
End of explanation
import os
os.environ['CC'] = 'gcc-6'
Explanation: My "real" GCC command is gcc-5
You can switch the compiler by changing the CC environment variable:
End of explanation
%%cython -f
# distutils: extra_compile_args = -fopenmp
# distutils: extra_link_args = -fopenmp
# cython: boundscheck = False
from libc.math cimport log
from cython.parallel cimport prange
def f1(double[:] x, double[:] out):
    cdef int i, n = x.shape[0]
    for i in range(n):
        out[i] = log(x[i])
        
def f2(double[:] x, double[:] out):
    cdef int i, n = x.shape[0]
    for i in prange(n, nogil=True):
        out[i] = log(x[i])
Explanation: <div style="margin-top: 100px;"></div>
Compare range() and prange()
End of explanation
data = np.random.rand(10000000)
out = np.zeros_like(data)
Explanation: Make some data
End of explanation
%timeit f1(data, out)
%timeit np.log(data, out=out)
%timeit f2(data, out)
Explanation: Timings
End of explanation
%%cython
# distutils: extra_compile_args = -fopenmp
# distutils: extra_link_args = -fopenmp
# cython: boundscheck = False
from libc.math cimport log
from cython.parallel cimport prange
def f_single(double[:] x):
    cdef int i, n = x.shape[0]
    cdef double result = 0
    for i in range(n):
        if x[i] > 0.5:
            result += log(x[i])
        else:
            result += 1.0
    return result
        
def f_parallel(double[:] x):
    cdef int i, n = x.shape[0]
    cdef double result = 0
    for i in prange(n, nogil=True):
        if x[i] > 0.5:
            result += log(x[i])
        else:
            result += 1.0
    return result
Explanation: Some things are tricky
End of explanation
%%cython
# distutils: extra_compile_args = -fopenmp
# distutils: extra_link_args = -fopenmp
# cython: boundscheck = False
from libc.math cimport log
from cython.parallel cimport prange
def g_single(double[:] x):
    cdef int i, n = x.shape[0]
    cdef double result = 0
    for i in range(n):
        if x[i] > 0.5:
            result += log(x[i])
        else:
            result += 1.0
    return result
        
def g_parallel(double[:] x):
    cdef int i, n = x.shape[0]
    cdef double tmp, result = 0
    for i in prange(n, nogil=True):
        if x[i] > 0.5:
            tmp = log(x[i])
        else:
            tmp = 1.0
        result += tmp
    return result
print(g_single(data[:3]))
print(g_parallel(data[:3]))
%timeit g_single(data)
%timeit g_parallel(data)
Explanation: Confusing explanation from the Cython docs:
If you assign to a variable in a prange block, it becomes lastprivate, meaning that the variable will contain the value from the last iteration. <u>If you use an inplace operator on a variable, it becomes a reduction</u>, meaning that the values from the thread-local copies of the variable will be reduced with the operator and assigned to the original variable after the loop. The index variable is always lastprivate. Variables assigned to in a parallel with block will be private and unusable after the block, as there is no concept of a sequentially last value.
Solution: need a temporary variable
End of explanation
<END_TASK>