NumPy: eşzamanlı max () ve min () işlevi


109

numpy.amax () , bir dizideki maksimum değeri bulur ve numpy.amin () , minimum değer için aynı şeyi yapar. Hem max hem de min bulmak istiyorsam, her iki işlevi de çağırmalıyım, bu da (çok büyük) diziyi iki kez geçirmeyi gerektirir, ki bu yavaş görünüyor.

Numpy API'de, verilerde yalnızca tek bir geçişle hem maks hem de min'i bulan bir işlev var mı?


1
Ne kadar büyük, çok büyük? Biraz zamanım olursa, bir fortran uygulamasını amaxveamin
mgilson

1
"Çok büyük" ifadesinin öznel olduğunu kabul ediyorum. Benim durumumda, birkaç GB olan dizilerden bahsediyorum.
Stuart Berg

bu oldukça büyük. Fortran'da hesaplamak için bir örnek kodladım (fortran'ı bilmeseniz bile, kodu anlamak oldukça kolay olmalı). Fortran ile numpy arasında koşmak gerçekten bir fark yaratıyor. (Muhtemelen, C'den aynı performansı alabilmelisiniz ...) Emin değilim - sanırım işlevlerimin neden onlarınkinden çok daha iyi performans gösterdiğini yorumlamak için
aptal bir geliştiriciye ihtiyacımız olacak

Tabii ki, bu pek de yeni bir fikir değil. Örneğin, boost minmax kitaplığı (C ++), aradığım algoritmanın bir uygulamasını sağlar.
Stuart Berg

3
Sorulan soruya gerçekten bir cevap değil, ancak muhtemelen bu konudaki insanların ilgisini çekiyor. NumPy'ye sayıdaki minmaxkitaplığa eklemeyle ilgili sorular soruldu ( github.com/numpy/numpy/issues/9836 ).
jakirkham

Yanıtlar:


49

Numpy API'de, verilerde yalnızca tek bir geçişle hem maks hem de min'i bulan bir işlev var mı?

Hayır. Bu yazının yazıldığı tarihte böyle bir işlev yoktur. (Orada Ve evet vardı böyle bir fonksiyon, performans olacağını anlamlı çağırmaktan daha iyi numpy.amin()ve numpy.amax()arka arkaya büyük dizi.)


31

Diziyi iki kez geçmenin bir sorun olduğunu düşünmüyorum. Aşağıdaki sözde kodu düşünün:

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

Burada sadece 1 döngü varken, hala 2 kontrol var. (Her biri 1 kontrol içeren 2 döngü yerine). Gerçekten kaydettiğiniz tek şey 1 döngünün ek yüküdür. Diziler dediğiniz gibi gerçekten büyükse, bu ek yük, gerçek döngünün iş yüküne kıyasla küçüktür. (Bunların hepsinin C'de uygulandığına dikkat edin, bu nedenle döngüler zaten az çok özgürdür).


DÜZENLE Onay veren ve bana inanan 4'ünüze özür dilerim. Bunu kesinlikle optimize edebilirsiniz.

İşte bir python modülüne derlenebilecek bazı fortran kodları f2py(belki bir Cythonguru gelip bunu optimize edilmiş bir C versiyonuyla karşılaştırabilir ...):

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

Şu yolla derleyin:

f2py -m untitled -c fortran_code.f90

Ve şimdi test edebileceğimiz bir yerdeyiz:

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'

Sonuçlar benim için biraz şaşırtıcı:

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

Söylemeliyim, tam olarak anlamıyorum. Sadece ile np.minkarşılaştırmak minmax1ve minmax2hala kaybedilen bir savaş, bu yüzden bu sadece bir hafıza sorunu değil ...

Notlar - Boyutu bir faktör kadar artırmak 10**ave tekrarı bir faktör kadar azaltmak 10**a(problem boyutunu sabit tutmak) performansı değiştirir, ancak görünüşte tutarlı bir şekilde değil; python. Hatta minfortran beats numpy'deki basit bir uygulamayı yaklaşık 2 ...


21
Tek geçişin avantajı bellek verimliliğidir. Özellikle diziniz takas edilebilecek kadar büyükse, bu çok büyük olabilir.
Dougal

4
Bu tam olarak doğru değil, neredeyse yarısı kadar hızlı, çünkü bu tür dizilerde bellek hızı genellikle sınırlayıcı faktördür, bu yüzden yarı yarıya hızlı olabilir ...
seberg

3
Her zaman iki çeke ihtiyacınız yoktur. Eğer i < minvaldoğruysa, o zaman i > maxvalsadece ikinci zaman ortalama yineleme başına 1.5 kontrolleri yapmak gerekir, böylece her zaman yanlış ifbir değiştirilir elif.
Fred Foo

2
Küçük not: Cython'un en optimize Python çağrılabilir C modülünü elde etmenin yolu olduğundan şüpheliyim. Cython'un hedefi, bir tür tip açıklamalı Python olmaktır, bu daha sonra makine tarafından C'ye çevrilir, oysa f2pysadece elle kodlanmış Fortran'ı Python tarafından çağrılabilir olacak şekilde sarar. "Daha adil" bir test muhtemelen C'yi elle kodlamaktır ve sonra f2pyPython için sarmak için (!) Kullanmaktır . C ++ 'ya izin veriyorsanız, Shed Skin, kodlama kolaylığını performansla dengelemek için tatlı nokta olabilir.
John Y

4
numpy 1.8 min ve max itibariyle amd64 platformlarında vektörleştirilir, benim core2duo numpy'imde bu fortran kodu kadar iyi performans gösterir. Ancak dizi, daha büyük cpu önbelleklerinin boyutunu aşarsa tek bir geçiş avantajlı olacaktır.
jtaylor

23

Sizin için yararlıysa numpy.ptp adında (maks-min) bulmak için bir işlev vardır :

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

ancak bir geçişle hem min hem de maks. bulmanın bir yolu olduğunu sanmıyorum.

DÜZENLEME: ptp kaputun altında sadece min ve max çağırır


2
Sinir bozucu çünkü muhtemelen ptp'nin uygulanma şekli max ve min!
Andy Hayden

1
Veya sadece max ve
min'i çağırabilir

3
@hayden çıkıyor ptp sadece max ve
min'i

1
Bu, maskelenmiş dizi koduydu; ana ndarray kodu C'dedir. Ancak, C kodunun dizi üzerinde iki kez yinelediği ortaya çıktı: github.com/numpy/numpy/blob/… .
Ken Arnold

20

Sen kullanabilirsiniz Numba LLVM kullanarak NumPy farkında dinamik Python derleyici olduğunu. Ortaya çıkan uygulama oldukça basit ve açıktır:

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()))

