Python 3'te x ** 4.0 neden x ** 4 'den daha hızlı?


164

Neden daha x**4.0hızlı x**4? CPython 3.5.2 kullanıyorum.

$ python -m timeit "for x in range(100):" " x**4.0"
  10000 loops, best of 3: 24.2 usec per loop

$ python -m timeit "for x in range(100):" " x**4"
  10000 loops, best of 3: 30.6 usec per loop

Nasıl hareket ettiğini görmek için yükselttiğim gücü değiştirmeyi denedim ve örneğin x'i 10 veya 16'nın gücüne yükseltirsem 30'dan 35'e atlarsam, ancak bir şamandıra olarak 10.0'a yükseltirsem , sadece hareket eder yaklaşık 24.1 ~ 4.

Sanırım float dönüşümü ve 2 gücü ile ilgili bir şey var, ama gerçekten bilmiyorum.

Her iki durumda da 2 gücünün daha hızlı olduğunu fark ettim, çünkü bu hesaplamalar tercüman / bilgisayar için daha doğal / kolay. Ama yine de, yüzenlerle neredeyse hareket etmiyor. 2.0 => 24.1~4 & 128.0 => 24.1~4 fakat 2 => 29 & 128 => 62


TigerhawkT3 , bunun döngü dışında gerçekleşmediğine dikkat çekti. Kontrol ettim ve durum sadece taban görüldüğünde (gördüğümden) meydana geliyor . Bunun hakkında bir fikrin var mı?


11
Değeri için: Python 2.7.13 benim için 2 ~ 3 daha hızlı bir faktördür ve ters davranışı gösterir: bir tamsayı üssü bir kayan nokta üssünden daha hızlıdır.

4
@Evert yup için 14 usec x**4.0ve 3.9 aldım x**4.
dabadaba

Yanıtlar:


161

Neden Python 3 * ' den x**4.0 daha hızlı ?x**4

