Python: x **. 5 ใดเร็วกว่าหรือ math.sqrt (x)


187

ฉันเคยสงสัยเรื่องนี้มาระยะหนึ่งแล้ว ตามที่ชื่อบอกว่าจะเร็วกว่าฟังก์ชั่นจริงหรือเพียงแค่เพิ่มกำลังครึ่งหนึ่ง?

UPDATE

นี่ไม่ใช่เรื่องของการเพิ่มประสิทธิภาพก่อนวัยอันควร นี่เป็นเพียงคำถามว่าโค้ดอ้างอิงทำงานอย่างไร ทฤษฎีของ Python code ทำงานอย่างไร

ฉันส่งอีเมลถึง Guido van Rossum เพราะฉันต้องการทราบความแตกต่างของวิธีการเหล่านี้

อีเมลของฉัน:

มีอย่างน้อย 3 วิธีในการทำรากที่สองใน Python: math.sqrt, ตัวดำเนินการ '**' และ pow (x, .5) ฉันแค่อยากรู้เกี่ยวกับความแตกต่างในการใช้งานของแต่ละสิ่งเหล่านี้ เมื่อพูดถึงประสิทธิภาพซึ่งดีกว่า

คำตอบของเขา:

pow และ ** เทียบเท่ากัน math.sqrt ไม่ทำงานสำหรับตัวเลขที่ซับซ้อนและลิงก์ไปยังฟังก์ชัน C sqrt () เป็นที่หนึ่งที่เร็วกว่าฉันไม่มีความคิด ...


81
นั่นยอดเยี่ยมที่กุยโด้ตอบกลับอีเมล
Evan Fosmark

3
อีวานฉันรู้สึกประหลาดใจที่ผมได้รับการตอบสนอง
Nope

11
ฉันไม่คิดว่านี่เป็นคำถามที่ไม่ดี ตัวอย่างเช่น x * x นั้นเร็วกว่า 10 เท่าเต็ม x ** 2. ความสามารถในการอ่านคือการโยนในสถานการณ์นี้ดังนั้นทำไมไม่ทำวิธีที่รวดเร็ว?
TM

12
Casey ฉันอยู่กับคุณในสิ่งที่ "การเพิ่มประสิทธิภาพก่อนวัย" :) คำถามของคุณดูเหมือนการปรับให้เหมาะสมก่อนกำหนดกับฉัน: ไม่มีความเสี่ยงใด ๆ ที่ทำให้รหัสของคุณแตกหัก มันเป็นเรื่องของการรู้มากขึ้นว่าคุณทำอะไร (ในแง่ของเวลาดำเนินการ) เมื่อคุณเลือก pow () มากกว่า math.sqrt ()
Eric O Lebigot

8
นี่ไม่ใช่การเพิ่มประสิทธิภาพก่อนวัยอันควร แต่ควรหลีกเลี่ยงการทำให้เป็นแง่ร้ายก่อนวัยอันควร (อ้างอิงหมายเลข 28, มาตรฐานการเข้ารหัส C ++, A.Alexandrescu) หากmath.sqrtเป็นประจำเพิ่มประสิทธิภาพมากขึ้น (มันเป็น) x**.5และเป็นการแสดงออกถึงความตั้งใจมากขึ้นอย่างเห็นได้ชัดก็ควรจะต้องการมากกว่า ไม่ใช่การเพิ่มประสิทธิภาพก่อนวัยอันควรที่จะรู้ว่าคุณเขียนอะไรและเลือกทางเลือกที่เร็วกว่าและให้รหัสที่ชัดเจนยิ่งขึ้น ถ้าเป็นเช่นนั้นคุณต้องเถียงกันพอ ๆ กันว่าทำไมคุณถึงเลือกทางเลือกอื่น
swalog

คำตอบ:


89

math.sqrt(x)x**0.5อย่างมีนัยสำคัญได้เร็วกว่า

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

10 ลูปที่ดีที่สุดคือ 3: 156 ms ต่อลูป

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

10 ลูปที่ดีที่สุดคือ 3: 91.1 ms ต่อลูป

ใช้ Python 3.6.9 ( โน้ตบุ๊ก )


ตอนนี้ฉันรันมัน 3 ครั้งบน codepad.org และทั้งสามครั้ง () นั้นเร็วกว่า b () มาก
Jeremy Ruten

