2010-06-15 30 views
12

Tôi có một thói quen python rất đơn giản liên quan đến việc đi xe đạp thông qua danh sách khoảng 20.000 vĩ độ, kinh độ và tính toán khoảng cách của mỗi điểm đến một điểm tham chiếu.deepcopy và python - mẹo để tránh sử dụng nó?

def compute_nearest_points(lat, lon, nPoints=5): 
    """Find the nearest N points, given the input coordinates.""" 

    points = session.query(PointIndex).all() 
    oldNearest = [] 
    newNearest = [] 
    for n in xrange(nPoints): 
     oldNearest.append(PointDistance(None,None,None,99999.0,99999.0)) 
     newNearest.append(obj2) 

    #This is almost certainly an inappropriate use of deepcopy 
    # but how SHOULD I be doing this?!?! 
    for point in points: 
     distance = compute_spherical_law_of_cosines(lat, lon, point.avg_lat, point.avg_lon) 
     k = 0 
     for p in oldNearest: 
      if distance < p.distance: 
       newNearest[k] = PointDistance(
        point.point, point.kana, point.english, point.avg_lat, point.avg_lon, distance=distance 
        ) 
       break 
      else: 
       newNearest[k] = deepcopy(oldNearest[k]) 
      k += 1 
     for j in range(k,nPoints-1): 
      newNearest[j+1] = deepcopy(oldNearest[j]) 
     oldNearest = deepcopy(newNearest) 

    #We're done, now print the result 
    for point in oldNearest: 
     print point.station, point.english, point.distance 

    return 

ban đầu tôi viết điều này trong C, sử dụng phương pháp chính xác giống nhau, và nó hoạt động tốt ở đó, và về cơ bản là tức thời cho nPoints < = 100. Vì vậy, tôi quyết định chuyển nó sang python vì tôi muốn sử dụng SqlAlchemy để làm một số thứ khác. Lần đầu tiên tôi chuyển nó mà không có các câu lệnh sâu sắc mà bây giờ hạt tiêu phương pháp, và điều này gây ra kết quả là 'lẻ', hoặc một phần không chính xác, bởi vì một số điểm đã được chỉ sao chép như tài liệu tham khảo (tôi đoán? Tôi nghĩ rằng, tôi nghĩ rằng nó không?). ?) - nhưng nó vẫn gần như nhanh như phiên bản C.

Bây giờ với các cuộc gọi sâu hơn được thêm vào, thường trình thực hiện công việc một cách chính xác, nhưng nó đã phát sinh một hình phạt cực kỳ hiệu suất, và bây giờ phải mất vài giây để thực hiện công việc tương tự.

Điều này có vẻ như là một công việc khá phổ biến, nhưng tôi rõ ràng không làm theo cách nhiệt tình. Làm thế nào tôi nên làm điều này để tôi vẫn nhận được kết quả chính xác nhưng không cần phải bao gồm sâu ở khắp mọi nơi?

EDIT:
Tôi đã nhấn vào một giải pháp đơn giản hơn nhiều và nhanh hơn,

def compute_nearest_points2(lat, lon, nPoints=5): 
    """Find the nearest N points, given the input coordinates.""" 

    points = session.query(PointIndex).all() 
    nearest = [] 

    for point in points: 
     distance = compute_spherical_law_of_cosines(lat, lon, point.avg_lat, point.avg_lon) 
     nearest.append( 
      PointDistance(
       point.point, point.kana, point.english, point.avg_lat, point.avg_lon, distance=distance 
       ) 
      ) 

    nearest_points = sorted(nearest, key=lambda point: point.distance)[:nPoints]  
    for item in nearest_points: 
     print item.point, item.english, item.distance 
    return 

Vì vậy, về cơ bản tôi chỉ thực hiện một bản sao hoàn chỉnh của đầu vào và phụ thêm một giá trị mới - khoảng cách từ điểm tham khảo. Sau đó, tôi chỉ áp dụng 'sắp xếp' vào danh sách kết quả, xác định rằng phím sắp xếp phải là thuộc tính khoảng cách của đối tượng PointDistance.

Điều này nhanh hơn nhiều so với việc sử dụng bản in sâu mặc dù tôi thú nhận rằng tôi thực sự không hiểu tại sao. Tôi đoán nó là xuống để thực hiện C hiệu quả python của "sắp xếp"?

+0

Lớp 'PointDistance' trông như thế nào? Nếu bạn làm cho lớp 'PointDistance' đơn giản chỉ đề cập đến điểm gốc và khoảng cách của nó (tức là nó là một bộ dữ liệu với hai phần tử), bạn không cần phải sử dụng' deepcopy' vì các điểm sẽ không thay đổi trong thuật toán và khoảng cách là một số đơn giản. –

+0

@ Tamás có nó về cơ bản chỉ là một từ điển. tuy nhiên điều này chắc chắn không hoạt động ngay mà không có bản in sâu trong ví dụ đầu tiên. có lẽ nó sẽ hoạt động nếu tôi chỉ loại bỏ hoàn toàn lớp học và sử dụng một từ điển để thay thế? thành thật mà nói, tôi chỉ không nắm rõ mô hình tham chiếu để biết điều gì sẽ xảy ra trong những trường hợp đó. có lẽ bạn có thể xây dựng hoặc chỉ cho tôi một số tài nguyên hoặc bài đăng khác về chủ đề này? – si28719e

Trả lời

31