Python 3 intnesneleri, keyfi bir boyutu desteklemek için tasarlanmış tam teşekküllü bir nesnedir; Bu nedenle, C seviyesinde bu şekilde ele alınırlar (tüm değişkenlerin nasıl PyLongObject *tür olarak bildirildiğine bakın long_pow). Bu, üslerini çok daha zor ve sıkıcı hale getirir, çünkü ob_digitdeğerini gerçekleştirmek için kullandığı dizi ile oynamanız gerekir. ( Cesurlar için kaynak. - Bkz . Daha fazla bilgi için Python'daki büyük tamsayılar için bellek ayırmayı anlamaPyLongObject .)

Piton floatamaçları, tam tersine, transforme edilebilir bir C double(kullanarak türü PyFloat_AsDouble) ve işlemler yapılabilir , bu doğal türleri kullanarak . Bu harika alakalı kenar-durumlar için kontrol ettikten sonra, bu için Python verir, çünkü platformları kullanabilirsinizpow ( C'ler powolduğunu ) fiili üs ele:

/* Now iv and iw are finite, iw is nonzero, and iv is
 * positive and not equal to 1.0.  We finally allow
 * the platform pow to step in and do the rest.
 */
errno = 0;
PyFPE_START_PROTECT("pow", return NULL)
ix = pow(iv, iw); 

nerede ivve iwbizim orijinal PyFloatObjectC olarak doubles.

Değeri 2.7.13için: Benim için Python 2~3daha hızlı bir faktördür ve ters davranışı gösterir.

Önceki gerçek de Python 2 ve 3 arasındaki tutarsızlığı açıklıyor , bu yüzden ilginç olduğu için bu yorumu da ele alacağımı düşündüm.

Python 2'de, Python 3'teki nesneden intfarklı olan eski nesneyi kullanıyorsunuz int( int3.x'teki tüm nesneler türdedir PyLongObject). Python 2'de, nesnenin değerine (veya sonek kullanırsanız L/l) bağlı bir ayrım vardır :

# Python 2
type(30)  # <type 'int'>
type(30L) # <type 'long'>

<type 'int'>Burada gördüğünüz aynı şeyi yapar floatler yapmak güvenle bir C dönüştürülmüş olur, long üs alma üzerine yapılır ( int_powbunu yapabiliyorsa, böylece aynı zamanda bir kayıt onları koymak için derleyici ipuçları verebilir bir fark yaratmak) :

static PyObject *
int_pow(PyIntObject *v, PyIntObject *w, PyIntObject *z)
{
    register long iv, iw, iz=0, ix, temp, prev;
/* Snipped for brevity */    

bu iyi bir hız kazancı sağlar.

Durgun s'ların <type 'long'>s ile karşılaştırıldığında ne kadar olduğunu görmek için <type 'int'>, xadı longPython 2'deki bir çağrıda tamamladıysanız (esas long_powolarak Python 3'teki gibi kullanmaya zorlarsanız), hız kazancı kaybolur:

# <type 'int'>
(python2)  python -m timeit "for x in range(1000):" " x**2"       
10000 loops, best of 3: 116 usec per loop
# <type 'long'> 
(python2)  python -m timeit "for x in range(1000):" " long(x)**2"
100 loops, best of 3: 2.12 msec per loop

Not alın, bu bir pasajı dönüşümleri olsa intiçin long(@pydsinger tarafından sivri out gibi) diğer yaptığı, bu döküm yavaşlamanın arkasında katkıda güç değildir. Uygulamasıdır long_pow. (İfadeleri yalnızca long(x)görmek için zamanlayın).

[...] döngü dışında olmaz. [...] Bunun hakkında bir fikrin var mı?

Bu, sabitleri sizin için katlayan CPython'un gözetleme deliği optimize edicisidir. Her iki durumda da aynı kesin zamanlamaları elde edersiniz, çünkü üstelimin sonucunu bulmak için gerçek bir hesaplama yoktur, sadece değerlerin yüklenmesi:

dis.dis(compile('4 ** 4', '', 'exec'))
  1           0 LOAD_CONST               2 (256)
              3 POP_TOP
              4 LOAD_CONST               1 (None)
              7 RETURN_VALUE

Özdeş bayt kodu, '4 ** 4.'tek fark int yerine LOAD_CONSTfloat yükler olmasıdır :256.0256

dis.dis(compile('4 ** 4.', '', 'exec'))
  1           0 LOAD_CONST               3 (256.0)
              2 POP_TOP
              4 LOAD_CONST               2 (None)
              6 RETURN_VALUE

Yani zamanlar aynı.


* Yukarıdakilerin tümü sadece Python'un referans uygulaması olan CPython için geçerlidir. Diğer uygulamalar farklı performans gösterebilir.


Her ne ise, rangesadece zamanlamanın **tamsayılar ve şamandıralar arasında bir fark yaratmadığından , a üzerindeki döngü ile ilgilidir .
TigerhawkT3

Fark sadece bir değişken ararken görülür ( 4**4aynı hızdadır 4**4.0) ve bu cevap buna hiç değinmez.
TigerhawkT3

1
Ancak, sabitler @ TigerhawkT3 ( dis(compile('4 ** 4', '', 'exec'))) 'e katlanır, böylece zaman tam olarak aynı olmalıdır .
Dimitris Fasarakis Hilliard

Son zamanlamalarınız söylediklerinizi göstermiyor gibi görünüyor. long(x)**2.hala long(x)**24-5 katına göre daha hızlı . (Yine de downvoters değil)
Graipher

3
@ mbomb007, <type 'long'>Python 3'teki türün ortadan kaldırılması muhtemelen dili basitleştirmek için gösterilen çabalarla açıklanmaktadır. Tamsayıları temsil edecek bir türünüz varsa, ikiden daha yönetilebilir (ve gerektiğinde birinden diğerine dönüştürme konusunda endişeleniyorsanız, kullanıcıların kafası karışır vb.). Hız kazancı buna ikincildir. PEP 237'nin mantık bölümü de biraz daha içgörü sunmaktadır.
Dimitris Fasarakis Hilliard

25

Bayt koduna bakarsak, ifadelerin tamamen aynı olduğunu görebiliriz. Tek fark, argümanı olacak bir sabit tipidir BINARY_POWER. Bu yüzden büyük olasılıkla intçizginin aşağısında kayan nokta numarasına dönüştürülmesinden kaynaklanıyor .

>>> def func(n):
...    return n**4
... 
>>> def func1(n):
...    return n**4.0
... 
>>> from dis import dis
>>> dis(func)
  2           0 LOAD_FAST                0 (n)
              3 LOAD_CONST               1 (4)
              6 BINARY_POWER
              7 RETURN_VALUE
>>> dis(func1)
  2           0 LOAD_FAST                0 (n)
              3 LOAD_CONST               1 (4.0)
              6 BINARY_POWER
              7 RETURN_VALUE

Güncelleme: CPython kaynak kodundaki Objects / abstract.c dosyasına bir göz atalım :

PyObject *
PyNumber_Power(PyObject *v, PyObject *w, PyObject *z)
{
    return ternary_op(v, w, z, NB_SLOT(nb_power), "** or pow()");
}

PyNumber_Powerternary_opBuraya yapıştırmak için çok uzun olan çağrılar , işte link .

Bir argüman olarak geçerek nb_poweryuvasını çağırır .xy

Son olarak, Objects / floatobject.cfloat_pow() 686 satırında , argümanların gerçek işlemden hemen önce C'ye dönüştürüldüğünü görüyoruz :double

static PyObject *
float_pow(PyObject *v, PyObject *w, PyObject *z)
{
    double iv, iw, ix;
    int negate_result = 0;

    if ((PyObject *)z != Py_None) {
        PyErr_SetString(PyExc_TypeError, "pow() 3rd argument not "
            "allowed unless all arguments are integers");
        return NULL;
    }

    CONVERT_TO_DOUBLE(v, iv);
    CONVERT_TO_DOUBLE(w, iw);
    ...

1
@ Jean-FrançoisFabre Bunun sürekli katlanmadan kaynaklandığını düşünüyorum.
Dimitris Fasarakis Hilliard

2
Ben bir dönüşüm olduğunu ima ve onlar "kesinlikle" satır aşağı farklı ele değildir bir kaynak olmadan bir streç biraz olduğunu düşünüyorum.
miradulo

1
@Mitch - Özellikle, bu özel kodda, bu iki işlem için yürütme süresinde hiçbir fark yoktur. Fark sadece OP'nin döngüsünde ortaya çıkar. Bu cevap sonuçlara atlıyor.
TigerhawkT3

2
Neden sadece float_powbu yavaş dava için bile çalışmazken bakıyorsun ?
user2357112 Monica

2
@ TigerhawkT3: 4**4ve 4**4.0sürekli katlanın. Bu tamamen ayrı bir etki.
user2357112 Monica

-1

Biri doğru olduğu için diğeri yaklaşıktır.

>>> 334453647687345435634784453567231654765 ** 4.0
1.2512490121794596e+154
>>> 334453647687345435634784453567231654765 ** 4
125124901217945966595797084130108863452053981325370920366144
719991392270482919860036990488994139314813986665699000071678
41534843695972182197917378267300625

Bu vahşinin neden azaldığını bilmiyorum ama yaptım çünkü bu cevap soruya cevap vermiyor. Bir şeyin doğru olması hiçbir şekilde daha hızlı veya daha yavaş olduğu anlamına gelmez. Biri diğerinden daha yavaştır, çünkü biri C tipleri ile çalışabilirken diğeri Python Nesneleri ile çalışmak zorundadır.
Dimitris Fasarakis Hilliard

1
Açıklama için teşekkürler. Aslında, bir sayının 12'ye yakın rakamını hesaplamanın, hepsini tam olarak hesaplamaktan daha hızlı olduğunu gerçekten düşündüm. Sonuçta, yaklaşımları kullanmamızın tek nedeni hesaplamanın daha hızlı olması değil mi?
Veky
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.