ฉันจะตั้งค่ารหัส Python ทีละบรรทัดได้อย่างไร


116

ฉันใช้ cProfile เพื่อสร้างโปรไฟล์โค้ดของฉันและมันก็ใช้งานได้ดี ฉันยังใช้gprof2dot.pyเพื่อดูผลลัพธ์ (ทำให้ชัดเจนขึ้นเล็กน้อย)

อย่างไรก็ตาม cProfile (และ Python profilers อื่น ๆ ส่วนใหญ่ที่ฉันเคยเห็นจนถึงตอนนี้) ดูเหมือนจะเป็นโปรไฟล์ที่ระดับการเรียกใช้ฟังก์ชันเท่านั้น สิ่งนี้ทำให้เกิดความสับสนเมื่อมีการเรียกฟังก์ชันบางอย่างจากที่ต่างๆ - ฉันไม่รู้ว่าการโทร # 1 หรือโทร # 2 ใช้เวลาส่วนใหญ่ สิ่งนี้จะแย่ลงไปอีกเมื่อฟังก์ชั่นที่เป็นปัญหามีความลึกหกระดับซึ่งเรียกมาจากที่อื่นอีกเจ็ดแห่ง

ฉันจะสร้างโปรไฟล์ทีละบรรทัดได้อย่างไร

แทนสิ่งนี้:

function #12, total time: 2.0s

ฉันต้องการเห็นสิ่งนี้:

function #12 (called from somefile.py:102) 0.5s
function #12 (called from main.py:12) 1.5s

cProfile จะแสดงจำนวนเวลาทั้งหมดที่ "โอน" ไปยังผู้ปกครอง แต่การเชื่อมต่อนี้จะหายไปอีกครั้งเมื่อคุณมีเลเยอร์จำนวนมากและการโทรที่เชื่อมต่อกัน

ตามหลักการแล้วฉันชอบที่จะมี GUI ที่จะแยกวิเคราะห์ข้อมูลจากนั้นแสดงไฟล์ต้นฉบับของฉันพร้อมเวลาทั้งหมดที่กำหนดให้กับแต่ละบรรทัด สิ่งนี้:

main.py:

a = 1 # 0.0s
result = func(a) # 0.4s
c = 1000 # 0.0s
result = func(c) # 5.0s

จากนั้นฉันจะสามารถคลิกที่การโทร "func (c)" ครั้งที่สองเพื่อดูว่าอะไรใช้เวลาในการโทรนั้นโดยแยกจากการโทร "func (a)"

มันสมเหตุสมผลไหม มีไลบรารีการทำโปรไฟล์ใดบ้างที่รวบรวมข้อมูลประเภทนี้ มีเครื่องมือที่ยอดเยี่ยมที่ฉันพลาดไปหรือไม่?


2
pstats.print_callersฉันเดาว่าคุณจะสนใจใน ตัวอย่างคือที่นี่
มูฮัมหมัดอัลคารูรี

มูฮัมหมัดนั่นเป็นประโยชน์อย่างแน่นอน! อย่างน้อยก็แก้ปัญหาหนึ่ง: การแยกการเรียกใช้ฟังก์ชันขึ้นอยู่กับที่มา ฉันคิดว่าคำตอบของ Joe Kington ใกล้เคียงกับเป้าหมายของฉันมากขึ้น แต่ print_callers () ทำให้ฉันอยู่ที่นั่นได้ครึ่งทาง ขอบคุณ!
rocketmonkeys

คำตอบ:


120

ฉันเชื่อว่านั่นคือสิ่งที่line_profiler ของ Robert Kernมีไว้สำหรับ จากลิงค์:

File: pystone.py
Function: Proc2 at line 149
Total time: 0.606656 s

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
   149                                           @profile
   150                                           def Proc2(IntParIO):
   151     50000        82003      1.6     13.5      IntLoc = IntParIO + 10
   152     50000        63162      1.3     10.4      while 1:
   153     50000        69065      1.4     11.4          if Char1Glob == 'A':
   154     50000        66354      1.3     10.9              IntLoc = IntLoc - 1
   155     50000        67263      1.3     11.1              IntParIO = IntLoc - IntGlob
   156     50000        65494      1.3     10.8              EnumLoc = Ident1
   157     50000        68001      1.4     11.2          if EnumLoc == Ident1:
   158     50000        63739      1.3     10.5              break
   159     50000        61575      1.2     10.1      return IntParIO