Được rồi, mọi thứ đơn giản nhất đầu tiên:

  1. deepcopy là chậm nói chung vì nó đã làm rất nhiều sổ sách kế toán nội bộ để sao chép các trường hợp bệnh lý như đối tượng có chứa bản thân một cách lành mạnh. Xem, ví dụ: this page hoặc xem mã nguồn của deepcopy trong copy.py đó là một nơi nào đó trong đường dẫn Python của bạn.

  2. sorted là nhanh, vì được triển khai trong C. Nhanh hơn nhiều so với loại tương đương bằng Python.

Bây giờ, một số điều khác về hành vi đếm tham chiếu của Python như bạn đã hỏi trong nhận xét của bạn. Trong Python, các biến là các tham chiếu. Khi bạn nói a=1, hãy nghĩ về việc có một đối tượng độc lập là 1a chỉ là một thẻ được gắn với nó. Trong một số ngôn ngữ khác như C, biến là vùng chứa (không phải thẻ) và khi bạn thực hiện a=1, bạn thực sự đặt 1 vào a. Điều này không giữ cho Python, trong đó các biến là các tham chiếu.Này có một số hậu quả thú vị mà bạn có thể cũng đã stumbled khi:

>>> a = []  # construct a new list, attach a tag named "a" to it 
>>> b = a  # attach a tag named "b" to the object which is tagged by "a" 
>>> a.append(1) # append 1 to the list tagged by "a" 
>>> print b  # print the list tagged by "b" 
[1] 

Hành vi này được xem vì danh sách là mutable đối tượng: bạn có thể sửa đổi một danh sách sau khi bạn đã tạo ra nó, và việc sửa đổi được nhìn thấy khi truy cập danh sách thông qua bất kỳ biến nào tham chiếu đến nó. Các bất biến tương đương của danh sách là các bộ:

>>> a =()  # construct a new tuple, attach a tag named "a" to it 
>>> b = a  # now "b" refers to the same empty tuple as "a" 
>>> a += (1, 2) # appending some elements to the tuple 
>>> print b 
() 

Ở đây, a += (1, 2) tạo ra một mới tuple từ tuple hiện gọi bằng a, cộng với một tuple (1, 2) được xây dựng trên-the-fly, và a được điều chỉnh để trỏ đến tuple mới, trong khi tất nhiên b vẫn đề cập đến tuple cũ. Điều tương tự cũng xảy ra với các số bổ sung đơn giản như a = a+2: trong trường hợp này, số ban đầu được trỏ đến bởi a không bị đột biến theo bất kỳ cách nào, Python "tạo" một số mới và di chuyển a để trỏ đến số mới. Vì vậy, trong một nutshell: số, dây và tuples là bất biến; danh sách, dicts và bộ có thể thay đổi. Các lớp do người dùng định nghĩa nói chung có thể thay đổi trừ khi bạn bảo đảm rõ ràng rằng trạng thái bên trong không thể bị biến đổi. Và có frozenset, đó là một bộ không thể thay đổi. Cộng với nhiều người khác tất nhiên :)

Tôi không biết tại sao mã ban đầu của bạn không hoạt động, nhưng có thể bạn đã đạt đến hành vi liên quan đến đoạn mã tôi đã hiển thị với các danh sách như lớp PointDistance của bạn theo mặc định. Một sự thay thế có thể là lớp namedtuple từ collections, cấu trúc một đối tượng giống như bộ tup có các trường cũng có thể được truy cập theo tên. Ví dụ, bạn có thể làm điều này:

from collections import namedtuple 
PointDistance = namedtuple("PointDistance", "point distance") 

Điều này tạo ra một lớp PointDistance cho bạn rằng có hai tên là lĩnh vực: pointdistance. Trong vòng lặp chính của bạn for, bạn có thể chỉ định những điều này một cách thích hợp. Vì các đối tượng điểm được trỏ đến bởi các trường point sẽ không bị sửa đổi trong quá trình vòng for của bạn và distance là một số (nghĩa là, theo định nghĩa, không thay đổi), bạn nên an toàn khi thực hiện theo cách này. Nhưng có, nói chung, có vẻ như chỉ đơn giản là sử dụng sorted là nhanh hơn kể từ khi sorted được triển khai trong C. Bạn cũng có thể may mắn với mô-đun heapq, thực hiện cấu trúc dữ liệu heap được hỗ trợ bởi danh sách Python thông thường, do đó nó cho phép bạn tìm top k các yếu tố dễ dàng mà không cần phải sắp xếp các phần tử khác. Tuy nhiên, kể từ khi heapq cũng được triển khai bằng Python, rất có thể là sorted hoạt động tốt hơn trừ khi bạn có nhiều điểm.

Cuối cùng, tôi muốn thêm rằng tôi chưa bao giờ phải sử dụng deepcopy cho đến nay, vì vậy tôi đoán có nhiều cách để tránh nó trong hầu hết các trường hợp.

+1

cảm ơn bạn đã dành thời gian viết một lời giải thích chi tiết, rõ ràng và ngắn gọn. tôi cảm thấy như tôi hiểu rõ hơn về 'lý do' của sự việc. – si28719e

6

Tôi hiểu điều này không trực tiếp giải quyết câu hỏi của bạn (và tôi biết đây là câu hỏi cũ), nhưng vì có một số thảo luận về hiệu suất nên xem hoạt động append. Bạn có thể muốn xem xét "tiền phân bổ" mảng.Ví dụ:

array = [None] * num_elements 
for i in range(num_elements): 
    array[i] = True 

so:

array = [] 
for i in range(num_elements): 
    array.append(True) 

Một đơn giản timeit chạy của hai phương pháp này cho thấy một sự cải thiện 25% nếu bạn trước khi bố trí mảng cho dù giá trị vừa phải num_elements.

Các vấn đề liên quan