Ayrıca bir Numpy'nin min() & max()uygulamasından daha hızlı olmalıdır . Üstelik tek bir C / Fortran kod satırı yazmak zorunda kalmadan.

Her zaman mimarinize, verilerinize, paket sürümlerinize bağlı olduğu için kendi performans testlerinizi yapın ...


2
> Ayrıca bir Numpy'nin min () & max () uygulamasından daha hızlı olmalı Bunun doğru olduğunu düşünmüyorum. numpy yerel python değildir - C. "x = numpy.random.rand (10000000) t = time () i için range (1000): minmax (x) print ('numba', time () - t) t = time () aralıktaki i için (1000): x.min () x.max () print ('numpy', time () - t) `` Sonuçlar: ('numba', 10.299750089645386 ) ('numpy', 9.898081064224243)
Authman Apatira

1
@AuthmanApatira: Evet, kıyaslamalar her zaman böyledir, bu yüzden " yapmalı " (daha hızlı olmalı ) ve " her zaman mimarinize, verilerinize bağlı olduğu için kendi performans testlerinizi yapın ... " dedim . Benim durumumda, 3 bilgisayarla denedim ve aynı sonucu aldım (Numba, Numpy'den daha hızlıydı), ancak bilgisayarınızda sonuçlar farklı olabilir ... numbaJIT tarafından derlendiğinden emin olmak için, karşılaştırmadan önce işlevi bir kez çalıştırmayı denediniz mi? ?. Ayrıca, ipythonbasitlik için kullanırsanız %timeit whatever_code(), zaman yürütmeyi ölçmek için kullanmanızı öneririm .
Peque

3
@AuthmanApatira: Her halükarda, bu cevapla göstermeye çalıştığım şey, bazen Python kodunun (bu durumda Numba ile JIT tarafından derlenen) en hızlı C-derlenmiş kitaplık kadar hızlı olabileceğidir (en azından aynı siparişten bahsediyoruz) (büyüklük), saf Python kodundan başka bir şey yazmadığımızı hesaba katarsak bu etkileyici, katılmıyor musunuz? ^^
Peque

Katılıyorum =) Ayrıca, Jupyter ile ilgili önceki yorumdaki ipuçları ve zamanlama kodunun dışında bir kez işlevi derlediğiniz için teşekkür ederim.
Authman Apatira