หวังว่าจะช่วยได้!


10
line_profiler ทำงานกับ Python 3 ได้หรือไม่ ฉันไม่สามารถรับข้อมูลได้เลย
user1251007

3
line_profiler ไม่แสดงยอดฮิตและเวลาให้ฉัน ใครช่วยบอกทีว่าทำไม? แล้วแก้ยังไง?
I159

6
นี่คือมัณฑนากรที่ผมเขียน: gist.github.com/kylegibson/6583590 หากคุณกำลังเรียกใช้ nosetests อย่าลืมใช้ตัวเลือก -s เพื่อให้พิมพ์ stdout ทันที
Kyle Gibson

5
สคริปต์ python ที่สร้างผลลัพธ์นี้มีลักษณะอย่างไร? import line_profiler;แล้ว?
Zhubarb

10
ใครสามารถแสดงวิธีการใช้ห้องสมุดนี้จริง? Readme สอนวิธีการติดตั้งและตอบคำถามที่พบบ่อยต่างๆ แต่ไม่ได้กล่าวถึงวิธีการใช้งานหลังจากติดตั้ง pip ..
cryanbhu

47

คุณยังสามารถใช้pprofile ( pypi ) หากคุณต้องการสร้างโปรไฟล์การดำเนินการทั้งหมดไม่จำเป็นต้องแก้ไขซอร์สโค้ด คุณยังสามารถกำหนดโปรไฟล์ส่วนย่อยของโปรแกรมขนาดใหญ่ได้สองวิธี:

  • สลับการทำโปรไฟล์เมื่อไปถึงจุดที่ระบุในโค้ดเช่น:

    import pprofile
    profiler = pprofile.Profile()
    with profiler:
        some_code
    # Process profile content: generate a cachegrind file and send it to user.
    
    # You can also write the result to the console:
    profiler.print_stats()
    
    # Or to a file:
    profiler.dump_stats("/tmp/profiler_stats.txt")
  • สลับการทำโปรไฟล์แบบอะซิงโครนัสจาก call stack (ต้องการวิธีทริกเกอร์โค้ดนี้ในแอปพลิเคชันที่พิจารณาเช่นตัวจัดการสัญญาณหรือเธรดผู้ปฏิบัติงานที่มี) โดยใช้การทำโปรไฟล์ทางสถิติ:

    import pprofile
    profiler = pprofile.StatisticalProfile()
    statistical_profiler_thread = pprofile.StatisticalThread(
        profiler=profiler,
    )
    with statistical_profiler_thread:
        sleep(n)
    # Likewise, process profile content

รูปแบบเอาต์พุตคำอธิบายประกอบรหัสเหมือนกับ line profiler:

$ pprofile --threads 0 demo/threads.py
Command line: ['demo/threads.py']
Total duration: 1.00573s
File: demo/threads.py
File duration: 1.00168s (99.60%)
Line #|      Hits|         Time| Time per hit|      %|Source code
------+----------+-------------+-------------+-------+-----------
     1|         2|  3.21865e-05|  1.60933e-05|  0.00%|import threading
     2|         1|  5.96046e-06|  5.96046e-06|  0.00%|import time
     3|         0|            0|            0|  0.00%|
     4|         2|   1.5974e-05|  7.98702e-06|  0.00%|def func():
     5|         1|      1.00111|      1.00111| 99.54%|  time.sleep(1)
     6|         0|            0|            0|  0.00%|
     7|         2|  2.00272e-05|  1.00136e-05|  0.00%|def func2():
     8|         1|  1.69277e-05|  1.69277e-05|  0.00%|  pass
     9|         0|            0|            0|  0.00%|
    10|         1|  1.81198e-05|  1.81198e-05|  0.00%|t1 = threading.Thread(target=func)
