ทำไม x ** 4.0 เร็วกว่า x ** 4 ใน Python 3


164

ทำไมจึงx**4.0เร็วกว่าx**4? ฉันใช้ CPython 3.5.2

$ 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

ฉันพยายามเปลี่ยนพลังที่ฉันยกขึ้นมาเพื่อดูว่ามันทำหน้าที่อย่างไรและตัวอย่างเช่นถ้าฉันเพิ่ม x เป็นพลังของ 10 หรือ 16 มันกระโดดจาก 30 เป็น 35 แต่ถ้าฉันเพิ่มขึ้น10.0เท่าลอยมันแค่ขยับ ประมาณ 24.1 ~ 4

ฉันเดาว่ามันมีบางอย่างเกี่ยวข้องกับการแปลงแบบลอยตัวและอาจมีพลังของ 2 แต่ฉันไม่รู้จริง ๆ

ฉันสังเกตเห็นว่าในทั้งสองกรณีพลังของ 2 เร็วขึ้นฉันเดาว่าเนื่องจากการคำนวณเหล่านั้นมีความเป็นพื้นเมือง / ง่ายกว่าสำหรับล่าม / คอมพิวเตอร์ แต่ถึงกระนั้นด้วยการลอยมันเกือบจะไม่เคลื่อนไหว 2.0 => 24.1~4 & 128.0 => 24.1~4 แต่ 2 => 29 & 128 => 62


TigerhawkT3ชี้ให้เห็นว่ามันไม่ได้เกิดขึ้นนอกวง ฉันตรวจสอบแล้วและสถานการณ์ก็เกิดขึ้น (จากสิ่งที่ฉันเคยเห็น) เมื่อฐานถูกยกขึ้น มีความคิดเกี่ยวกับสิ่งนั้นไหม?


11
สำหรับสิ่งที่มีค่า: Python 2.7.13 สำหรับฉันนั้นเร็วกว่าตัวคูณ 2 ~ 3 และแสดงพฤติกรรมผกผัน: เลขชี้กำลังจำนวนเต็มเร็วกว่าเลขชี้กำลังแบบทศนิยม

4
@Evert yup ผมได้ 14 usec สำหรับx**4.0และ 3.9 x**4สำหรับ
dabadaba

คำตอบ:


161

เพราะเหตุใดจึงx**4.0 ได้เร็วขึ้นกว่าx**4ในหลาม 3 * ?

intวัตถุPython 3 เป็นวัตถุที่เต็มเปี่ยมที่ออกแบบมาเพื่อรองรับขนาดที่กำหนดเอง เนื่องจากความจริงนั้นพวกเขาจะจัดการเช่นนี้ในระดับ C (ดูวิธีการประกาศตัวแปรทั้งหมดเป็นPyLongObject *ประเภทในlong_pow) สิ่งนี้ยังทำให้การยกกำลังของพวกเขายุ่งยากและน่าเบื่อมากขึ้นเนื่องจากคุณต้องเล่นกับob_digitอาเรย์ที่มันใช้เพื่อแทนค่าของมันเพื่อดำเนินการ ( แหล่งที่มาสำหรับผู้กล้า - ดู: ทำความเข้าใจเกี่ยวกับการจัดสรรหน่วยความจำสำหรับจำนวนเต็มขนาดใหญ่ใน Pythonสำหรับข้อมูลเพิ่มเติมเกี่ยวกับPyLongObjects)

งูหลามfloatวัตถุในทางที่สามารถเปลี่ยนไปยัง C doubleประเภท (โดยการใช้PyFloat_AsDouble) และการดำเนินงานสามารถดำเนินการได้โดยใช้ประเภทพื้นเมืองเหล่านั้น นี่เป็นสิ่งที่ยอดเยี่ยมเพราะหลังจากตรวจสอบกรณีขอบที่เกี่ยวข้องจะช่วยให้ Python ใช้แพลตฟอร์ม 'pow ( ของ C powนั่นคือ ) เพื่อจัดการการยกกำลังจริง:

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

ที่ไหนivและiwเป็นต้นฉบับของเราPyFloatObjectเป็น C doubles

สำหรับสิ่งที่มีค่า: Python 2.7.13สำหรับฉันเป็นปัจจัยที่2~3เร็วกว่าและแสดงพฤติกรรมผกผัน