1
Sadece bununla karşılaştım, pratik durumlarda önemli değil, ancak elifminimum değerinizin maksimum değerinizden daha büyük olmasına izin veriyor. Örneğin, 1 uzunluğunda bir dizi ile maksimum, değer ne olursa olsun, min + sonsuzdur. Tek seferlik bir şey için büyük bir şey değil, ama bir üretim canavarının derinliklerine atmak için iyi bir kod değil.
Mike Williamson

12

Genel olarak, aynı anda iki öğeyi işleyerek ve yalnızca küçük olanı geçici minimuma ve büyük olanı geçici maksimum ile karşılaştırarak bir minmax algoritması için karşılaştırma miktarını azaltabilirsiniz. Ortalama olarak, karşılaştırmaların sadece 3 / 4'üne, naif bir yaklaşıma ihtiyaç vardır.

Bu, c veya fortran (veya başka herhangi bir düşük seviyeli dilde) uygulanabilir ve performans açısından neredeyse rakipsiz olmalıdır. kullanıyorum ilkeyi açıklamak ve çok hızlı, dtype bağımsız bir uygulama elde etmek için:

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

Peque'in sunduğu saf yaklaşımdan kesinlikle daha hızlı :

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

Beklendiği gibi, yeni minmax uygulaması, saf uygulamanın aldığı sürenin kabaca 3 / 4'ünü alıyor ( 2.1 / 2.75 = 0.7636363636363637)


1
Benim makinemde, çözümünüz Peque'inkinden daha hızlı değil. Numba 0.33.
John Zwinck

@johnzwinck, cevabımda farklı bir ölçüt mü çalıştırdınız? Eğer öyleyse paylaşabilir misin? Ama mümkün: Yeni sürümlerde de bazı gerileme olduğunu fark ettim.
MSeifert

Kıyaslamanı yaptım. Çözümünüzün ve @ Peque'in zamanlamaları hemen hemen aynıydı (~ 2.8 ms).
John Zwinck

@JohnZwinck Bu garip, sadece tekrar test ettim ve bilgisayarımda kesinlikle daha hızlı. Belki de donanıma bağlı olarak numba ve LLVM ile bir ilgisi vardır.
MSeifert

Şimdi başka bir makineyi denedim (etli bir iş istasyonu) ve sizinki için 2,4, Peque'ler için 2,6 ms aldım. Yani, küçük bir galibiyet.
John Zwinck

11

Aşağıdaki yaklaşımlar göz önüne alındığında, beklenebilecek rakamlar hakkında bazı fikirler edinmek için:

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_*()yaklaşımlar önerilmiştir ne benzer buraya iken, extrema_while_*()yaklaşımlar kod dayanmaktadır burada )

Aşağıdaki zamanlamalar:

bm

extrema_while_*()en hızlı olanın en hızlı olduğunu belirtin extrema_while_nb(). Her durumda, extrema_loop_nb()ve extrema_loop_cy()çözümleri de yalnızca NumPy yaklaşımından daha iyi performans gösterir (kullanarak np.max()ve np.min()ayrı ayrı).

Son olarak, not bunların hiçbiri kadar esnek olduğunu np.min()/ np.max()(n-dim destek açısından, axisparametresi, vs).

(tam kod burada mevcuttur )


2
@Njit kullanırsanız ekstra% 10 hız kazanabilirsiniz gibi görünüyor (fastmath = True)extrema_while_nb
argenisleon

10

Hiç kimse numpy.percentile'dan bahsetmedi , ben de yapacağımı düşündüm. [0, 100]Yüzdelik dilimler için sorarsanız , size iki öğeden oluşan bir dizi verir: minimum (0. yüzdelik dilim) ve maksimum (100. yüzdelik dilim).

Ancak, OP'nin amacını karşılamıyor: ayrı ayrı minimum ve maksimumdan daha hızlı değil. Bu muhtemelen aşırı olmayan yüzdelik dilimlere izin veren bazı makinelerden kaynaklanmaktadır (daha uzun sürmesi gereken daha zor bir problem ).

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'nin gelecekteki bir sürümü, yalnızca [0, 100]istenirse normal yüzdelik hesaplamasını atlamak için özel bir durum oluşturabilir . Arayüze herhangi bir şey eklemeden, Numpy'den bir aramada min ve max istemenin bir yolu var (kabul edilen cevapta söylenenin aksine), ancak kütüphanenin standart uygulaması bunu yapmak için bu durumdan yararlanmıyor değerli.


