NumPy:max()とmin()を同時に実行する関数


109

numpy.amax()は配列内の最大値を検索し、numpy.amin()は最小値に対して同じことを行います。maxとminの両方を検索する場合は、両方の関数を呼び出す必要があります。これには、(非常に大きい)配列を2回渡す必要があり、遅いようです。

numpy APIに、データを1回パスするだけで最大と最小の両方を見つける関数はありますか?


1
どれくらい大きいですか?時間があれば、Fortranの実装をamaxandと比較するいくつかのテストを実行しますamin
mgilson

1
「とても大きい」は主観的だと認めます。私の場合、数GBのアレイについて話しています。
スチュアートバーグ

それはかなり大きいです。fortranで計算する例をコード化しました(fortranを知らなくても、コードを理解するのは簡単です)。これは、Fortranから実行する場合とnumpy経由で実行する場合で本当に違いがあります。(おそらく、Cから同じパフォーマンスを得ることができるはずです...)わからない-私の関数がそれらの関数よりもはるかに優れている理由についてコメントするには、numpy devが必要だと思います...
mgilson

もちろん、これは斬新なアイデアではありません。たとえば、boost minmaxライブラリ(C ++)は、探しているアルゴリズムの実装を提供します。
スチュアートバーグ

3
質問に対する答えは実際にはありませんが、おそらくこのスレッドの人々に関心があります。minmax問題のライブラリーへの追加についてNumPyに尋ねました(github.com/numpy/numpy/issues/9836)。
jakirkham 2017

回答:


49

numpy APIに、データを1回パスするだけで最大と最小の両方を見つける関数はありますか?

いいえ。この記事の執筆時点では、そのような機能はありません。(そして、そうです、そのような関数があった場合、そのパフォーマンスは、大きな配列を連続して呼び出すよりもはるかに優れています。)numpy.amin()numpy.amax()


31

配列を2回渡すことは問題ではないと思います。 次の疑似コードを考えてみます。

minval = array[0]
maxval = array[0]
for i in array:
    if i < minval:
       minval = i
    if i > maxval:
       maxval = i

ここにはループが1つしかありませんが、チェックは2つあります。(2つのループにそれぞれ1つのチェックがある代わりに)。実際に保存する唯一のものは、1ループのオーバーヘッドです。あなたが言うように配列が本当に大きい場合、そのオーバーヘッドは実際のループの作業負荷と比較して小さいです。(これはすべてCで実装されているため、ループは多かれ少なかれフリーです)。


編集私に賛成して私を信じていたあなたの4人に申し訳ありません。あなたは間違いなくこれを最適化できます。

以下は、PythonモジュールにコンパイルできるFortranコードですf2py(おそらくCythonグルがやって来て、これを最適化されたCバージョンと比較できます...):

subroutine minmax1(a,n,amin,amax)
  implicit none
  !f2py intent(hidden) :: n
  !f2py intent(out) :: amin,amax
  !f2py intent(in) :: a
  integer n
  real a(n),amin,amax
  integer i

  amin = a(1)
  amax = a(1)
  do i=2, n
     if(a(i) > amax)then
        amax = a(i)
     elseif(a(i) < amin) then
        amin = a(i)
     endif
  enddo
end subroutine minmax1

subroutine minmax2(a,n,amin,amax)
  implicit none
  !f2py intent(hidden) :: n
  !f2py intent(out) :: amin,amax
  !f2py intent(in) :: a
  integer n
  real a(n),amin,amax
  amin = minval(a)
  amax = maxval(a)
end subroutine minmax2

次の方法でコンパイルします。

f2py -m untitled -c fortran_code.f90

そして今、私たちはそれをテストできる場所にいます:

import timeit

size = 100000
repeat = 10000

print timeit.timeit(
    'np.min(a); np.max(a)',
    setup='import numpy as np; a = np.arange(%d, dtype=np.float32)' % size,
    number=repeat), " # numpy min/max"

print timeit.timeit(
    'untitled.minmax1(a)',
    setup='import numpy as np; import untitled; a = np.arange(%d, dtype=np.float32)' % size,
    number=repeat), '# minmax1'