(call)|         1|  0.000610828|  0.000610828|  0.06%|# /usr/lib/python2.7/threading.py:436 __init__
    11|         1|  1.52588e-05|  1.52588e-05|  0.00%|t2 = threading.Thread(target=func)
(call)|         1|  0.000438929|  0.000438929|  0.04%|# /usr/lib/python2.7/threading.py:436 __init__
    12|         1|  4.79221e-05|  4.79221e-05|  0.00%|t1.start()
(call)|         1|  0.000843048|  0.000843048|  0.08%|# /usr/lib/python2.7/threading.py:485 start
    13|         1|  6.48499e-05|  6.48499e-05|  0.01%|t2.start()
(call)|         1|   0.00115609|   0.00115609|  0.11%|# /usr/lib/python2.7/threading.py:485 start
    14|         1|  0.000205994|  0.000205994|  0.02%|(func(), func2())
(call)|         1|      1.00112|      1.00112| 99.54%|# demo/threads.py:4 func
(call)|         1|  3.09944e-05|  3.09944e-05|  0.00%|# demo/threads.py:7 func2
    15|         1|  7.62939e-05|  7.62939e-05|  0.01%|t1.join()
(call)|         1|  0.000423908|  0.000423908|  0.04%|# /usr/lib/python2.7/threading.py:653 join
    16|         1|  5.26905e-05|  5.26905e-05|  0.01%|t2.join()
(call)|         1|  0.000320196|  0.000320196|  0.03%|# /usr/lib/python2.7/threading.py:653 join

โปรดทราบว่าเนื่องจาก pprofile ไม่ได้อาศัยการแก้ไขโค้ดจึงสามารถกำหนดโปรไฟล์คำสั่งโมดูลระดับบนสุดได้ทำให้สามารถกำหนดเวลาเริ่มต้นโปรแกรมได้ (ระยะเวลาในการนำเข้าโมดูลการเริ่มต้น globals ... )

สามารถสร้างเอาต์พุตที่จัดรูปแบบ cachegrind ดังนั้นคุณจึงสามารถใช้ kcachegrindเพื่อเรียกดูผลลัพธ์ขนาดใหญ่ได้อย่างง่ายดาย

การเปิดเผยข้อมูล: ฉันเป็นผู้เขียน pprofile


1
+1 ขอบคุณที่อุดหนุน มันดูเรียบร้อยดี ฉันมีมุมมองที่แตกต่างกันเล็กน้อย - การวัดเวลารวมที่เกิดจากข้อความและหน้าที่เป็นวัตถุประสงค์อย่างหนึ่ง การค้นหาสิ่งที่สามารถทำได้เพื่อทำให้โค้ดเร็วขึ้นเป็นวัตถุประสงค์ที่แตกต่างกัน ความแตกต่างจะชัดเจนอย่างเจ็บปวดเมื่อโค้ดมีขนาดใหญ่เช่นรหัส 10 ^ 6 บรรทัด รหัสอาจเสียเวลามาก วิธีที่ฉันพบคือการเก็บตัวอย่างที่ละเอียดมากจำนวนเล็กน้อยและตรวจสอบด้วยสายตามนุษย์ - ไม่ใช่การสรุป ปัญหาถูกเปิดเผยโดยเศษของเวลาที่เสียไป
Mike Dunlavey

1
คุณพูดถูกฉันไม่ได้พูดถึงการใช้งาน pprofile เมื่อต้องการสร้างโปรไฟล์ย่อยที่เล็กกว่า ฉันแก้ไขโพสต์ของฉันเพื่อเพิ่มตัวอย่างนี้
vpelletier

3
นี่คือสิ่งที่ฉันกำลังมองหา: ไม่ล่วงล้ำและกว้างขวาง
egpbos

1
เครื่องมือที่ดี แต่ทำงานช้ากว่ารหัสเดิมหลายเท่า
rominf

4

คุณสามารถใช้แพ็คเกจline_profilerสำหรับสิ่งนี้ได้

1. ติดตั้งแพ็คเกจครั้งแรก:

    pip install line_profiler

2. ใช้คำสั่ง magic เพื่อโหลดแพ็คเกจไปยังสภาพแวดล้อม python / notebook ของคุณ

    %load_ext line_profiler

