2012-01-08 7 views
5

Trong khi lược tả ứng dụng Python của tôi, tôi phát hiện ra rằng len() có vẻ là một cái rất đắt tiền khi sử dụng bộ. Xem mã bên dưới:Hiệu suất cấu hình của len (set) so với tập hợp .__ len __() trong Python 3

import cProfile 

def lenA(s): 
    for i in range(1000000): 
     len(s); 

def lenB(s): 
    for i in range(1000000): 
     s.__len__(); 

def main(): 
    s = set(); 
    lenA(s); 
    lenB(s); 

if __name__ == "__main__": 
    cProfile.run("main()","stats"); 

Theo số liệu thống kê hồ sơ của bên dưới, lenA() có vẻ là chậm hơn so với lenB() 14 lần:

ncalls tottime percall cumtime percall filename:lineno(function) 
     1 1.986 1.986 3.830 3.830 .../lentest.py:5(lenA) 
1000000 1.845 0.000 1.845 0.000 {built-in method len} 
     1 0.273 0.273 0.273 0.273 .../lentest.py:9(lenB) 

Tôi có thiếu cái gì? Hiện nay tôi sử dụng __len__() thay vì len(), nhưng mã trông bẩn :(

+7

Tại sao bạn sử dụng 'cProfile' thay vì' timeit'? Trước đây là để tìm ra tắc nghẽn trong các chương trình lớn, và hy sinh một số độ chính xác trên quy mô nhỏ cho nó. Thứ hai là để đo hiệu suất tổng thể của các đoạn nhỏ tương đối chính xác. 'timeit' nên là lựa chọn đầu tiên cho microbenchmarks như thế này. Và đối với tôi, nó cho thấy một sự khác biệt cực thấp (0,0879 µs cho mỗi cuộc gọi 'len', 0,125 µs trên' .__ len__' call => 'len' chậm hơn 70%). – delnan

+0

Cảm ơn @delnan, tôi khá mới trong Python. Sử dụng 'timeit' tôi cũng nhận được tỷ lệ tương tự. Thật vậy, chương trình của tôi là lớn hơn nhiều so với mã trên, nhưng nó làm tôi ngạc nhiên rằng hàm 'len()' xuất hiện như một trong những nút cổ chai chính. OK, vì vậy tôi sẽ bỏ qua 'len()' và tập trung vào các chức năng của riêng tôi, đúng không? – Tregoreg

Trả lời

13

Rõ ràng, len có một số chi phí, vì nó hiện một cuộc gọi chức năng và dịch AttributeError-TypeError. Ngoài ra, set.__len__ là một thao tác đơn giản như vậy mà nó là ràng buộc để rất nhanh so với chỉ là về bất cứ điều gì, nhưng tôi vẫn không tìm thấy bất cứ điều gì giống như sự khác biệt khi sử dụng 14x timeit:

In [1]: s = set() 

In [2]: %timeit s.__len__() 
1000000 loops, best of 3: 197 ns per loop 

In [3]: %timeit len(s) 
10000000 loops, best of 3: 130 ns per loop 

Bạn nên luôn luôn chỉ cần gọi len, không __len__ Nếu cuộc gọi đến len là. nút cổ chai trong chương trình của bạn, bạn nên suy nghĩ lại về thiết kế của nó, ví dụ: kích thước bộ nhớ cache ở đâu đó hoặc tính toán chúng mà không cần gọi số len.

+0

+1: Đặc biệt, không tối ưu hóa sớm. Điểm chuẩn có thể thiếu sót, và như bạn có thể đã thấy bây giờ, ba điểm chuẩn sẽ có khả năng trả lại ba kết quả khác nhau; và bạn có thể kết thúc điểm chuẩn một thứ hoàn toàn khác với dự kiến ​​của bạn với một điểm chuẩn nhỏ như vậy. Rõ ràng, 'len' không thể nhanh hơn, vì nó gọi' __len__'. Nhưng đó là về tất cả những gì là chắc chắn. –

+2

@ Anony-Mousse: thực ra, tôi chỉ xem lại kết quả của chính mình và bây giờ tôi chỉ thấy rằng 'len' nhanh hơn' __len__'. Bạn không chắc chắn về điều đó. –

+2

's .__ len__' thực hiện cuộc gọi hàm, * và * phải tra cứu một thuộc tính. Điều đó vượt quá sự tra cứu toàn cầu của 'len'. – WolframH

1

Đây sẽ là một bình luận nhưng sau khi nhận xét của người larsman về kết quả gây tranh cãi của anh ấy và kết quả tôi nhận được, tôi nghĩ thú vị là thêm dữ liệu của tôi vào chuỗi.

Đang cố gắng nhiều hơn hoặc ít hơn cùng một thiết lập tôi có trái OP có, và trong cùng một hướng nhận xét bởi larsman:

12.1964105975 <- __len__ 
6.22144670823 <- len() 

C:\Python26\programas> 

Các thử nghiệm:

def lenA(s): 
    for i in range(100): 
     len(s); 

def lenB(s): 
    for i in range(100): 
     s.__len__(); 

s = set() 

if __name__ == "__main__": 

    from timeit import timeit 
    print timeit("lenB(s)", setup="from __main__ import lenB, s") 
    print timeit("lenA(s)", setup="from __main__ import lenA, s") 

Đây là ActivePython 2.6. 7 64bit trong win7

3

Đây là một quan sát thú vị về profiler, không liên quan gì đến hiệu suất thực tế của hàm len. Bạn thấy đấy, trong số liệu thống kê hồ sơ, có hai dòng liên quan đến lenA:

ncalls tottime percall cumtime percall filename:lineno(function) 
     1 1.986 1.986 3.830 3.830 .../lentest.py:5(lenA) 
1000000 1.845 0.000 1.845 0.000 {built-in method len} 

... trong khi chỉ có một dòng liên quan đến lenB:

 1 0.273 0.273 0.273 0.273 .../lentest.py:9(lenB) 

Các hồ sơ đã bị time mỗi cuộc gọi duy nhất từ ​​lenA đến len, nhưng đã tính thời gian là lenB. Thời gian cuộc gọi luôn thêm một số chi phí; trong trường hợp của lenA bạn thấy chi phí này nhân lên hàng triệu lần.

+1

Tôi nghĩ rằng quan điểm của bạn là hoàn toàn chính xác. Đó là tất cả về chi phí của 'cProfile', không phải về hiệu suất của hàm' len'. – Tregoreg