print timeit.timeit(
    'untitled.minmax2(a)',
    setup='import numpy as np; import untitled; a = np.arange(%d, dtype=np.float32)' % size,
    number=repeat), '# minmax2'

結果は私にとって少し驚異的です:

8.61869883537 # numpy min/max
1.60417699814 # minmax1
2.30169081688 # minmax2

私はそれを完全に理解していません。だけで比較するnp.minminmax1minmax2まだ負けた戦いなので、それは単なるメモリの問題ではありません...

-サイズを1倍に増やし、10**a繰り返しを1倍に減らして10**a(問題のサイズを一定に保つ)と、パフォーマンスは変化しますが、一見一貫した方法ではありません。 python。minFortranでの単純な実装を比較しても、numpyの係数は約2倍です...


21
シングルパスの利点は、メモリ効率です。特に、配列がスワップアウトするのに十分な大きさである場合、これは巨大になる可能性があります。
Dougal 2012

4
これらの種類の配列では、通常メモリ速度が制限要因となるため、それはかなり真実ではありません。半分ほど高速になる可能性があります...
seberg

3
常に2つのチェックが必要なわけではありません。場合はi < minvaltrueで、その後、i > maxvalあなただけの第二の時に平均的に反復あたり1.5のチェックを行う必要があるので、常に偽でif置き換えられますelif
Fred Foo

2
小さな注意:Cythonが最も最適化されたPython呼び出し可能なCモジュールを取得する方法であるとは思えません。Cythonの目標は、型で注釈が付けられた一種のPythonになることです。これは、Cに機械翻訳さf2pyれますが、Pythonから呼び出せるように、手動でコーディングしたFortranをラップするだけです。「より公正な」テストはおそらくCを手動でコーディングし、それをf2py(!)を使用してPython用にラップすることです。C ++を許可している場合、Shed Skinはコーディングの容易さとパフォーマンスのバランスをとるためのスイートスポットになる可能性があります。
ジョンY

4
numpyの時点で、1.8 minおよびmaxはamd64プラットフォームでベクトル化されていますが、私のcore2duoでnumpyはこのFortranコードと同じように動作します。ただし、配列がより大きなCPUキャッシュのサイズを超える場合は、1回のパスが有利です。
jtaylor 14年

23

有用であれば、numpy.ptpと呼ばれる(max-min)を見つけるための関数があります。

>>> import numpy
>>> x = numpy.array([1,2,3,4,5,6])
>>> x.ptp()
5

しかし、1つのトラバーサルで最小値と最大値の両方を見つける方法はないと思います。

編集: ptpは内部でminとmaxを呼び出すだけです


2
おそらくptpの実装方法は最大値と最小値を追跡する必要があるので、それは厄介です!
アンディヘイデン

1
または、最大と最小を呼び出すだけかもしれませんが、よく
わかり

3
@haydenは、ptpがmaxとminを呼び出すだけであることが判明
jterrace 2012

1
これがマスクされた配列コードでした。メインのndarrayコードはCですが、Cコードも配列を2回反復することがわかります:github.com/numpy/numpy/blob/…
ケンアーノルド

20

LLVMを使用するNumPy対応の動的PythonコンパイラであるNumbaを使用できます。結果の実装は非常にシンプルで明確です。

import numpy
import numba


@numba.jit
def minmax(x):
    maximum = x[0]
    minimum = x[0]
    for i in x[1:]:
        if i > maximum:
            maximum = i
        elif i < minimum:
            minimum = i
    return (minimum, maximum)


numpy.random.seed(1)
x = numpy.random.rand(1000000)
print(minmax(x) == (x.min(), x.max()))

また、Numpyのmin() & max()実装よりも高速である必要があります。そして、C / Fortranのコードを1行も書く必要がありません。

アーキテクチャ、データ、パッケージバージョンに常に依存しているため、独自のパフォーマンステストを行ってください...


