2

Conjugating a complex number appears to be about 30 times faster if the type() of the complex number is complex rather than numpy.complex128, see the minimal example below. However, the absolute value takes about the same time. Taking the real and the imaginary part is only about 3 times faster.

Why is the conjugate slower by that much? When I take a from a large complex-valued array, it seems I should cast it to complex first (the complex conjugation is part of a larger code which has many (> 10^6) iterations).

import numpy as np

np.random.seed(100)

a = (np.random.rand(1) + 1j*np.random.rand(1))[0]
b = complex(a)

%timeit a.conjugate() # 2.95 µs ± 24 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit a.conj()      # 2.86 µs ± 14.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit b.conjugate() # 82.8 ns ± 1.28 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
%timeit abs(a)        # 112 ns ± 1.7 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
%timeit abs(b)        # 99.6 ns ± 0.623 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
%timeit a.real        # 145 ns ± 0.259 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
%timeit b.real        # 54.8 ns ± 0.121 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
%timeit a.imag        # 144 ns ± 0.771 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
%timeit b.imag        # 55.4 ns ± 0.297 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
petezurich
  • 9,280
  • 9
  • 43
  • 57
bproxauf
  • 1,076
  • 12
  • 23

1 Answers1

0

Calling NumPy routines always comes at a fixed cost, which in this case is more expensive than cost of the Python-native routine.

As soon as you start processing more than one number (possibly millions) at once NumPy will be much faster:

import numpy as np

N = 10
a = np.random.rand(N) + 1j*np.random.rand(N)
b = [complex(x) for x in a]

%timeit a.conjugate()              # 481 ns ± 1.39 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
%timeit [x.conjugate() for x in b] # 605 ns ± 6.11 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
Nils Werner
  • 34,832
  • 7
  • 76
  • 98
  • 1
    I understand. However, I am surprised that the ratio is that large for `conjugate()`, but significantly smaller for `abs()`, `a.real` or `a.imag`. As mentioned in my question, I am reading a complex number from a large array and need to conjugate it. However, the large array changes between iterations of my code, so I cannot pre-compute the conjugates at once. Then it appears that casting to complex (about ~190 ns on my machine), followed by conjugation (~80 ns) is faster by a factor of more than 10 compared to direct conjugation (~2.8 mus). – bproxauf Jul 20 '22 at 15:13
  • Maybe you can slice your problem in a different way that you can still process more than one number at once to speed up your algorithm. If you really can't then it seems doing it this way will be the fastest. – Nils Werner Jul 20 '22 at 15:15
  • 1
    I get similar time ratios with `a.real-a.imag*1j` – hpaulj Jul 20 '22 at 15:53
  • @NilsWerner: I don't think I can. The code this part is used in is a nonlinear iterative algorithm, where in each iteration the array changes in an a priori unknown manner, so I need to read out the respective single element in each iteration separately. – bproxauf Jul 20 '22 at 18:32
  • @hpaulj: In my case according to the timings, it seems that would take 2 x 145 = 290 ns, plus the time for the subtraction, so > 300 ns... – bproxauf Jul 20 '22 at 18:34
  • 1
    Most of the time is spent in various layers of function calls, not in actual calculations (flops). Without reading the C code it is hard to reason our way around this. Even timings leave us guessing. `numpy` is meant for array operations. In my tests `conj` of a (600,) shape array is faster than one `np.complex128`. In your test, the `[0]` step that unboxes a array scalar from a (1,) shape array actually slows things down. – hpaulj Jul 20 '22 at 20:21
  • But if I am not mistaken, the unboxing is already done (and the result saved into `a`) so that does not influence the `conjugate()`, does it? – bproxauf Jul 20 '22 at 22:12