Cái nào nhanh hơn trong Python: x **. 5 hoặc math.sqrt (x)?


187

Tôi đã tự hỏi điều này một thời gian. Như tiêu đề nói, cái nào nhanh hơn, chức năng thực tế hay đơn giản là nâng lên một nửa sức mạnh?

CẬP NHẬT

Đây không phải là vấn đề tối ưu hóa sớm. Đây chỉ đơn giản là một câu hỏi về cách mã cơ bản thực sự hoạt động. Lý thuyết về cách mã Python hoạt động là gì?

Tôi đã gửi cho Guido van Rossum một email vì tôi thực sự muốn biết sự khác biệt trong các phương pháp này.

Email của tôi:

Có ít nhất 3 cách để thực hiện một căn bậc hai trong Python: math.sqrt, toán tử '**' và pow (x, .5). Tôi chỉ tò mò về sự khác biệt trong việc thực hiện từng điều này. Khi nói đến hiệu quả thì tốt hơn?

Phản ứng của anh ấy:

pow và ** là tương đương; math.sqrt không hoạt động đối với các số phức và liên kết đến hàm C sqrt (). Về việc cái nào nhanh hơn, tôi không biết ...


81
Thật tuyệt vời khi Guido trả lời email.
Evan Fosmark

3
Evan, tôi đã rất ngạc nhiên khi nhận được phản hồi
Không,

11
Tôi không nghĩ đây là một câu hỏi tồi. Ví dụ: x * x nhanh hơn 10 lần so với x ** 2. Khả năng đọc là một sự thay đổi trong tình huống này, vậy tại sao không làm cách nhanh?
TM.

12
Casey, tôi với bạn về điều "tối ưu hóa sớm". :) Câu hỏi của bạn không giống như tối ưu hóa sớm đối với tôi: không có rủi ro rằng bất kỳ biến thể nào phá vỡ mã của bạn. Đó là vấn đề để biết rõ hơn những gì bạn làm (về thời gian thực hiện) khi bạn chọn pow () trên math.sqrt ().
Eric O Lebigot

8
Đây không phải là tối ưu hóa sớm, mà là tránh sự bi quan sớm (tham khảo số 28, tiêu chuẩn mã hóa C ++, A.Alexandrescu). Nếu math.sqrtlà một thói quen tối ưu hơn (như nó là) và thể hiện ý định rõ ràng hơn, nó nên luôn luôn được ưu tiên hơn x**.5. Không phải là tối ưu hóa sớm để biết những gì bạn viết và chọn phương án nhanh hơn và cung cấp nhiều mã rõ ràng hơn. Nếu vậy, bạn cần tranh luận tốt như nhau tại sao bạn sẽ chọn các lựa chọn thay thế khác.
Anh

Câu trả lời:


89

math.sqrt(x)nhanh hơn đáng kể so với x**0.5.

import math
N = 1000000
%%timeit
for i in range(N):
    z=i**.5

10 vòng, tốt nhất là 3: 156 ms mỗi vòng

%%timeit
for i in range(N):
    z=math.sqrt(i)

10 vòng, tốt nhất là 3: 91,1 ms mỗi vòng

Sử dụng Python 3.6.9 ( máy tính xách tay ).


Bây giờ tôi đã chạy nó 3 lần trên codepad.org và cả ba lần a () đều nhanh hơn b ().
Jeremy Ruten

10
Các mô-đun thời gian tiêu chuẩn là bạn của bạn. Nó tránh những cạm bẫy phổ biến khi đo thời gian thực hiện!
Eric O Lebigot

1
Dưới đây là kết quả của tập lệnh của bạn: zoltan @ host: ~ $ python2.5 p.py Took 0.183226 giây Took 0.155829 giây zoltan @ host: ~ $ python2.4 p.py Took 0.181142 giây Took 0.153742 giây zoltan @ host: ~ $ python2.6 p.py Took 0.157436 giây Mất 0,093905 giây Hệ thống đích: CPU Ubuntu Linux: Intel (R) Core (TM) 2 Duo CPU T9600 @ 2.80GHz Như bạn có thể thấy tôi nhận được các kết quả khác nhau. Theo đó, câu trả lời của bạn không chung chung.
zoli2k

2
Codepad là một dịch vụ tuyệt vời, nhưng thật kinh khủng cho hiệu năng thời gian, ý tôi là ai biết máy chủ sẽ bận rộn như thế nào tại một thời điểm nhất định. Mỗi lần chạy có khả năng cho kết quả rất khác nhau
adamJLev

1
Tôi đã thêm so sánh hiệu suất của x **. 5 so với sqrt (x) cho các trình thông dịch py32, py31, py30, py27, py26, pypy, jython, py25, py24 trên Linux. gist.github.com/783011
JFS