2
>また、Numpyのmin()&max()実装よりも高速である必要があります。これは正しくないと思います。numpyはネイティブpythonではありません-Cです。```x = numpy.random.rand(10000000)t = time()for i for range(1000):minmax(x)print( 'numba'、time() -t)t = time()for i for range(1000):x.min()x.max()print( 'numpy'、time()-t) `` `結果:( 'numba'、10.299750089645386 )( 'numpy'、9.898081064224243)
Authman Apatira '13年

1
@AuthmanApatira:ええ、ベンチマークは常にそのようなものです。それが、「アーキテクチャ(データ)に常に依存しているため、「より高速で」、「独自のパフォーマンステストを行う必要がある」と私が言った理由です。私の場合、3台のコンピューターで試して同じ結果が得られました(NumbaはNumpyよりも高速でした)が、コンピューターの結果は異なる場合があります... numbaベンチマークの前に関数を1回実行して、JITコンパイルされていることを確認しましたか? ?。また、を使用する場合ipython、簡単にするために、%timeit whatever_code()時間実行の測定に使用することをお勧めします。
ペケ

3
@AuthmanApatira:どんな場合でも、私がこの回答で示したのは、Pythonコード(この場合はNumbaでJITコンパイル)が最速のCコンパイルライブラリと同じくらい高速になる場合があるということです(少なくとも同じ順序について話している)。純粋なPythonコード以外に何も書いていないことを考えると、これは印象的です。同意しませんか?^^
ペケ2017年

私も同意します=)また、jupyterに関する以前のコメントのヒントと、タイミングコードの外で関数を1回コンパイルすることに感謝します。
Authman Apatira 2017年

1
実際に問題になることはありませんが、これに遭遇しただけelifで、最小値を最大値より大きくすることができます。たとえば、長さが1の配列の場合、最大値はその値になり、最小値は+無限大になります。1回限りの場合は大した問題ではありませんが、生産獣の腹に深く入り込むための良いコードではありません。
マイクウィリアムソン

12

一般に、一度に2つの要素を処理し、小さい方を一時的な最小値と比較し、大きい方を一時的な最大値と比較するだけで、minmaxアルゴリズムの比較量を減らすことができます。平均して必要なのは、単純なアプローチよりも比較の3/4だけです。

これは、cやfortran(またはその他の低レベル言語)で実装でき、パフォーマンスの面ではほとんど無敵です。私は使っています 原理を説明し、非常に高速でdtypeに依存しない実装を取得します。

import numba as nb
import numpy as np

@nb.njit
def minmax(array):
    # Ravel the array and return early if it's empty
    array = array.ravel()
    length = array.size
    if not length:
        return

    # We want to process two elements at once so we need
    # an even sized array, but we preprocess the first and
    # start with the second element, so we want it "odd"
    odd = length % 2
    if not odd:
        length -= 1

    # Initialize min and max with the first item
    minimum = maximum = array[0]

    i = 1
    while i < length:
        # Get the next two items and swap them if necessary
        x = array[i]
        y = array[i+1]
        if x > y:
            x, y = y, x
        # Compare the min with the smaller one and the max
        # with the bigger one
        minimum = min(x, minimum)
        maximum = max(y, maximum)
        i += 2

    # If we had an even sized array we need to compare the
    # one remaining item too.
    if not odd:
        x = array[length]
        minimum = min(x, minimum)
        maximum = max(x, maximum)

    return minimum, maximum

それはdefinetly速いという単純なアプローチよりもだPequeのが提示しました:

arr = np.random.random(3000000)
assert minmax(arr) == minmax_peque(arr)  # warmup and making sure they are identical 
%timeit minmax(arr)            # 100 loops, best of 3: 2.1 ms per loop
%timeit minmax_peque(arr)      # 100 loops, best of 3: 2.75 ms per loop