3. หากคุณต้องการกำหนดโปรไฟล์รหัสสำหรับฟังก์ชันให้
ทำดังนี้:

    %lprun -f demo_func demo_func(arg1, arg2)

คุณจะได้ผลลัพธ์ที่มีรูปแบบสวยงามพร้อมรายละเอียดทั้งหมดหากคุณทำตามขั้นตอนเหล่านี้ :)

Line #      Hits      Time    Per Hit   % Time  Line Contents
 1                                           def demo_func(a,b):
 2         1        248.0    248.0     64.8      print(a+b)
 3         1         40.0     40.0     10.4      print(a)
 4         1         94.0     94.0     24.5      print(a*b)
 5         1          1.0      1.0      0.3      return a/b

4

เพียงเพื่อปรับปรุงคำตอบข้างต้นของ @Joe Kingtonคำตอบดังกล่าวข้างต้น

สำหรับPython 3.xให้ใช้line_profiler :


การติดตั้ง:

pip install line_profiler

การใช้งาน:

สมมติว่าคุณมีโปรแกรมmain.pyและภายในนั้นมีฟังก์ชันfun_a()และfun_b()คุณต้องการสร้างโปรไฟล์ตามเวลา คุณจะต้องใช้มัณฑนากร@profileก่อนกำหนดฟังก์ชัน สำหรับเช่น

@profile
def fun_a():
    #do something

@profile
def fun_b():
    #do something more

if __name__ == '__main__':
    fun_a()
    fun_b()

โปรแกรมสามารถทำโปรไฟล์ได้โดยดำเนินการคำสั่งเชลล์:

$ kernprof -l -v main.py

สามารถดึงอาร์กิวเมนต์ได้โดยใช้ $ kernprof -h

Usage: kernprof [-s setupfile] [-o output_file_path] scriptfile [arg] ...

Options:
  --version             show program's version number and exit
  -h, --help            show this help message and exit
  -l, --line-by-line    Use the line-by-line profiler from the line_profiler
                        module instead of Profile. Implies --builtin.
  -b, --builtin         Put 'profile' in the builtins. Use 'profile.enable()'
                        and 'profile.disable()' in your code to turn it on and
                        off, or '@profile' to decorate a single function, or
                        'with profile:' to profile a single section of code.
  -o OUTFILE, --outfile=OUTFILE
                        Save stats to <outfile>
  -s SETUP, --setup=SETUP
                        Code to execute before the code to profile
  -v, --view            View the results of the profile in addition to saving
                        it.

ผลลัพธ์จะถูกพิมพ์บนคอนโซลเป็น:

Total time: 17.6699 s
File: main.py
Function: fun_a at line 5

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    5                                           @profile
    6                                           def fun_a():
...

แก้ไข: ผลลัพธ์จากผู้สร้างโปรไฟล์สามารถแยกวิเคราะห์ได้โดยใช้แพ็คเกจTAMPPA ใช้มันเราจะได้พล็อตที่ต้องการทีละบรรทัดเป็น พล็อต


คำแนะนำถูกต้อง แต่กราฟทำให้เข้าใจผิดเนื่องจากline_profilerไม่ได้กำหนดโปรไฟล์การใช้หน่วยความจำ ( memory_profilerทำ แต่มักจะล้มเหลว) ผมขอแนะนำให้ใช้ (ของฉัน) ย้วย Profiler แทนถ้าคุณอยู่ใน Mac OS X หรือ Linux: pip install -U scalene, github.com/emeryberger/scalene - (! และอื่น ๆ ) พร้อมกันไม่ระดับสาย profiling เวลาของ CPU และหน่วยความจำ .
EmeryBerger

สวัสดี @emeryberger พล็อตที่แสดงนั้นทำโดยแพ็คเกจใหม่: TAMPPA แม้ว่าจะมีปัญหาก็ตาม ฉันรู้ว่ามีหลายวิธี ขอบคุณสำหรับการแบ่งปัน ฉันขอแนะนำให้ส่งคำตอบโดยละเอียดที่นี่ :) คุณได้ส่งปัญหาสำหรับ 'memory_profiler' หรือไม่?
Pe Dro

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