19
  • quy tắc tối ưu hóa đầu tiên: đừng làm điều đó
  • quy tắc thứ hai: không làm điều đó , chưa

Đây là một số thời gian (Python 2.5.2, Windows):

$ python -mtimeit -s"from math import sqrt; x = 123" "x**.5"
1000000 loops, best of 3: 0.445 usec per loop

$ python -mtimeit -s"from math import sqrt; x = 123" "sqrt(x)"
1000000 loops, best of 3: 0.574 usec per loop

$ python -mtimeit -s"import math; x = 123" "math.sqrt(x)"
1000000 loops, best of 3: 0.727 usec per loop

Thử nghiệm này cho thấy x**.5là nhanh hơn một chút sqrt(x).

Đối với Python 3.0, kết quả ngược lại:

$ \Python30\python -mtimeit -s"from math import sqrt; x = 123" "x**.5"
1000000 loops, best of 3: 0.803 usec per loop

$ \Python30\python -mtimeit -s"from math import sqrt; x = 123" "sqrt(x)"
1000000 loops, best of 3: 0.695 usec per loop

$ \Python30\python -mtimeit -s"import math; x = 123" "math.sqrt(x)"
1000000 loops, best of 3: 0.761 usec per loop

math.sqrt(x)luôn nhanh hơn x**.5trên một máy khác (Ubuntu, Python 2.6 và 3.1):

$ python -mtimeit -s"from math import sqrt; x = 123" "x**.5"
10000000 loops, best of 3: 0.173 usec per loop
$ python -mtimeit -s"from math import sqrt; x = 123" "sqrt(x)"
10000000 loops, best of 3: 0.115 usec per loop
$ python -mtimeit -s"import math; x = 123" "math.sqrt(x)"
10000000 loops, best of 3: 0.158 usec per loop
$ python3.1 -mtimeit -s"from math import sqrt; x = 123" "x**.5"
10000000 loops, best of 3: 0.194 usec per loop
$ python3.1 -mtimeit -s"from math import sqrt; x = 123" "sqrt(x)"
10000000 loops, best of 3: 0.123 usec per loop
$ python3.1 -mtimeit -s"import math; x = 123" "math.sqrt(x)"
10000000 loops, best of 3: 0.157 usec per loop

10

Có bao nhiêu căn bậc hai bạn đang thực sự thực hiện? Bạn đang cố gắng viết một số công cụ đồ họa 3D bằng Python? Nếu không, tại sao đi với mã đó là mật mã trên mã dễ đọc? Sự khác biệt về thời gian sẽ ít hơn bất kỳ ai có thể nhận thấy trong bất kỳ ứng dụng nào tôi có thể thấy trước. Tôi thực sự không có ý định đặt câu hỏi của bạn xuống, nhưng có vẻ như bạn đang đi quá xa với việc tối ưu hóa sớm.


16
Tôi không thực sự cảm thấy tôi đang tối ưu hóa sớm. Đây là một câu hỏi đơn giản về việc quyết định từ 2 phương pháp khác nhau, trung bình, sẽ nhanh hơn.
Không có

2
Kibbee: đó chắc chắn là một câu hỏi hợp lệ, nhưng tôi chia sẻ sự thất vọng của bạn về số lượng câu hỏi trên Stack Overflow ngụ ý rằng người hỏi đang thực hiện tất cả các loại tối ưu hóa sớm. Đó chắc chắn là một tỷ lệ lớn các câu hỏi được hỏi cho mọi ngôn ngữ.
Eli Courtwright

2
Math.sqrt (x) có dễ đọc hơn x ** 0,5 không? Tôi nghĩ rằng cả hai đều khá rõ ràng là căn bậc hai ... ít nhất là nếu bạn đã quen thuộc với python. Đừng gọi các toán tử python tiêu chuẩn như ** "khó hiểu" chỉ vì bạn không quen với python.
TM.

5
Tôi không nghĩ toán tử ** là khó hiểu. Tôi nghĩ rằng việc tăng thứ gì đó lên số mũ 0,5 như một phương pháp để lấy căn bậc hai trở nên khó hiểu đối với những người không theo kịp môn toán của họ.
Kibbee

13
Điều gì sẽ xảy ra nếu anh ta tạo ra một công cụ 3D bằng Python?
Chris Burt-Brown

9

Trong các điểm chuẩn vi mô này, math.sqrtsẽ chậm hơn, vì cần ít thời gian để tra cứu sqrttrong không gian tên toán học. Bạn có thể cải thiện nó một chút với

 from math import sqrt

Mặc dù sau đó, chạy một vài biến thể thông qua thời gian, cho thấy lợi thế hiệu suất nhẹ (4-5%) cho x**.5

Thật thú vị

 import math
 sqrt = math.sqrt