予想通り、新しいminmax実装は、単純な実装にかかった時間の約3/4しかかかりません(2.1 / 2.75 = 0.7636363636363637


1
私のマシンでは、あなたのソリューションはペケのソリューションよりも速くありません。Numba 0.33。
John Zwinck、2017年

@johnzwinck私の答えのベンチマークは別のものですか?もしそうならそれを共有できますか?しかし、それは可能です。私は新しいバージョンでもいくつかのリグレッションに気づきました。
MSeifert 2017年

私はあなたのベンチマークを実行しました。ソリューションと@Pequeのタイミングはほぼ同じでした(〜2.8ミリ秒)。
John Zwinck、2017年

@JohnZwinck奇妙なことですが、もう一度テストしましたが、私のコンピューターでは明らかに高速です。多分それはハードウェアに依存するnumbaとLLVMと関係があります。
MSeifert 2017年

私は今別のマシン(強力なワークステーション)を試してみましたが、あなたのマシンでは2.4ミリ秒、ペケマシンでは2.6ミリ秒でした。だから、小さな勝利。
John Zwinck 2017年

11

以下のアプローチを前提として、予想できる数についていくつかのアイデアを得るために:

import numpy as np


def extrema_np(arr):
    return np.max(arr), np.min(arr)
import numba as nb


@nb.jit(nopython=True)
def extrema_loop_nb(arr):
    n = arr.size
    max_val = min_val = arr[0]
    for i in range(1, n):
        item = arr[i]
        if item > max_val:
            max_val = item
        elif item < min_val:
            min_val = item
    return max_val, min_val
import numba as nb


@nb.jit(nopython=True)
def extrema_while_nb(arr):
    n = arr.size
    odd = n % 2
    if not odd:
        n -= 1
    max_val = min_val = arr[0]
    i = 1
    while i < n:
        x = arr[i]
        y = arr[i + 1]
        if x > y:
            x, y = y, x
        min_val = min(x, min_val)
        max_val = max(y, max_val)
        i += 2
    if not odd:
        x = arr[n]
        min_val = min(x, min_val)
        max_val = max(x, max_val)
    return max_val, min_val
%%cython -c-O3 -c-march=native -a
#cython: language_level=3, boundscheck=False, wraparound=False, initializedcheck=False, cdivision=True, infer_types=True


import numpy as np


cdef void _extrema_loop_cy(
        long[:] arr,
        size_t n,
        long[:] result):
    cdef size_t i
    cdef long item, max_val, min_val
    max_val = arr[0]
    min_val = arr[0]
    for i in range(1, n):
        item = arr[i]
        if item > max_val:
            max_val = item
        elif item < min_val:
            min_val = item
    result[0] = max_val
    result[1] = min_val


def extrema_loop_cy(arr):
    result = np.zeros(2, dtype=arr.dtype)
    _extrema_loop_cy(arr, arr.size, result)
    return result[0], result[1]
%%cython -c-O3 -c-march=native -a
#cython: language_level=3, boundscheck=False, wraparound=False, initializedcheck=False, cdivision=True, infer_types=True


import numpy as np


cdef void _extrema_while_cy(
        long[:] arr,
        size_t n,
        long[:] result):
    cdef size_t i, odd
    cdef long x, y, max_val, min_val
    max_val = arr[0]
    min_val = arr[0]
    odd = n % 2
    if not odd:
        n -= 1
    max_val = min_val = arr[0]
    i = 1
    while i < n:
        x = arr[i]
        y = arr[i + 1]
        if x > y:
            x, y = y, x
        min_val = min(x, min_val)
        max_val = max(y, max_val)
        i += 2
    if not odd:
        x = arr[n]
        min_val = min(x, min_val)
        max_val = max(x, max_val)
    result[0] = max_val
    result[1] = min_val


def extrema_while_cy(arr):
    result = np.zeros(2, dtype=arr.dtype)
    _extrema_while_cy(arr, arr.size, result)
    return result[0], result[1]

extrema_loop_*()アプローチはここで提案されているものに似ていますがextrema_while_*()アプローチはここからのコードに基づいています)

以下のタイミング:

bm

ことを示しているextrema_while_*()と、最速のあるextrema_while_nb()最速であること。いずれの場合でも、extrema_loop_nb()およびextrema_loop_cy()ソリューションは、NumPyのみのアプローチ(np.max()およびをnp.min()個別に使用)よりも優れています。

