Tại sao x ** 4.0 nhanh hơn x ** 4 trong Python 3?


164

Tại sao x**4.0nhanh hơn x**4? Tôi đang sử dụng 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

Tôi đã thử thay đổi sức mạnh mà tôi đã tăng lên để xem nó hoạt động như thế nào, và ví dụ nếu tôi tăng x lên sức mạnh của 10 hoặc 16 thì nó nhảy từ 30 lên 35, nhưng nếu tôi tăng lên 10,0 như một chiếc phao, thì nó chỉ di chuyển khoảng 24,1 ~ 4.

Tôi đoán nó có liên quan đến chuyển đổi float và sức mạnh của 2 có thể, nhưng tôi thực sự không biết.

Tôi nhận thấy rằng trong cả hai trường hợp, quyền hạn của 2 đều nhanh hơn, tôi đoán vì những tính toán đó là bản địa / dễ dàng hơn cho trình thông dịch / máy tính. Tuy nhiên, với phao nó gần như không di chuyển. 2.0 => 24.1~4 & 128.0 => 24.1~4 nhưng 2 => 29 & 128 => 62


TigerhawkT3 chỉ ra rằng nó không xảy ra bên ngoài vòng lặp. Tôi đã kiểm tra và tình huống chỉ xảy ra (từ những gì tôi đã thấy) khi căn cứ đang được nâng lên. Bất cứ ý tưởng về điều đó?


11
Đối với những gì nó có giá trị: Python 2.7.13 đối với tôi là hệ số 2 ~ 3 nhanh hơn hiển thị hành vi nghịch đảo: số mũ nguyên tố nhanh hơn số mũ dấu phẩy động.

4
@Evert yup, tôi có 14 usec cho x**4.0và 3.9 cho x**4.
dabadaba

Câu trả lời:


161

Tại sao x**4.0 nhanh hơn x**4trong Python 3 * ?

Các intđối tượng Python 3 là một đối tượng chính thức được thiết kế để hỗ trợ kích thước tùy ý; do thực tế đó, chúng được xử lý như vậy ở cấp độ C (xem cách tất cả các biến được khai báo là PyLongObject *kiểu trong long_pow). Điều này cũng làm cho phép lũy thừa của chúng trở nên phức tạptẻ nhạt hơn rất nhiều vì bạn cần phải chơi xung quanh với ob_digitmảng mà nó sử dụng để biểu thị giá trị của nó để thực hiện nó. ( Nguồn cho người dũng cảm. - Xem: Tìm hiểu phân bổ bộ nhớ cho số nguyên lớn trong Python để biết thêm về PyLongObjects.)

floatNgược lại, các đối tượng Python có thể được chuyển đổi thành doubleloại C (bằng cách sử dụng PyFloat_AsDouble) và các hoạt động có thể được thực hiện bằng các loại gốc đó . Này là rất tốt bởi vì, sau khi kiểm tra cho cạnh các trường hợp có liên quan, nó cho phép Python để sử dụng các nền tảngpow ( C pow, có nghĩa là ) để xử lý các lũy thừa thực tế:

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

ở đâu iviwlà bản gốc của chúng tôi PyFloatObjectlà C doubles.

Đối với những gì nó có giá trị: Python 2.7.13đối với tôi là một yếu tố 2~3nhanh hơn và cho thấy hành vi nghịch đảo.

Thực tế trước đây cũng giải thích sự khác biệt giữa Python 2 và 3 vì vậy, tôi nghĩ tôi cũng sẽ giải quyết nhận xét này vì nó rất thú vị.

Trong Python 2, bạn đang sử dụng intđối tượng cũ khác với intđối tượng trong Python 3 (tất cả intcác đối tượng trong 3.x là PyLongObjectloại). Trong Python 2, có một sự phân biệt phụ thuộc vào giá trị của đối tượng (hoặc, nếu bạn sử dụng hậu tố L/l):

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