tăng tốc hơn nữa, trong phạm vi chênh lệch 1% về tốc độ, với rất ít ý nghĩa thống kê.


Tôi sẽ lặp lại Kibbee, và nói rằng đây có lẽ là một sự tối ưu hóa sớm.


7

Trong python 2.6, (float).__pow__() hàm sử dụng hàm C pow()và các math.sqrt()hàm sử dụng sqrt()hàm C.

Trong trình biên dịch glibc, việc thực hiện pow(x,y)khá phức tạp và nó được tối ưu hóa tốt cho các trường hợp ngoại lệ khác nhau. Ví dụ, gọi C pow(x,0.5)chỉ đơn giản là gọi sqrt()hàm.

Sự khác biệt về tốc độ sử dụng .**hoặc math.sqrtđược gây ra bởi các trình bao bọc được sử dụng xung quanh các hàm C và tốc độ phụ thuộc rất nhiều vào các cờ tối ưu / trình biên dịch C được sử dụng trên hệ thống.

Biên tập:

Đây là kết quả của thuật toán của Claudiu trên máy của tôi. Tôi đã nhận được kết quả khác nhau:

zoltan@host:~$ python2.4 p.py 
Took 0.173994 seconds
Took 0.158991 seconds
zoltan@host:~$ python2.5 p.py 
Took 0.182321 seconds
Took 0.155394 seconds
zoltan@host:~$ python2.6 p.py 
Took 0.166766 seconds
Took 0.097018 seconds

4

Để biết giá trị của nó (xem câu trả lời của Jim). Trên máy của tôi, chạy python 2.5:

PS C:\> python -m timeit -n 100000 10000**.5
100000 loops, best of 3: 0.0543 usec per loop
PS C:\> python -m timeit -n 100000 -s "import math" math.sqrt(10000)
100000 loops, best of 3: 0.162 usec per loop
PS C:\> python -m timeit -n 100000 -s "from math import sqrt" sqrt(10000)
100000 loops, best of 3: 0.0541 usec per loop

4

sử dụng mã của Claudiu, trên máy của tôi ngay cả với "từ toán nhập sqrt" x **. 5 nhanh hơn nhưng sử dụng psyco.full () sqrt (x) trở nên nhanh hơn nhiều, ít nhất 200%


3

Rất có thể math.sqrt (x), vì nó được tối ưu hóa cho root vuông.

Điểm chuẩn sẽ cung cấp cho bạn câu trả lời bạn đang tìm kiếm.


3

Ai đó đã nhận xét về "căn bậc hai Newton-Raphson nhanh" từ Quake 3 ... Tôi đã triển khai nó với ctypes, nhưng nó siêu chậm so với các phiên bản gốc. Tôi sẽ thử một vài tối ưu hóa và triển khai thay thế.

from ctypes import c_float, c_long, byref, POINTER, cast

def sqrt(num):
 xhalf = 0.5*num
 x = c_float(num)
 i = cast(byref(x), POINTER(c_long)).contents.value
 i = c_long(0x5f375a86 - (i>>1))
 x = cast(byref(i), POINTER(c_float)).contents.value

 x = x*(1.5-xhalf*x*x)
 x = x*(1.5-xhalf*x*x)
 return x * num

Đây là một phương pháp khác sử dụng struct, xuất hiện nhanh hơn khoảng 3,6 lần so với phiên bản ctypes, nhưng vẫn bằng 1/10 tốc độ của C.

from struct import pack, unpack

def sqrt_struct(num):
 xhalf = 0.5*num
 i = unpack('L', pack('f', 28.0))[0]
 i = 0x5f375a86 - (i>>1)
 x = unpack('f', pack('L', i))[0]

 x = x*(1.5-xhalf*x*x)
 x = x*(1.5-xhalf*x*x)
 return x * num

1

Kết quả của Claudiu khác với tôi. Tôi đang sử dụng Python 2.6 trên Ubuntu trên máy P4 2.4Ghz cũ ... Đây là kết quả của tôi:

>>> timeit1()
Took 0.564911 seconds
>>> timeit2()
Took 0.403087 seconds
>>> timeit1()
Took 0.604713 seconds
>>> timeit2()
Took 0.387749 seconds
>>> timeit1()
Took 0.587829 seconds
>>> timeit2()
Took 0.379381 seconds