ความจริงก่อนหน้านี้ยังอธิบายถึงความแตกต่างระหว่าง Python 2 และ 3 ดังนั้นฉันคิดว่าฉันจะพูดถึงความคิดเห็นนี้เพราะมันน่าสนใจ

ใน Python 2 คุณกำลังใช้intวัตถุเก่าที่แตกต่างจากintวัตถุใน Python 3 ( intวัตถุทั้งหมดใน 3.x เป็นPyLongObjectประเภท) ใน Python 2 มีความแตกต่างที่ขึ้นอยู่กับมูลค่าของวัตถุ (หรือถ้าคุณใช้คำต่อท้ายL/l):

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

<type 'int'>คุณเห็นที่นี่จะเป็นสิ่งเดียวกันfloats ทำมันได้รับการแปลงอย่างปลอดภัยเป็น C long เมื่อยกกำลังจะดำเนินการกับมัน (คนint_powนอกจากนี้ยังมีคำแนะนำคอมไพเลอร์ที่จะนำ 'em ในการลงทะเบียนถ้ามันสามารถทำได้เพื่อให้สามารถสร้างความแตกต่าง) :

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

สิ่งนี้จะช่วยให้ได้รับความเร็วที่ดี

หากต้องการดูว่าการทำงานของคนเกียจคร้าน<type 'long'>เปรียบเทียบกับ<type 'int'>s อย่างไรหากคุณใส่xชื่อไว้ในการlongโทรใน Python 2 (บังคับให้ใช้long_powใน Python 3 เป็นหลัก) ความเร็วจะหายไป:

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

โปรดทราบว่าแม้ว่าข้อมูลโค้ดหนึ่งจะแปลงintไปเป็นlongในขณะที่อีกตัวอย่างหนึ่งไม่ได้ (ตามที่ระบุโดย @ pydsinger) นักแสดงนี้ไม่ได้เป็นกำลังที่สนับสนุนเบื้องหลังการชะลอตัว การดำเนินการของlong_powคือ (เวลางบเพียงlong(x)เพื่อดู)

[... ] มันไม่ได้เกิดขึ้นนอกวง [... ] มีความคิดเกี่ยวกับเรื่องนี้ไหม?

นี่คือเครื่องมือเพิ่มประสิทธิภาพช่องมองของ CPython พับค่าคงที่สำหรับคุณ คุณจะได้รับการกำหนดเวลาที่แน่นอนเหมือนกันทั้งสองกรณีเนื่องจากไม่มีการคำนวณจริงเพื่อค้นหาผลลัพธ์ของการยกกำลังเพียงแค่โหลดค่า:

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

รหัสไบต์ที่เหมือนกันถูกสร้างขึ้น'4 ** 4.'โดยมีความแตกต่างเพียงอย่างเดียวที่LOAD_CONSTโหลดลอย256.0แทน int 256:

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

ดังนั้นเวลาจึงเท่ากัน


* ทั้งหมดข้างต้นมีผลบังคับใช้สำหรับ CPython เท่านั้นการใช้งานอ้างอิงของ Python การใช้งานอื่น ๆ อาจทำงานแตกต่างกัน


ไม่ว่าจะเป็นอะไรก็ตามมันเกี่ยวข้องกับการวนรอบ a rangeเนื่องจากเวลาเท่านั้นการ**ดำเนินการเองนั้นไม่ทำให้เกิดความแตกต่างระหว่างจำนวนเต็มและจำนวนลอย
TigerhawkT3

ความแตกต่างจะปรากฏขึ้นเมื่อค้นหาตัวแปร ( 4**4เร็วเท่ากับ4**4.0) และคำตอบนี้ไม่ได้สัมผัสกับสิ่งนั้นเลย
TigerhawkT3

1
แต่ค่าคงที่จะได้รับการพับ @ TigerhawkT3 ( dis(compile('4 ** 4', '', 'exec'))) ดังนั้นเวลาควรจะเหมือนกันทุกประการ
Dimitris Fasarakis Hilliard

การกำหนดเวลาล่าสุดของคุณดูเหมือนจะไม่แสดงสิ่งที่คุณพูด long(x)**2.ยังเร็วกว่าlong(x)**2คูณด้วย 4-5 (ไม่ใช่หนึ่งในผู้ลง
คะแนนเสียง

3
@ mbomb007 การกำจัด<type 'long'>ประเภทใน Python 3 นั้นอาจอธิบายได้จากความพยายามในการทำให้ภาษาง่ายขึ้น หากคุณมีประเภทหนึ่งเพื่อเป็นตัวแทนจำนวนเต็มมันสามารถจัดการได้มากกว่าสองประเภท (และกังวลเกี่ยวกับการแปลงจากประเภทหนึ่งเป็นอีกประเภทเมื่อจำเป็นผู้ใช้จะสับสน ฯลฯ ) ความเร็วที่เพิ่มขึ้นนั้นเป็นรอง ส่วนเหตุผลของPEP 237ยังให้ข้อมูลเชิงลึกเพิ่มเติม
Dimitris Fasarakis Hilliard

25

ถ้าเราดูที่ bytecode เราจะเห็นว่าการแสดงออกนั้นเหมือนกันหมดจด BINARY_POWERความแตกต่างเพียงอย่างเดียวคือประเภทของการอย่างต่อเนื่องที่จะเป็นข้อโต้แย้งของ ดังนั้นแน่นอนที่สุดเนื่องจากมีintการแปลงเป็นเลขทศนิยมในบรรทัด

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

อัปเดต: ลองมาดูที่Objects / abstract.cในซอร์สโค้ด CPython:

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

PyNumber_Powerโทรternary_opซึ่งเป็นนานเกินไปที่จะวางที่นี่ดังนั้นนี่คือการเชื่อมโยง

มันเรียกว่าnb_powerสล็อตของxผ่านyเป็นอาร์กิวเมนต์

ในที่สุดfloat_pow()ที่บรรทัด 686 ของObjects / floatobject.cเราจะเห็นว่าการขัดแย้งถูกแปลงเป็น C 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ฉันเชื่อว่าเป็นเพราะการพับอย่างต่อเนื่อง
Dimitris Fasarakis Hilliard

2
ฉันคิดว่าความหมายที่ว่ามีการแปลงและพวกเขาไม่ได้รับการจัดการที่แตกต่างกันไปตามเส้น "แน่นอนที่สุด" เป็นบิตของการยืดโดยไม่มีแหล่งที่มา
miradulo

1
@Mitch - โดยเฉพาะอย่างยิ่งตั้งแต่ในรหัสเฉพาะนี้ไม่มีความแตกต่างในเวลาดำเนินการสำหรับการดำเนินการทั้งสองนั้น ความแตกต่างเกิดขึ้นกับลูปของ OP เท่านั้น คำตอบนี้กำลังกระโดดไปสู่ข้อสรุป
TigerhawkT3

2
ทำไมคุณดูเฉพาะfloat_powเมื่อที่ไม่ได้ใช้สำหรับกรณีที่ช้า?
user2357112 รองรับ Monica

2
@ TigerhawkT3: 4**4และ4**4.0ได้รับการพับอย่างต่อเนื่อง นั่นเป็นผลกระทบที่แยกจากกันอย่างสิ้นเชิง
user2357112 รองรับโมนิก้า

-1

เนื่องจากข้อหนึ่งถูกต้องอีกข้อหนึ่งเป็นการประมาณ

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

ฉันไม่รู้ว่าทำไม downvoter downvote แต่ฉันทำเพราะคำตอบนี้ไม่ตอบคำถาม เพียงเพราะสิ่งที่ถูกต้องไม่ได้หมายความว่ามันเร็วกว่าหรือช้ากว่า อันหนึ่งช้ากว่าอีกอันหนึ่งเพราะสามารถทำงานกับประเภท C ในขณะที่อีกประเภทหนึ่งต้องทำงานกับ Python Objects
Dimitris Fasarakis Hilliard

1
ขอบคุณสำหรับคำอธิบาย ฉันคิดว่ามันชัดเจนว่ามันจะเร็วกว่าในการคำนวณเพียงประมาณ 12 หลักหรือตัวเลขมากกว่าการคำนวณทั้งหมด ท้ายที่สุดเหตุผลเดียวที่เราใช้การประมาณก็คือพวกมันเร็วกว่าในการคำนวณใช่ไหม
Veky
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.