10
โมดูล timeit มาตรฐานคือเพื่อนของคุณ มันหลีกเลี่ยงข้อผิดพลาดทั่วไปเมื่อมันมาถึงการวัดเวลาดำเนินการ!
Eric O Lebigot

1
นี่คือผลลัพธ์ของสคริปต์ของคุณ: zoltan @ host: ~ $ python2.5 p.py ใช้ 0.183226 วินาทีใช้เวลา 0.155829 วินาที zoltan @ host: ~ $ python2.4 p.py เอา 0.181142 วินาทีใช้เวลา 0.153742 วินาที zoltan @ host: ~ $ python2.6 p.py ใช้ 0.157436 วินาทีใช้เวลา 0.093905 วินาทีระบบเป้าหมาย: Ubuntu Linux CPU: Intel (R) Core (TM) 2 Duo CPU T9600 @ 2.80GHz อย่างที่คุณเห็นฉันได้ผลลัพธ์ที่แตกต่างกัน ตามคำตอบของคุณไม่ใช่คำทั่วไป
zoli2k

2
Codepad เป็นบริการที่ยอดเยี่ยม แต่น่ากลัวสำหรับประสิทธิภาพเวลาฉันหมายความว่าใครจะรู้ว่าเซิร์ฟเวอร์จะยุ่งแค่ไหนในช่วงเวลาหนึ่ง การวิ่งแต่ละครั้งอาจให้ผลลัพธ์ที่แตกต่างกันมาก
adamJLev

1
ฉันได้เพิ่มการเปรียบเทียบประสิทธิภาพของ x **. 5 vs sqrt (x) สำหรับ py32, py31, py30, py27, py26, pypy, jython, py25, py24 ล่ามบน Linux gist.github.com/783011
jfs

19
  • กฎข้อแรกของการเพิ่มประสิทธิภาพ: อย่าทำ
  • กฎข้อที่สอง: ไม่ได้ทำมันยัง