9

Bu eski bir konu, ama her neyse, biri buna bir daha bakarsa ...

Minimum ve maksimum değerleri aynı anda ararken, karşılaştırma sayısını azaltmak mümkündür. Karşılaştırdığınız yüzer ise (sanırım öyle) bu size biraz zaman kazandırabilir, ancak hesaplama karmaşıklığı olmasa da.

(Python kodu) yerine:

_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]

Önce dizideki iki bitişik değeri karşılaştırabilir ve ardından yalnızca küçük olanı geçerli minimum değerle ve büyük olanı geçerli maksimum değerle karşılaştırabilirsiniz:

## 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

Buradaki kod Python'da yazılmıştır, açıkça C veya Fortran veya Cython kullanacağınız hız içindir, ancak bu şekilde her yineleme için 3 karşılaştırma yaparak, uzunluk (ar) / 2 yineleme ile 3/2 * uzunluk (ar) karşılaştırmaları yaparsınız. Bunun tersine, karşılaştırmayı "açık yolla" yaparak yineleme başına iki karşılaştırma yaparak 2 * len (ar) karşılaştırmalarına yol açarsınız. Karşılaştırma süresinden% 25 tasarruf sağlar.

Belki bir gün birisi bunu faydalı bulacaktır.


6
bunu kıyasladın mı? modern x86 donanımında, ilk varyantta kullanılan minimum ve maksimum için makine talimatlarınız vardır; bunlar, dallara olan ihtiyacı ortadan kaldırırken, kodunuz muhtemelen donanımla aynı şekilde eşleşmeyen bir kontrol bağımlılığı sağlar.
jtaylor

Aslında yapmadım. Bir şansım olursa yapacağım. Saf python kodunun herhangi bir mantıklı derlenmiş uygulamaya elden bırakılacağının oldukça açık olduğunu düşünüyorum, ancak Cython'da bir hızlanma görülebilecek mi merak ediyorum ...
Bennet

13
Numpy'de, kaputun altında, tarafından kullanılan bir minmax uygulaması var, burayanp.bincount bakın . Bahsettiğiniz numarayı kullanmıyor, çünkü saf yaklaşımdan 2 kat daha yavaş olduğu ortaya çıktı. Bir bağlantı vardır PR hem yöntemlerden bazılarını kapsamlı kriterler.
Jaime

5

İlk bakışta hile yapıyor gibi görünüyor :numpy.histogram

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