最後に、これらのどれもnp.min()/ ほど柔軟ではないことに注意してくださいnp.max()(n-dimサポート、axisパラメーターなどに関して)。

(完全なコードはここにあります


2
@njit(fastmath = True)を使用すると、さらに10%の速度が得られるようですextrema_while_nb
argenisleon

10

numpy.percentileについては誰も言及していなかったので、私はそう思いました。[0, 100]パーセンタイルを要求すると、最小(0パーセンタイル)と最大(100パーセンタイル)の2つの要素の配列が表示されます。

ただし、OPの目的を満たしていません。最小値と最大値を個別に指定するよりも高速ではありません。これはおそらく、極端でないパーセンタイルを可能にするいくつかの機構によるものです(より長い時間かかる、より難しい問題)。

In [1]: import numpy

In [2]: a = numpy.random.normal(0, 1, 1000000)

In [3]: %%timeit
   ...: lo, hi = numpy.amin(a), numpy.amax(a)
   ...: 
100 loops, best of 3: 4.08 ms per loop

In [4]: %%timeit
   ...: lo, hi = numpy.percentile(a, [0, 100])
   ...: 
100 loops, best of 3: 17.2 ms per loop

In [5]: numpy.__version__
Out[5]: '1.14.4'

Numpyの将来のバージョンでは、特別な場合にのみ[0, 100]要求された場合に通常のパーセンタイル計算をスキップする可能性があります。インターフェースに何も追加せずに、1つの呼び出しでNumpyに最小と最大を要求する方法があります(承認された回答で述べられていることとは異なり)が、ライブラリの標準実装は、このケースを利用してそれを作成しません。価値がある。


9

これは古いスレッドですが、とにかく、これをもう一度見る人がいたら...

最小値と最大値を同時に検索する場合、比較の数を減らすことができます。フロートの場合、比較していると思いますが(これはそうだと思います)、計算は複雑ではありませんが、時間を節約できます。

(Pythonコード)の代わりに:

_max = ar[0]
_min=  ar[0]
for ii in xrange(len(ar)):
    if _max > ar[ii]: _max = ar[ii]
    if _min < ar[ii]: _min = ar[ii]

最初に配列内の2つの隣接する値を比較してから、小さい方の値を現在の最小値と比較し、大きい方の値を現在の最大値と比較するだけです。

## for an even-sized array
_max = ar[0]
_min = ar[0]
for ii in xrange(0, len(ar), 2)):  ## iterate over every other value in the array
    f1 = ar[ii]
    f2 = ar[ii+1]
    if (f1 < f2):
        if f1 < _min: _min = f1
        if f2 > _max: _max = f2
    else:
        if f2 < _min: _min = f2
        if f1 > _max: _max = f1

ここのコードはPythonで書かれており、明らかに速度を上げるにはC、Fortran、またはCythonを使用しますが、この方法では、反復ごとに3回の比較を行い、len(ar)/ 2回の反復で3/2 * len(ar)の比較を行います。それとは対照的に、「明白な方法」で比較を行うと、反復ごとに2つの比較が行われ、2 * len(ar)の比較になります。比較時間を25%節約できます。

多分誰かがこれが役立つと思うでしょう。


6
これをベンチマークしましたか?最新のx86ハードウェアでは、最初のバリアントで使用されるminとmaxの機械語命令があります。これらにより、コードがおそらくハードウェアにマッピングされない制御依存関係を配置する間、分岐の必要がなくなります。
jtaylor 2014年

私は実際にはしていません。機会があればやります。純粋なpythonコードが賢明なコンパイル済み実装に手を抜くことはかなり明らかだと思いますが、Cythonでスピードアップが見られるかどうか疑問に思います...
Bennet

13
で使用される内部でnumpyにminmax実装がありますここをnp.bincount参照してください。単純なアプローチよりも最大2倍遅いことが判明したため、指摘したトリックは使用しません。PRから両方の方法のいくつかの包括的なベンチマークへのリンクがあります。
ハイメ

5

一見すると、トリックを行うように見えますnumpy.histogram

count, (amin, amax) = numpy.histogram(a, bins=1)