Các <type 'int'>bạn thấy ở đây làm điều tương tự floats làm , nó được một cách an toàn chuyển đổi thành một C long khi lũy thừa được thực hiện trên nó (The int_powcũng gợi ý trình biên dịch để đưa 'em trong một thanh ghi nếu nó có thể làm như vậy, để có thể tạo sự khác biệt) :

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

điều này cho phép đạt được tốc độ tốt.

Để xem mức độ chậm chạp <type 'long'>so với <type 'int'>s, nếu bạn gói xtên trong một longcuộc gọi trong Python 2 (về cơ bản buộc nó phải sử dụng long_pownhư trong Python 3), tốc độ tăng sẽ biến mất:

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

Xin lưu ý rằng, mặc dù một đoạn biến đổi intthành longtrong khi đoạn kia không (như được chỉ ra bởi @pydsinger), dàn diễn viên này không phải là lực lượng đóng góp đằng sau sự chậm lại. Việc thực hiện long_powlà. (Thời gian báo cáo chỉ long(x)để xem).

[...] Nó không xảy ra bên ngoài vòng lặp. [...] Có ý kiến ​​gì về điều đó không?

Đây là trình tối ưu hóa lổ nhìn trộm của CPython gấp các hằng số cho bạn. Bạn cũng có được thời gian chính xác như nhau vì không có tính toán thực tế để tìm kết quả của lũy thừa, chỉ tải các giá trị:

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

Mã byte giống hệt nhau được tạo ra '4 ** 4.'với sự khác biệt duy nhất là LOAD_CONSTtải float 256.0thay vì 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

Vì vậy, thời gian là giống hệt nhau.


* Tất cả những điều trên chỉ áp dụng cho CPython, triển khai tham chiếu của Python. Các triển khai khác có thể thực hiện khác nhau.


Dù là gì đi nữa, nó liên quan đến vòng lặp trên a range, vì chỉ có thời gian **hoạt động tự nó không mang lại sự khác biệt giữa số nguyên và số float.
TigerhawkT3

Sự khác biệt chỉ xuất hiện khi tìm kiếm một biến số ( 4**4cũng nhanh như vậy 4**4.0) và câu trả lời này hoàn toàn không chạm vào điều đó.
TigerhawkT3

1
Nhưng, hằng số sẽ được gấp lại @ TigerhawkT3 ( dis(compile('4 ** 4', '', 'exec'))) vì vậy thời gian nên hoàn toàn giống nhau.
Dimitris Fasarakis Hilliard

Thời gian cuối cùng của bạn dường như không hiển thị những gì bạn nói. long(x)**2.vẫn nhanh hơn long(x)**2hệ số 4-5. (Tuy nhiên, không phải là một trong những
kẻ hạ bệ

3
@ mbomb007 việc loại bỏ <type 'long'>loại trong Python 3 có thể được giải thích bằng những nỗ lực được thực hiện để đơn giản hóa ngôn ngữ. Nếu bạn có thể có một loại để đại diện cho số nguyên thì nó dễ quản lý hơn hai loại (và lo lắng về việc chuyển đổi từ loại này sang loại khác khi cần thiết, người dùng bị nhầm lẫn, v.v.). Tốc độ đạt được là thứ yếu. Phần lý do của PEP 237 cũng cung cấp một số cái nhìn sâu sắc hơn.
Dimitris Fasarakis Hilliard

25

Nếu chúng ta nhìn vào mã byte, chúng ta có thể thấy rằng các biểu thức hoàn toàn giống nhau. Sự khác biệt duy nhất là một loại hằng số sẽ là một đối số BINARY_POWER. Vì vậy, chắc chắn là do intđược chuyển đổi thành số dấu phẩy động xuống dòng.

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

Cập nhật: chúng ta hãy xem các đối tượng / trừu tượng trong mã nguồn CPython:

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

PyNumber_Powercác cuộc gọi ternary_op, quá dài để dán ở đây, vì vậy đây là liên kết .

Nó gọi nb_powervị trí của x, đi qua ynhư một đối số.

Cuối cùng, float_pow()ở dòng 686 của Object / floatobject.c, chúng ta thấy rằng các đối số được chuyển đổi thành C doublengay trước khi hoạt động thực tế:

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 Tôi tin rằng đó là do việc gấp liên tục.
Dimitris Fasarakis Hilliard

2
Tôi nghĩ rằng hàm ý rằng có một chuyển đổi và chúng không được xử lý khác nhau theo dòng "chắc chắn nhất" là một chút kéo dài mà không có nguồn.
miradulo

1
@Mitch - Đặc biệt, trong mã cụ thể này, không có sự khác biệt về thời gian thực hiện cho hai thao tác đó. Sự khác biệt chỉ phát sinh với vòng lặp của OP. Câu trả lời này đang nhảy đến kết luận.
TigerhawkT3

2
Tại sao bạn chỉ nhìn vào float_powkhi nó thậm chí không chạy cho trường hợp chậm?
user2357112 hỗ trợ Monica

2
@ TigerhawkT3: 4**44**4.0được xếp lại liên tục. Đó là một hiệu ứng hoàn toàn riêng biệt.
user2357112 hỗ trợ Monica

-1

Bởi vì một cái là chính xác, cái khác là gần đúng.

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

Tôi không biết lý do tại sao điều đó hạ cấp xuống nhưng tôi đã làm vì câu trả lời này không trả lời câu hỏi. Chỉ vì một cái gì đó là chính xác không có nghĩa là nó nhanh hơn hoặc chậm hơn. Một loại chậm hơn loại kia vì một loại có thể hoạt động với các loại C trong khi loại kia phải hoạt động với các Đối tượng Python.
Dimitris Fasarakis Hilliard

1
Cảm ơn đã giải thích. Chà, tôi thực sự nghĩ rằng rõ ràng là nhanh hơn khi chỉ tính xấp xỉ một số đến 12 chữ số, hơn là tính toán chính xác tất cả chúng. Rốt cuộc, lý do duy nhất tại sao chúng ta sử dụng xấp xỉ là chúng nhanh hơn để tính toán, phải không?
Veky
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.