2017-07-21 23 views
5

Điều này khiến tôi hoàn toàn bối rối.Trật tự lệnh PythonDự phú xạ so với dict()

asset_hist = [] 
for key_host, val_hist_list in am_output.asset_history.items(): 
    for index, hist_item in enumerate(val_hist_list): 
     #row = collections.OrderedDict([("computer_name", key_host), ("id", index), ("hist_item", hist_item)]) 
     row = {"computer_name": key_host, "id": index, "hist_item": hist_item} 
     asset_hist.append(row) 

Mã này hoạt động hoàn hảo với dòng bộ sưu tập đã nhận xét. Tuy nhiên, khi tôi bình luận ra dòng = dòng dict và loại bỏ các bình luận từ các dòng bộ sưu tập thì mọi thứ trở nên rất lạ. Có khoảng 4 triệu trong số các hàng này được tạo và được thêm vào asset_hist.

Vì vậy, khi tôi sử dụng hàng = dict, toàn bộ vòng lặp kết thúc trong khoảng 10 mili giây, nhanh như chớp. Khi tôi sử dụng từ điển đặt hàng, tôi đã chờ hơn 10 phút và nó vẫn chưa kết thúc. Bây giờ, tôi biết OrderDict được cho là chậm hơn một chút so với một dict, nhưng nó được cho là chậm hơn khoảng 10 lần ở mức tồi tệ nhất và bởi toán của tôi thực sự chậm hơn khoảng 100.000 lần so với chức năng này.

Tôi quyết định in chỉ mục trong vòng lặp thấp nhất để xem điều gì đang xảy ra. Điều thú vị là, tôi nhận thấy một sự xuất hiện trong giao diện điều khiển. Chỉ số sẽ in rất nhanh trên màn hình và sau đó dừng lại trong khoảng 3-5 giây trước khi tiếp tục.

am_output.asset_history là một từ điển có một khóa, máy chủ và mỗi hàng là một danh sách các chuỗi. Ví dụ.

am_output.asset_history = {"host1": ["string1", "string2", ...], "host2": ["string1", "string2", ...], ...}

EDIT: Phân tích nổ lách tách với OrderedDict

Tổng số bộ nhớ trên máy chủ VM này: Chỉ 8GB ... cần để provissioned hơn.

LOOP NUM

184796 (~ 5 giây chờ đợi, ~ 60% sử dụng bộ nhớ)

634481 (~ 5 chờ đợi thứ hai, ~ 65% sử dụng bộ nhớ)

1197564 (~ 5 chờ đợi thứ hai , ~ 70% sử dụng bộ nhớ)

1899247 (~ 5 chờ đợi thứ hai, ~ 75% sử dụng bộ nhớ)

2777296 (~ 5 chờ đợi thứ hai, ~ 80% sử dụng bộ nhớ)

012.351.

3873730 (LONG WAIT ... đã chờ 20 phút và đã ngừng sử dụng !, 88,3% mức sử dụng bộ nhớ, quá trình vẫn đang chạy)

Trường hợp việc chờ đợi xảy ra thay đổi theo từng lần chạy.

EDIT: Chạy lại lần nữa, lần này nó dừng lại trên 3873333, gần điểm dừng trước đó. Nó dừng lại sau khi hình thành hàng, trong khi cố gắng để nối thêm ... Tôi đã không nhận thấy nỗ lực cuối cùng này nhưng nó đã có sau đó quá ... vấn đề là với dòng append, không phải dòng hàng ... Tôi vẫn còn vách ngăn. Đây là hàng nó được sản xuất ngay trước điểm dừng dài (thêm hàng vào bản in) ... tên máy chủ đã thay đổi để bảo vệ người vô tội:

3873333: OrderedDict ([('computer_name', 'bg-fd5612ea'), ('id', 1), ('hist_item', "sys1 Normalizer (sys1-4): Tên miền không thể được xác định từ sys1 Name 'bg-fd5612ea'.")])

+1

Tôi cảm thấy khó tin rằng những gì bạn đang quan sát là do hành vi 'dict' so với' OrderedDict', có thể điều gì đó đang thay đổi không? Tôi cũng nghĩ rằng ai đó sẽ đấu tranh để tái sản xuất này –

+1

Vâng, họ sẽ cần phải tạo ra các từ điển lớn đầu tiên, có lẽ máy phát điện chuỗi ngẫu nhiên sẽ đủ tốt. Nhưng tôi hoàn toàn nghiêm túc, khi tôi di chuyển # xuống một dòng này xảy ra. Nó cũng đáng chú ý: khi nó bị mắc kẹt trong vòng lặp này khi sử dụng lệnh dict và tôi nhấn control + C để dừng vòng lặp, nó giống như không có gì xảy ra ... Tôi phải bấm control + Z để thoát ra. – gunslingor

+0

Phiên bản Python nào? Trong phiên bản mới nhất, OrderedDict chỉ đơn giản là một bí danh cho dict, vì việc triển khai hiện tại xảy ra được sắp xếp theo chi tiết thực hiện. – RemcoGerlich

Trả lời

1