あなたが見れば...しかし、ソース、その機能のために、それは単に呼び出すa.min()a.max()独立して、したがって懸念は、この問題に対処し、パフォーマンスを避けるために失敗しました。:-(

同様に、scipy.ndimage.measurements.extrema可能性のように見えますが、それは、あまりにも、簡単に呼び出し、a.min()およびa.max()独立。


3
np.histogram戻り(amin, amax)値はビンの最小値と最大値に対するものであるため、これは常に機能するとは限りません。たとえば、のa = np.zeros(10)場合、np.histogram(a, bins=1)が返されます(array([10]), array([-0.5, 0.5]))。その場合、ユーザーは(amin, amax)=(0、0)を探します。
2017年

3

とにかく私にとっては努力の価値があったので、興味がある人のために、ここで最も難しくてエレガントでない解決策を提案します。私の解決策は、C ++のワンパスアルゴリズムでマルチスレッドのmin-maxを実装し、これを使用してPython拡張モジュールを作成することです。この作業には、PythonおよびNumPy C / C ++ APIの使用方法を学習するための少しのオーバーヘッドが必要です。ここでは、コードを示し、このパスを使用したい人のためにいくつかの簡単な説明と参照を示します。

マルチスレッドの最小/最大

ここにはあまり興味深いものはありません。配列はサイズのチャンクに分割されますlength / workers。最小値/最大値は、の各チャンクについて計算futureされ、グローバル最小値/最大値がスキャンされます。

    // mt_np.cc
    //
    // multi-threaded min/max algorithm

    #include <algorithm>
    #include <future>
    #include <vector>

    namespace mt_np {

    /*
     * Get {min,max} in interval [begin,end)
     */
    template <typename T> std::pair<T, T> min_max(T *begin, T *end) {
      T min{*begin};
      T max{*begin};
      while (++begin < end) {
        if (*begin < min) {
          min = *begin;
          continue;
        } else if (*begin > max) {
          max = *begin;
        }
      }
      return {min, max};
    }

    /*
     * get {min,max} in interval [begin,end) using #workers for concurrency
     */
    template <typename T>
    std::pair<T, T> min_max_mt(T *begin, T *end, int workers) {
      const long int chunk_size = std::max((end - begin) / workers, 1l);
      std::vector<std::future<std::pair<T, T>>> min_maxes;
      // fire up the workers
      while (begin < end) {
        T *next = std::min(end, begin + chunk_size);
        min_maxes.push_back(std::async(min_max<T>, begin, next));
        begin = next;
      }
      // retrieve the results
      auto min_max_it = min_maxes.begin();
      auto v{min_max_it->get()};
      T min{v.first};
      T max{v.second};
      while (++min_max_it != min_maxes.end()) {
        v = min_max_it->get();
        min = std::min(min, v.first);
        max = std::max(max, v.second);
      }
      return {min, max};
    }
    }; // namespace mt_np

Python拡張モジュール

ここからが醜くなります... PythonでC ++コードを使用する1つの方法は、拡張モジュールを実装することです。このモジュールは、distutils.core標準モジュールを使用して構築およびインストールできます。これに伴うものの完全な説明は、Pythonのドキュメントhttps://docs.python.org/3/extending/extending.htmlでカバーされています注:https : //docs.python.org/3/extending/index.html#extending-indexを引用して、同様の結果を得る他の方法は確かにあります

このガイドでは、このバージョンのCPythonの一部として提供される拡張機能を作成するための基本的なツールのみを扱います。Cython、cffi、SWIG、Numbaなどのサードパーティツールは、Python用のCおよびC ++拡張を作成するためのよりシンプルで洗練されたアプローチを提供します。

基本的に、このルートはおそらく実用的というよりも学術的です。それが言われて、私が次にしたことは、チュートリアルにかなり固執して、モジュールファイルを作成することでした。これは本質的に、distutilsがコードの処理方法を知り、それからPythonモジュールを作成するための定型文です。これを行う前に、システムパッケージを汚染しないようにPython 仮想環境を作成することをお勧めします(https://docs.python.org/3/library/venv.html#module-venvを参照)。

モジュールファイルは次のとおりです。

// mt_np_forpy.cc
//
// C++ module implementation for multi-threaded min/max for np

#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION

#include <python3.6/numpy/arrayobject.h>

#include "mt_np.h"

#include <cstdint>
#include <iostream>

using namespace std;

/*
 * check:
 *  shape
 *  stride
 *  data_type
 *  byteorder
 *  alignment
 */
static bool check_array(PyArrayObject *arr) {
  if (PyArray_NDIM(arr) != 1) {
    PyErr_SetString(PyExc_RuntimeError, "Wrong shape, require (1,n)");
    return false;
  }
  if (PyArray_STRIDES(arr)[0] != 8) {
    PyErr_SetString(PyExc_RuntimeError, "Expected stride of 8");
    return false;
  }
  PyArray_Descr *descr = PyArray_DESCR(arr);
  if (descr->type != NPY_LONGLTR && descr->type != NPY_DOUBLELTR) {
    PyErr_SetString(PyExc_RuntimeError, "Wrong type, require l or d");
    return false;
  }
  if (descr->byteorder != '=') {
    PyErr_SetString(PyExc_RuntimeError, "Expected native byteorder");
    return false;
  }
  if (descr->alignment != 8) {
    cerr << "alignment: " << descr->alignment << endl;
    PyErr_SetString(PyExc_RuntimeError, "Require proper alignement");
    return false;
  }
  return true;
}

template <typename T>
static PyObject *mt_np_minmax_dispatch(PyArrayObject *arr) {
  npy_intp size = PyArray_SHAPE(arr)[0];
  T *begin = (T *)PyArray_DATA(arr);
  auto minmax =
      mt_np::min_max_mt(begin, begin + size, thread::hardware_concurrency());
  return Py_BuildValue("(L,L)", minmax.first, minmax.second);
}

static PyObject *mt_np_minmax(PyObject *self, PyObject *args) {
  PyArrayObject *arr;
  if (!PyArg_ParseTuple(args, "O", &arr))
    return NULL;
  if (!check_array(arr))
    return NULL;
  switch (PyArray_DESCR(arr)->type) {
  case NPY_LONGLTR: {
    return mt_np_minmax_dispatch<int64_t>(arr);
  } break;
  case NPY_DOUBLELTR: {
    return mt_np_minmax_dispatch<double>(arr);
  } break;
  default: {
    PyErr_SetString(PyExc_RuntimeError, "Unknown error");
    return NULL;
  }
  }
}

static PyObject *get_concurrency(PyObject *self, PyObject *args) {
  return Py_BuildValue("I", thread::hardware_concurrency());
}

static PyMethodDef mt_np_Methods[] = {
    {"mt_np_minmax", mt_np_minmax, METH_VARARGS, "multi-threaded np min/max"},
    {"get_concurrency", get_concurrency, METH_VARARGS,
     "retrieve thread::hardware_concurrency()"},
    {NULL, NULL, 0, NULL} /* sentinel */
};

static struct PyModuleDef mt_np_module = {PyModuleDef_HEAD_INIT, "mt_np", NULL,
                                          -1, mt_np_Methods};

PyMODINIT_FUNC PyInit_mt_np() { return PyModule_Create(&mt_np_module); }

このファイルでは、PythonとNumPy APIの重要な使用方法があります。詳細については、https//docs.python.org/3/c-api/arg.html#c.PyArg_ParseTupleおよびNumPyを参照してください。:https : //docs.scipy.org/doc/numpy/reference/c-api.array.html

モジュールのインストール

次に行うことは、distutilsを利用してモジュールをインストールすることです。これにはセットアップファイルが必要です。

# setup.py

from distutils.core import setup,Extension

module = Extension('mt_np', sources = ['mt_np_module.cc'])

setup (name = 'mt_np', 
       version = '1.0', 
       description = 'multi-threaded min/max for np arrays',
       ext_modules = [module])

最終的にモジュールをインストールするにpython3 setup.py installは、仮想環境から実行します。

モジュールのテスト

最後に、C ++実装が実際にNumPyの単純な使用よりも優れているかどうかをテストして確認できます。そのために、簡単なテストスクリプトを次に示します。

# timing.py
# compare numpy min/max vs multi-threaded min/max

import numpy as np
import mt_np
import timeit

def normal_min_max(X):
  return (np.min(X),np.max(X))

print(mt_np.get_concurrency())

for ssize in np.logspace(3,8,6):
  size = int(ssize)
  print('********************')
  print('sample size:', size)
  print('********************')
  samples = np.random.normal(0,50,(2,size))
  for sample in samples:
    print('np:', timeit.timeit('normal_min_max(sample)',
                 globals=globals(),number=10))
    print('mt:', timeit.timeit('mt_np.mt_np_minmax(sample)',
                 globals=globals(),number=10))

これをすべて実行した結果は次のとおりです。

8  
********************  
sample size: 1000  
********************  
np: 0.00012079699808964506  
mt: 0.002468645994667895  
np: 0.00011947099847020581  
mt: 0.0020772050047526136  
********************  
sample size: 10000  
********************  
np: 0.00024697799381101504  
mt: 0.002037393998762127  
np: 0.0002713389985729009  
mt: 0.0020942929986631498  
********************  
sample size: 100000  
********************  
np: 0.0007130410012905486  
mt: 0.0019842900001094677  
np: 0.0007540129954577424  
mt: 0.0029724110063398257  
********************  
sample size: 1000000  
********************  
np: 0.0094779249993735  
mt: 0.007134920000680722  
np: 0.009129883001151029  
mt: 0.012836456997320056  
********************  
sample size: 10000000  
********************  
np: 0.09471094200125663  
mt: 0.0453535050037317  
np: 0.09436299200024223  
mt: 0.04188535599678289  
********************  
sample size: 100000000  
********************  
np: 0.9537652180006262  
mt: 0.3957935369980987  
np: 0.9624398809974082  
mt: 0.4019058070043684  

これらは、スレッドの初期の結果が示す3.5倍のスピードアップとマルチスレッド化を組み込んでいない結果よりもはるかに有望ではありません。私が得た結果はある程度合理的です。スレッド化のオーバーヘッドが予想され、配列が非常に大きくなるまでの時間を支配します。その時点でパフォーマンスの向上はstd::thread::hardware_concurrencyxの増加に近づき始めます。

結論

特にマルチスレッドに関しては、いくつかのNumPyコードに対してアプリケーション固有の最適化の余地は確かにあります。努力する価値があるかどうかは私には明らかではありませんが、確かに良い練習(または何か)のようです。Cythonのような「サードパーティのツール」のいくつかを学ぶことは、時間のより良い使い方かもしれませんが、誰もが知っていると思います。


1
私はあなたのコードを勉強し始めます、いくつかのC ++を知っていますが、それでもstd :: futureとstd :: asyncを使用していません。'min_max_mt'テンプレート関数で、すべてのワーカーが起動と結果の取得の間に完了したことをどのようにして知るのですか?(理解することだけを求め、
それで

ラインv = min_max_it->get();getメソッドのブロックの結果は準備ができているし、それを返すまで。ループはそれぞれの未来を通過するため、すべてが完了するまで終了しません。future.get()
Nathan Chappell

0

私が思いついた最短の方法はこれです:

mn, mx = np.sort(ar)[[0, -1]]

ただし、配列をソートするため、最も効率的ではありません。

別の短い方法は次のとおりです。

mn, mx = np.percentile(ar, [0, 100])

これはより効率的ですが、結果が計算され、floatが返されます。


残念ながら、この2つはこのページの他のソリューションと比較して最も遅いソリューションです。m = np.min(a); M = np.max(a)-> 0.54002 ||| m、M = f90_minmax1(a)-> 0.72134 ||| m、M = numba_minmax(a)-> 0.77323 ||| m、M = np.sort(a)[[0、-1]]-> 12.01456 ||| m、M = np.percentile(a、[0、100])-> 11.09418 ||| 100kの要素の配列のために10000回繰り返し秒で
イザヤ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.