นี่คือการกำหนดเวลาบางส่วน (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

การทดสอบนี้แสดงให้เห็นว่าจะเร็วกว่าเล็กน้อยx**.5sqrt(x)

สำหรับ Python 3.0 ผลลัพธ์จะตรงกันข้าม:

$ \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)จะเร็วกว่าx**.5บนเครื่องอื่นเสมอ(Ubuntu, Python 2.6 และ 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

คุณกำลังทำการแสดงรากที่สองจริง ๆ จำนวนเท่าใด คุณพยายามเขียนเอ็นจิ้นกราฟิก 3 มิติใน Python หรือไม่? ถ้าไม่เป็นเช่นนั้นไปด้วยรหัสที่เป็นความลับมากกว่ารหัสที่อ่านง่าย? ความแตกต่างของเวลาจะน้อยกว่าที่ทุกคนจะสังเกตเห็นได้ในแอปพลิเคชันใด ๆ ที่ฉันคาดไม่ถึง ฉันไม่ได้ตั้งใจที่จะวางคำถามของคุณ แต่ดูเหมือนว่าคุณจะไปไกลเกินไปกับการเพิ่มประสิทธิภาพก่อนวัยอันควร


16
ฉันไม่รู้สึกว่าฉันได้ทำการเพิ่มประสิทธิภาพก่อนเวลาอันควร มันเป็นคำถามที่ง่ายกว่าในการตัดสินใจจาก 2 วิธีที่ต่างกันซึ่งโดยเฉลี่ยจะเร็วกว่า
Nope

2
Kibbee: เป็นคำถามที่ถูกต้องแน่นอน แต่ฉันแบ่งปันความกลัวของคุณตามจำนวนคำถามใน Stack Overflow ที่บ่งบอกว่าผู้ถามกำลังทำการปรับให้เหมาะสมก่อนเวลาทุกประเภท เป็นคำถามที่ถูกถามทุกภาษาเป็นอย่างมาก
Eli Courtwright

2
math.sqrt (x) อ่านง่ายกว่า x ** 0.5 หรือไม่ ฉันคิดว่าพวกเขาทั้งคู่เห็นได้ชัดว่ารากที่สอง ... อย่างน้อยถ้าคุณคุ้นเคยกับงูหลาม อย่าเรียกตัวดำเนินการ python มาตรฐานเช่น ** "cryptic" เพียงเพราะคุณไม่คุ้นเคยกับ python
TM

5
ฉันไม่คิดว่าตัวดำเนินการ ** เป็นความลับ ฉันคิดว่าการยกบางสิ่งบางอย่างให้กับเลขยกกำลัง 0.5 เป็นวิธีการให้สแควร์รูทเป็นความลับเล็ก ๆ น้อย ๆ ให้กับผู้ที่ไม่เข้าใจคณิตศาสตร์ของพวกเขา
Kibbee

13
ถ้าเขาสร้างเอ็นจิ้น 3 มิติใน Python ล่ะ
Chris Burt-Brown

9

ในการวัดขนาดเล็กเหล่านี้math.sqrtจะช้าลงเนื่องจากใช้เวลาเล็กน้อยในการค้นหาsqrtในเนมสเปซคณิตศาสตร์ คุณสามารถปรับปรุงมันเล็กน้อยด้วย

 from math import sqrt

แม้ว่าจะมีการเปลี่ยนแปลงเล็กน้อยตามช่วงเวลา แต่ก็แสดงความได้เปรียบด้านประสิทธิภาพเล็กน้อย (4-5%) สำหรับ x**.5

ที่น่าสนใจทำ

 import math
 sqrt = math.sqrt

เร่งความเร็วให้มากขึ้นเพื่อให้ได้ความเร็วที่แตกต่างภายใน 1% โดยมีนัยสำคัญทางสถิติน้อยมาก


ฉันจะทำซ้ำ Kibbee และบอกว่านี่อาจเป็นการเพิ่มประสิทธิภาพก่อนวัยอันควร


7

ใน python 2.6 (float).__pow__() ฟังก์ชันใช้ฟังก์ชัน C pow()และmath.sqrt()ฟังก์ชันใช้sqrt()ฟังก์ชันC

ในคอมไพเลอร์ glibc การใช้งานpow(x,y)นั้นค่อนข้างซับซ้อนและได้รับการปรับให้เหมาะสมสำหรับกรณีพิเศษต่างๆ ตัวอย่างเช่นการเรียก C pow(x,0.5)เพียงแค่เรียกใช้sqrt()ฟังก์ชัน

ความแตกต่างของความเร็วในการใช้งาน.**หรือmath.sqrtเกิดจากเครื่องห่อหุ้มที่ใช้รอบฟังก์ชั่น C และความเร็วนั้นขึ้นอยู่กับการปรับแต่งค่าสถานะแฟล็ก / C คอมไพเลอร์ที่ใช้ในระบบ

แก้ไข:

นี่คือผลลัพธ์ของอัลกอริทึมของ Claudiu ในเครื่องของฉัน ฉันได้ผลลัพธ์ที่แตกต่าง:

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

สำหรับสิ่งที่คุ้มค่า (ดูคำตอบของ Jim) บนเครื่องของฉันใช้ 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

ใช้รหัสของ Claudiu บนเครื่องของฉันแม้จะมี "จาก math sqrt import" x **. 5 เร็วขึ้น แต่ใช้ psyco.full () sqrt (x) กลายเป็นเร็วขึ้นอย่างน้อย 200%


3

math.sqrt (x) เป็นไปได้มากที่สุดเพราะมันเหมาะสำหรับการรูทสแควร์

เกณฑ์มาตรฐานจะให้คำตอบที่คุณต้องการ


3

มีคนแสดงความคิดเห็นเกี่ยวกับ "รากที่รวดเร็วของนิวตัน - ราฟสัน" จาก Quake 3 ... ฉันติดตั้ง ctypes แต่มันช้ามากเมื่อเทียบกับเวอร์ชั่นดั้งเดิม ฉันจะลองเพิ่มประสิทธิภาพเล็กน้อยและการใช้งานทางเลือก

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

นี่เป็นอีกวิธีการหนึ่งที่ใช้ struct ออกมาเร็วกว่ารุ่น ctypes ประมาณ 3.6 เท่า แต่ยังคงความเร็ว 1/10 ของ 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

ผลลัพธ์ของ Claudiu นั้นแตกต่างจากของฉัน ฉันใช้ Python 2.6 บน Ubuntu บนเครื่อง P4 2.4Ghz รุ่นเก่า ... นี่คือผลลัพธ์ของฉัน:

>>> 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 นั้นเร็วกว่าสำหรับฉันเสมอ ... แม้แต่ Codepad.org ตอนนี้ดูเหมือนจะยอมรับว่า sqrt ในบริบทท้องถิ่นนั้นเร็วกว่า ( http://codepad.org/6trzcM3j ) Codepad ดูเหมือนว่าจะใช้งาน Python 2.5 ในปัจจุบัน บางทีพวกเขากำลังใช้ 2.4 หรือเก่ากว่าเมื่อ Claudiu ตอบครั้งแรก?

ในความเป็นจริงแม้แต่การใช้ math.sqrt (i) แทน arg (i) ฉันยังคงมีเวลาที่ดีกว่าสำหรับ sqrt ในกรณีนี้ timeit2 () ใช้เวลาระหว่าง 0.53 ถึง 0.55 วินาทีในเครื่องของฉันซึ่งยังดีกว่าตัวเลข 0.56-0.60 จาก timeit1

ฉันจะบอกว่าใน Python ที่ทันสมัยใช้ math.sqrt และนำไปสู่บริบทท้องถิ่นอย่างแน่นอนด้วย somevar = math.sqrt หรือจาก sqrt import import ทางคณิตศาสตร์


1

สิ่งที่จะปรับให้เหมาะสมสำหรับ Pythonic สามารถอ่านได้ สำหรับเรื่องนี้ฉันคิดว่าการใช้sqrtฟังก์ชั่นที่ชัดเจนดีที่สุด ต้องบอกว่ามาตรวจสอบประสิทธิภาพกันดีกว่า

ฉันอัปเดตโค้ดของ Claudiu สำหรับ Python 3 และทำให้มันเป็นไปไม่ได้ที่จะปรับการคำนวณให้เหมาะสม

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

ผลลัพธ์แตกต่างกัน แต่ผลลัพธ์ตัวอย่างคือ:

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

ลองด้วยตัวคุณเอง


0

ปัญหาSQRMINSUM ที่ฉันแก้ไขเมื่อไม่นานมานี้ต้องการการคำนวณสแควร์รูทซ้ำ ๆ บนชุดข้อมูลขนาดใหญ่ การส่ง 2 ครั้งที่เก่าแก่ที่สุดในประวัติศาสตร์ของฉันก่อนที่ฉันจะทำการเพิ่มประสิทธิภาพอื่น ๆ จะแตกต่างกันโดยการแทนที่ ** 0.5 ด้วย sqrt () ซึ่งช่วยลดรันไทม์จาก 3.74s เป็น 0.51s ใน PyPy นี่เป็นเกือบสองเท่าของการปรับปรุงที่ยิ่งใหญ่ 400% ที่ Claudiu วัด


0

แน่นอนหากมีการติดต่อกับตัวอักษรและต้องการค่าคงที่ Python runtime สามารถคำนวณค่าล่วงหน้า ณ เวลารวบรวมถ้ามันถูกเขียนด้วยตัวดำเนินการ - ไม่จำเป็นต้องทำโปรไฟล์แต่ละเวอร์ชันในกรณีนี้:

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

สิ่งที่จะเร็วยิ่งขึ้นคือถ้าคุณเข้าสู่ math.py และคัดลอกฟังก์ชัน "sqrt" ลงในโปรแกรมของคุณ โปรแกรมของคุณใช้เวลาในการค้นหา math.py จากนั้นเปิดขึ้นค้นหาฟังก์ชันที่คุณต้องการแล้วนำกลับมาที่โปรแกรมของคุณ หากฟังก์ชั่นนั้นเร็วขึ้นแม้จะมีขั้นตอน "ค้นหา" แสดงว่าฟังก์ชั่นนั้นจะต้องเร็วมาก อาจจะลดเวลาของคุณครึ่ง สรุป:

  1. ไปที่ math.py
  2. ค้นหาฟังก์ชัน "sqrt"
  3. คัดลอก
  4. วางฟังก์ชั่นลงในโปรแกรมของคุณเป็นโปรแกรมค้นหา sqrt
  5. เวลามัน

1
นั่นจะไม่ทำงาน ดูstackoverflow.com/q/18857355/3004881 นอกจากนี้ให้สังเกตเครื่องหมายคำพูดในคำถามเดิมที่ระบุว่าเป็นลิงก์ไปยังฟังก์ชัน C นอกจากนี้วิธีการคัดลอกรหัสที่มาของฟังก์ชั่นจะแตกต่างจากfrom math import sqrt?
Dan Getz

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