Khi thử nghiệm của riêng bạn chứng minh, bạn đang hết bộ nhớ. Ngay cả trên CPython 3.6 (khi đồng bằng dict thực sự được đặt hàng, mặc dù không phải là đảm bảo ngôn ngữ), OrderedDict có chi phí bộ nhớ đáng kể so với dict; nó vẫn được thực hiện với một danh sách liên kết bên cạnh để giữ lại trật tự và hỗ trợ lặp lại dễ dàng, sắp xếp lại với move_to_end, v.v. Bạn có thể biết bằng cách kiểm tra với sys.getsizeof (kết quả chính xác sẽ khác với phiên bản Python và bitwidth xây dựng, 32 so với 64 bit):

>>> od = OrderedDict([("a", 1), ("b", 2), ("c", 3)]) 
>>> d = {**od} 
>>> sys.getsizeof(od) 
464 # On 3.5 x64 it's 512 
>>> sys.getsizeof(d) 
240 # On 3.5 x64 it's 288 

Bỏ qua các dữ liệu được lưu trữ, các chi phí cho OrderedDict đây là gần gấp đôi so với vùng đồng bằng dict. Nếu bạn đang tạo ra 4 triệu trong số các mục này, trên máy tính của tôi có thể bổ sung thêm chi phí cho một mương trên 850 MB (trên cả 3,5 và 3,6).

Đó có thể là sự kết hợp của tất cả các chương trình khác trên hệ thống của bạn, cộng với chương trình Python của bạn, vượt quá RAM được cấp cho máy của bạn và bạn bị mắc kẹt. Đặc biệt, bất cứ khi nào asset_hist phải mở rộng cho các mục mới, có thể cần phải trang trong các phần lớn của nó (được phân trang vì thiếu sử dụng), và bất cứ khi nào một trình kích hoạt thu gom rác cyclic chạy (một GC đầy đủ xảy ra khoảng 70.000 phân bổ và deallocations theo mặc định), tất cả các OrderedDict s được phân trang trở lại để kiểm tra xem chúng vẫn được tham chiếu bên ngoài chu kỳ (bạn có thể kiểm tra xem GC chạy có phải là vấn đề chính hay không bằng cách vô hiệu hóa GC via gc.disable() theo chu kỳ).

Với trường hợp sử dụng cụ thể của bạn, tôi thực sự khuyên bạn nên tránh cả hai dictOrderedDict. Các chi phí của thậm chí dict, ngay cả các hình thức rẻ hơn trên Python 3,6, là loại cực đoan khi bạn có một bộ chính xác ba phím cố định hơn và hơn. Thay vào đó, use collections.namedtuple, được thiết kế cho các đối tượng nhẹ có thể tham chiếu bằng tên hoặc chỉ mục (chúng hoạt động như thường lệ tuple s, nhưng cũng cho phép truy cập từng giá trị dưới dạng thuộc tính), làm giảm đáng kể chi phí bộ nhớ của chương trình của bạn nó lên ngay cả khi bộ nhớ không phải là vấn đề).

Ví dụ:

from collections import namedtuple 

ComputerInfo = namedtuple('ComputerInfo', ['computer_name', 'id', 'hist_item']) 

asset_hist = [] 
for key_host, val_hist_list in am_output.asset_history.items(): 
    for index, hist_item in enumerate(val_hist_list): 
     asset_hist.append(ComputerInfo(key_host, index, hist_item)) 

Chỉ khác biệt trong sử dụng là bạn thay thế row['computer_name'] với row.computer_name, hoặc nếu bạn cần tất cả các giá trị, bạn có thể giải nén nó giống như một thường xuyên tuple, ví dụ comphost, idx, hist = row. Nếu bạn cần tạm thời OrderedDict đúng (không lưu trữ chúng cho tất cả mọi thứ), bạn có thể gọi row._asdict() để nhận được OrderedDict với cùng ánh xạ với số namedtuple, nhưng điều đó không cần thiết. Tiết kiệm bộ nhớ có ý nghĩa; trên hệ thống của tôi, ba phần tử namedtuple giảm chi phí cho mỗi mục đến 72 byte, nhỏ hơn một phần ba so với 3,6 dict và nhỏ hơn một phần sáu của 3,6 OrderedDict (và ba phần tử namedtuple vẫn là 72 byte trên 3,5, trong đó dict/OrderedDict lớn hơn trước 3.6). Nó có thể tiết kiệm nhiều hơn thế; tuple s (và namedtuple theo phần mở rộng) được phân bổ dưới dạng một tiếp giáp duy nhất C struct, trong khi dict và công ty có ít nhất hai phân bổ (một cho cấu trúc đối tượng, một hoặc nhiều phần tự động thay đổi cấu trúc), mỗi phần có thể trả chi phí phân bổ và chi phí liên kết. Dù bằng cách nào, đối với kịch bản bốn triệu hàng của bạn, sử dụng namedtuple có nghĩa là chi trả (vượt quá chi phí của các giá trị) trên tổng số 275 MB, so với 915 (3,6) - 1100 (3,5) MB cho dict và 1770 (3.6) - 1950 (3,5) MB cho OrderedDict. Khi bạn đang nói về một hệ thống 8 GB, cạo 1,5 GB chi phí của bạn là một cải tiến lớn.

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