sqrt luôn nhanh hơn đối với tôi ... Ngay cả Codepad.org NGAY dường như đồng ý rằng sqrt, trong bối cảnh địa phương, nhanh hơn ( http://codepad.org/6trzcM3j ). Codepad dường như đang chạy Python 2.5. Có lẽ họ đã sử dụng 2.4 hoặc cũ hơn khi lần đầu tiên Claudiu trả lời?

Trong thực tế, ngay cả khi sử dụng math.sqrt (i) thay cho arg (i), tôi vẫn nhận được thời gian tốt hơn cho sqrt. Trong trường hợp này, timeit2 () mất từ ​​0,53 đến 0,55 giây trên máy của tôi, vẫn tốt hơn con số 0,56-0,60 từ timeit1.

Tôi muốn nói, trên Python hiện đại, hãy sử dụng math.sqrt và chắc chắn đưa nó vào bối cảnh cục bộ, với somevar = math.sqrt hoặc với toán học nhập sqrt.


1

Điều Pythonic để tối ưu hóa là khả năng đọc. Đối với điều này tôi nghĩ rằng sử dụng rõ ràng của sqrtchức năng là tốt nhất. Đã nói rằng, hãy điều tra hiệu suất nào.

Tôi đã cập nhật mã của Claudiu cho Python 3 và cũng không thể tối ưu hóa các tính toán (điều mà một trình biên dịch Python tốt có thể làm trong tương lai):

from sys import version
from time import time
from math import sqrt, pi, e

print(version)

N = 1_000_000

def timeit1():
  z = N * e
  s = time()
  for n in range(N):
    z += (n * pi) ** .5 - z ** .5
  print (f"Took {(time() - s):.4f} seconds to calculate {z}")

def timeit2():
  z = N * e
  s = time()
  for n in range(N):
    z += sqrt(n * pi) - sqrt(z)
  print (f"Took {(time() - s):.4f} seconds to calculate {z}")

def timeit3(arg=sqrt):
  z = N * e
  s = time()
  for n in range(N):
    z += arg(n * pi) - arg(z)
  print (f"Took {(time() - s):.4f} seconds to calculate {z}")

timeit1()
timeit2()
timeit3()

Kết quả khác nhau, nhưng một đầu ra mẫu là:

3.6.6 (default, Jul 19 2018, 14:25:17) 
[GCC 8.1.1 20180712 (Red Hat 8.1.1-5)]
Took 0.3747 seconds to calculate 3130485.5713865166
Took 0.2899 seconds to calculate 3130485.5713865166
Took 0.2635 seconds to calculate 3130485.5713865166

Hãy thử nó.


0

Vấn đề SQRMINSUM tôi đã giải quyết gần đây yêu cầu tính toán căn bậc hai liên tục trên một tập dữ liệu lớn. 2 lần gửi cũ nhất trong lịch sử của tôi , trước khi tôi thực hiện các tối ưu hóa khác, chỉ khác nhau bằng cách thay ** 0,5 bằng sqrt (), do đó giảm thời gian chạy từ 3,74 xuống 0,51 giây trong PyPy. Con số này gần gấp đôi so với mức cải thiện 400% vốn đã rất lớn mà Claudiu đo được.


0

Tất nhiên, nếu một người đang xử lý bằng chữ và cần một giá trị không đổi, thời gian chạy Python có thể tính toán trước giá trị tại thời gian biên dịch, nếu nó được viết bằng toán tử - không cần phải lập hồ sơ cho từng phiên bản trong trường hợp này:

In [77]: dis.dis(a)                                                                                                                       
  2           0 LOAD_CONST               1 (1.4142135623730951)
              2 RETURN_VALUE

In [78]: def a(): 
    ...:     return 2 ** 0.5 
    ...:                                                                                                                                  

In [79]: import dis                                                                                                                       

In [80]: dis.dis(a)                                                                                                                       
  2           0 LOAD_CONST               1 (1.4142135623730951)
              2 RETURN_VALUE

-3

Điều gì sẽ nhanh hơn nữa là nếu bạn đã vào math.py và sao chép hàm "sqrt" vào chương trình của bạn. Phải mất thời gian để chương trình của bạn tìm math.txt, sau đó mở nó, tìm chức năng bạn đang tìm kiếm và sau đó đưa nó trở lại chương trình của bạn. Nếu chức năng đó nhanh hơn ngay cả với các bước "tra cứu", thì chính chức năng đó phải cực kỳ nhanh. Có lẽ sẽ cắt giảm thời gian của bạn một nửa. Tóm tắt:

  1. Truy cập math.txt
  2. Tìm hàm "sqrt"
  3. Sao chép nó
  4. Dán chức năng vào chương trình của bạn như là công cụ tìm sqrt.
  5. Thời gian đó.

1
Điều đó sẽ không làm việc; xem stackoverflow.com/q/18857355/3004881 . Cũng lưu ý trích dẫn trong câu hỏi ban đầu nói rằng đó là một liên kết đến chức năng C. Ngoài ra, làm thế nào có thể sao chép mã nguồn của hàm khác với from math import sqrt?
Dan Getz

Tôi sẽ không nói rằng chỉ cần làm rõ chính xác sự khác biệt trong cách gọi hai chức năng.
PyGuy
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.