... ancak bu fonksiyonun kaynağına bakarsanız , o sadece a.min()ve a.max()bağımsız olarak çağırır ve bu nedenle bu soruda ele alınan performans endişelerinden kaçınmada başarısız olur. :-(

Benzer şekilde, scipy.ndimage.measurements.extremabir olasılık gibi görünüyor, ama o da basitçe çağırıyor a.min()ve a.max()bağımsız olarak.


3
np.histogramher zaman bunun için çalışmaz çünkü döndürülen (amin, amax)değerler, bölmenin minimum ve maksimum değerleri içindir. Ben varsa, örneğin, a = np.zeros(10), np.histogram(a, bins=1)döner (array([10]), array([-0.5, 0.5])). Bu durumda kullanıcı (amin, amax)= (0, 0) arar .
eclark

3

Zaten benim için çabaya değdi, bu yüzden burada ilgilenen herkes için en zor ve en az zarif çözümü önereceğim. Benim çözümüm, C ++ 'da tek geçişli algoritmada çok iş parçacıklı bir min-maks uygulamak ve bunu bir Python genişletme modülü oluşturmak için kullanmaktır. Bu çaba, Python ve NumPy C / C ++ API'lerinin nasıl kullanılacağını öğrenmek için biraz ek yük gerektirir ve burada kodu göstereceğim ve bu yoldan gitmek isteyenler için bazı küçük açıklamalar ve referanslar vereceğim.

Çok iş parçacıklı Min / Maks

Burada çok ilginç bir şey yok. Dizi, büyüklükteki parçalara bölünmüştür length / workers. A'daki her öbek için min / maks hesaplanır ve futurebunlar daha sonra genel min / maks için taranır.

    // 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 Uzantı Modülü

İşte burada işler çirkinleşmeye başlıyor ... Python'da C ++ kodunu kullanmanın bir yolu, bir uzantı modülü uygulamaktır. Bu modül, distutils.corestandart modül kullanılarak oluşturulabilir ve kurulabilir . Bunun neleri gerektirdiğinin tam bir açıklaması Python belgelerinde yer almaktadır: https://docs.python.org/3/extending/extending.html . NOT: Benzer sonuçlar elde etmenin kesinlikle başka yolları da vardır, https://docs.python.org/3/extending/index.html#extending-index :

Bu kılavuz, yalnızca CPython'un bu sürümünün bir parçası olarak sağlanan uzantıları oluşturmak için temel araçları kapsar. Cython, cffi, SWIG ve Numba gibi üçüncü taraf araçlar, Python için C ve C ++ uzantıları oluşturmak için hem daha basit hem de daha karmaşık yaklaşımlar sunar.

Esasen, bu rota muhtemelen pratikten daha akademik. Bununla birlikte, daha sonra yaptığım şey, öğreticiye oldukça yaklaşarak bir modül dosyası oluşturmaktı. Bu, esasen dağıtımcıların kodunuzla ne yapacaklarını bilmesi ve ondan bir Python modülü oluşturması için bir ortak metindir. Bunlardan herhangi birini yapmadan önce, muhtemelen bir Python sanal ortamı oluşturmak akıllıca olacaktır, böylece sistem paketlerinizi kirletmezsiniz (bkz. Https://docs.python.org/3/library/venv.html#module-venv ).

İşte modül dosyası:

// 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); }

Bu dosyada NumPy API'sinin yanı sıra Python'un da önemli bir kullanımı vardır, daha fazla bilgi için şu adrese başvurun: https://docs.python.org/3/c-api/arg.html#c.PyArg_ParseTuple ve NumPy için : https://docs.scipy.org/doc/numpy/reference/c-api.array.html .

Modülü Kurma

Yapılması gereken bir sonraki şey, modülü kurmak için dağıtımları kullanmaktır. Bu bir kurulum dosyası gerektirir:

# 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])

Sonunda modülü kurmak python3 setup.py installiçin sanal ortamınızdan çalıştırın .

Modülü Test Etme

Son olarak, C ++ uygulamasının aslında NumPy'nin saf kullanımından daha iyi performans gösterip göstermediğini görmek için test edebiliriz. Bunu yapmak için işte basit bir test komut dosyası:

# 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))

İşte tüm bunları yapmaktan aldığım sonuçlar:

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  

Bunlar, 3.5 kat hızlanma olduğunu belirten ve çoklu iş parçacığı içermeyen, sonuçların daha önce gösterdiğinden çok daha az cesaret vericidir. Elde ettiğim sonuçlar biraz makul, iş parçacığının ek yükünün ve diziler çok geniş olana kadar geçen süreye hakim olacağını umuyorum, bu noktada performans artışı std::thread::hardware_concurrencyx artışına yaklaşmaya başlayacaktı .

Sonuç

Bazı NumPy kodlarında uygulamaya özel optimizasyonlar için kesinlikle yer vardır, öyle görünüyor ki, özellikle çoklu iş parçacığı ile ilgili olarak. Bunun çabaya değip değmeyeceği açık değil, ama kesinlikle iyi bir egzersiz (veya başka bir şey) gibi görünüyor. Sanırım Cython gibi bazı "üçüncü parti araçları" öğrenmenin zamanı daha iyi kullanabileceğini düşünüyorum, ama kim bilir.


1
Kodunuzu incelemeye başlıyorum, biraz C ++ biliyorum ama hala std :: future ve std :: async kullanmıyorum. 'Min_max_mt' şablon işlevinizde, her işçinin ateşleme ve sonuçları alma arasında bitirdiğini nasıl anlar? (Sadece anlamayı istemek, bunun yanlış bir şey olduğunu
söylememek

Çizgi v = min_max_it->get();. getYöntem blokları sonuç hazırdır ve döner o kadar. Döngü her gelecekten geçtiği için, hepsi bitene kadar bitmeyecek. Future.get ()
Nathan Chappell

0

Bulduğum en kısa yol şudur:

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

Ancak diziyi sıraladığı için en verimli olanı değil.

Başka bir kısa yol şudur:

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

Bu daha verimli olmalı, ancak sonuç hesaplanır ve bir kayan nokta döndürülür.


Utanç verici bir şekilde, bu ikisi bu sayfadaki diğerlerine kıyasla en yavaş çözümlerdir: 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 ||| 100 bin öğeden oluşan bir dizi için 10000 tekrar için saniye cinsinden
Isaías
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.