Đa xử lý trong Python: hiểu logic đằng sau `chunksize`


84

Những yếu tố nào xác định một chunksizeđối số tối ưu cho các phương pháp như thế multiprocessing.Pool.map()nào? Các .map()phương pháp dường như sử dụng một heuristic, tùy ý cho chunksize mặc định của nó (giải thích dưới đây); điều gì thúc đẩy sự lựa chọn đó và có cách tiếp cận chu đáo hơn dựa trên một số tình huống / thiết lập cụ thể không?

Ví dụ - nói rằng tôi là:

  • Chuyển một iterableđến .map()có ~ 15 triệu phần tử;
  • Làm việc trên máy có 24 lõi và sử dụng mặc định processes = os.cpu_count()bên trong multiprocessing.Pool().

Suy nghĩ ngây thơ của tôi là chia cho mỗi người trong số 24 công nhân một phần có kích thước bằng nhau, tức là 15_000_000 / 24hoặc 625.000. Các khối lớn sẽ giảm doanh thu / chi phí trong khi sử dụng đầy đủ tất cả công nhân. Nhưng có vẻ như điều này đang thiếu một số mặt trái tiềm ẩn của việc giao hàng loạt lớn cho mỗi công nhân. Đây có phải là một bức tranh chưa hoàn chỉnh, và tôi còn thiếu gì?


Một phần câu hỏi của tôi bắt nguồn từ logic mặc định cho if chunksize=None: both .map().starmap()call .map_async(), trông giống như sau:

def _map_async(self, func, iterable, mapper, chunksize=None, callback=None,
               error_callback=None):
    # ... (materialize `iterable` to list if it's an iterator)
    if chunksize is None:
        chunksize, extra = divmod(len(iterable), len(self._pool) * 4)  # ????
        if extra:
            chunksize += 1
    if len(iterable) == 0:
        chunksize = 0

Logic đằng sau là divmod(len(iterable), len(self._pool) * 4)gì? Điều này ngụ ý rằng kích thước khối sẽ gần hơn 15_000_000 / (24 * 4) == 156_250. Ý định nhân len(self._pool)4 là gì?

Điều này làm cho kích thước khối kết quả có một hệ số nhỏ hơn 4 so với "logic ngây thơ" của tôi ở trên, bao gồm chỉ chia độ dài của tệp có thể lặp lại cho số nhân công trong đó pool._pool.

Cuối cùng, cũng có đoạn mã này từ tài liệu Python trên .imap()đó càng thúc đẩy sự tò mò của tôi:

Đối chunksizesố giống như đối số được sử dụng bởi map() phương thức. Đối với rất dài iterables sử dụng một giá trị lớn cho chunksizethể làm cho công việc hoàn thành nhiều nhanh hơn bằng cách sử dụng giá trị mặc định là 1.


Câu trả lời liên quan hữu ích nhưng hơi quá cao cấp: Đa xử lý trong Python: tại sao các khối lớn lại chậm hơn? .


1
4Là tùy ý và toàn bộ việc tính toán kích thước khối là một phép tính toán. Yếu tố liên quan là thời gian xử lý thực tế của bạn có thể thay đổi bao nhiêu. Thêm một chút về điều này ở đây cho đến khi tôi có thời gian để trả lời nếu vẫn cần.
Darkonaut

Bạn đã kiểm tra câu hỏi này chưa?
Andrew Naguib

1
Cảm ơn @AndrewNaguib, tôi đã thực sự không stumbled trên một mà bằng cách nào đó
Brad Solomon

1
Chỉ xin cho bạn biết: Tôi không quên câu hỏi này. Trên thực tế, tôi đang nghiên cứu một câu trả lời chính tắc về các thứ nguyên trong Kinh thánh (rất nhiều đoạn mã hữu ích và đồ họa lạ mắt) kể từ ngày bạn hỏi. Tiền thưởng vẫn đến quá sớm 1-2 tuần để có thể hoàn thành tất cả, nhưng tôi tự tin rằng mình sẽ có thể hạ được thứ gì đó đủ gần trước thời hạn.
Darkonaut

@BradSolomon Chào mừng bạn :). Nó có trả lời câu hỏi của bạn tho không?
Andrew Naguib

Câu trả lời:


192

Câu trả lời ngắn

Thuật toán kích thước khối của Pool là một thuật toán kinh nghiệm. Nó cung cấp một giải pháp đơn giản cho tất cả các tình huống vấn đề có thể tưởng tượng mà bạn đang cố gắng đưa vào các phương pháp của Pool. Do đó, nó không thể được tối ưu hóa cho bất kỳ trường hợp cụ thể nào .

Thuật toán tự ý chia phần có thể lặp lại thành nhiều phần hơn xấp xỉ bốn lần so với cách tiếp cận đơn giản. Nhiều khối hơn có nghĩa là nhiều chi phí hơn, nhưng tăng tính linh hoạt trong lịch trình. Câu trả lời này sẽ hiển thị như thế nào, điều này dẫn đến mức sử dụng công nhân trung bình cao hơn, nhưng không đảm bảo thời gian tính toán tổng thể ngắn hơn cho mọi trường hợp.

"Thật tuyệt khi biết" bạn có thể nghĩ, "nhưng làm thế nào biết được điều này giúp tôi giải quyết các vấn đề cụ thể về xử lý đa quy trình của mình?" Vâng, nó không. Câu trả lời ngắn gọn trung thực hơn là, "không có câu trả lời ngắn", "đa xử lý là phức tạp" và "nó phụ thuộc". Một triệu chứng quan sát được có thể có nhiều gốc rễ khác nhau, ngay cả đối với các tình huống tương tự.

Câu trả lời này cố gắng cung cấp cho bạn các khái niệm cơ bản giúp bạn có hình dung rõ ràng hơn về hộp đen lập lịch của Pool. Nó cũng cố gắng cung cấp cho bạn một số công cụ cơ bản trong tầm tay để nhận biết và tránh các vách đá tiềm ẩn liên quan đến kích thước khối.


Mục lục

Phần I

  1. Các định nghĩa
  2. Mục tiêu song song
  3. Kịch bản song song
  4. Rủi ro của Chunksize> 1
  5. Pool's Chunksize-Algorithm
  6. Định lượng hiệu quả thuật toán

    6.1 Mô hình

    6.2 Lịch trình song song

    6.3 Hiệu quả

    6.3.1 Hiệu quả phân phối tuyệt đối (ADE)

    6.3.2 Hiệu quả phân phối tương đối (RDE)

Phần II

  1. Thuật toán Chunksize-Algorithm của Naive vs.
  2. Kiểm tra thực tế
  3. Phần kết luận

Trước tiên cần phải làm rõ một số thuật ngữ quan trọng.


1. Định nghĩa


Chunk

Một đoạn ở đây là một phần của iterable-argument được chỉ định trong một lời gọi phương thức gộp. Kích thước khối được tính toán như thế nào và điều này có thể có những ảnh hưởng gì, là chủ đề của câu trả lời này.


Bài tập

Biểu diễn vật lý của nhiệm vụ trong quy trình công nhân về mặt dữ liệu có thể được nhìn thấy trong hình bên dưới.

hình0

Hình bên cho thấy một lệnh gọi ví dụ đến pool.map(), được hiển thị dọc theo một dòng mã, được lấy từ multiprocessing.pool.workerhàm, trong đó một tác vụ được đọc từ inqueuenó được giải nén. workerlà chức năng chính cơ bản trong MainThreadquy trình pool-worker-process. Đối số func-được chỉ định trong phương thức funcgộp sẽ chỉ khớp với -variable bên trong-function workerđối với các phương thức gọi đơn như apply_asyncvà for imapvới chunksize=1. Đối với phần còn lại của các phương thức chunksizegộp với -parameter, hàm xử lý funcsẽ là một hàm ánh xạ ( mapstarhoặc starmapstar). Chức năng này cũng ánh xạ người dùng do người dùng chỉ định như một đơn vị công việc .func -parameter do trên mọi phần tử của đoạn được truyền của tệp có thể lặp (-> "map-task"). Thời gian này cần, xác định một nhiệm vụ


Taskel

Mặc dù việc sử dụng từ "task" cho toàn bộ quá trình xử lý một đoạn được khớp với mã bên trong multiprocessing.pool, không có dấu hiệu nào cho thấy một lệnh gọi đơn lẻ đến người dùng do người dùng chỉ định func, với một phần tử của đoạn là (các) đối số, sẽ như thế nào goi. Để tránh nhầm lẫn phát sinh từ xung đột đặt tên (hãy nghĩ đến maxtasksperchild-parameter cho -method của Pool __init__), câu trả lời này sẽ đề cập đến các đơn vị công việc đơn lẻ trong một nhiệm vụ dưới dạng taskel .

Một taskel (từ nhiệm vụ + el ement) là đơn vị nhỏ nhất của công việc trong một nhiệm vụ . Nó là việc thực thi một hàm được chỉ định với func-parameter của một -method Pool, được gọi với các đối số thu được từ một phần tử duy nhất của đoạn được truyền . Một nhiệm vụ bao gồm chunksize taskels .


Chi phí song song (PO)

PO bao gồm chi phí nội bộ Python và chi phí chung cho giao tiếp giữa các quá trình (IPC). Chi phí cho mỗi tác vụ trong Python đi kèm với mã cần thiết để đóng gói và giải nén các tác vụ cũng như kết quả của nó. IPC-overhead đi kèm với sự đồng bộ hóa cần thiết của các luồng và sao chép dữ liệu giữa các không gian địa chỉ khác nhau (cần hai bước sao chép: cha -> hàng đợi -> con). Số lượng chi phí IPC phụ thuộc vào hệ điều hành, phần cứng và kích thước dữ liệu, điều này khiến việc khái quát về tác động trở nên khó khăn.


2. Mục tiêu song song hóa

Khi sử dụng đa xử lý, mục tiêu tổng thể của chúng tôi (rõ ràng) là giảm thiểu tổng thời gian xử lý cho tất cả các tác vụ. Để đạt được mục tiêu tổng thể này, mục tiêu kỹ thuật của chúng tôi cần phải tối ưu hóa việc sử dụng tài nguyên phần cứng .

Một số mục tiêu phụ quan trọng để đạt được mục tiêu kỹ thuật là:

  • giảm thiểu chi phí song song (nổi tiếng nhất, nhưng không đơn lẻ: IPC )
  • sử dụng cao trên tất cả các lõi cpu
  • hạn chế mức sử dụng bộ nhớ để ngăn hệ điều hành phân trang quá mức (chuyển vào thùng rác )

Lúc đầu, các tác vụ cần phải đủ nặng về mặt tính toán (chuyên sâu), để kiếm lại PO, chúng ta phải trả tiền cho quá trình song song hóa. Mức độ liên quan của PO giảm khi tăng thời gian tính toán tuyệt đối cho mỗi nhiệm vụ. Hoặc, nói cách khác, thời gian tính toán tuyệt đối trên mỗi nhiệm vụ cho vấn đề của bạn càng lớn, thì nhu cầu giảm PO càng ít liên quan. Nếu quá trình tính toán của bạn mất hàng giờ cho mỗi tác vụ, thì tổng chi phí IPC sẽ không đáng kể so với. Mối quan tâm chính ở đây là ngăn chặn các tiến trình của worker chạy không tải sau khi tất cả các tác vụ đã được phân phối. Giữ tất cả các lõi được tải có nghĩa là, chúng tôi đang song song hóa nhiều nhất có thể.


3. Kịch bản song song

Yếu tố nào xác định một đối số kích thước khối tối ưu cho các phương thức như multiprocessing.Pool.map ()

Yếu tố chính được đề cập là thời gian tính toán có thể khác nhau trên các tác vụ đơn lẻ của chúng tôi. Để đặt tên cho nó, sự lựa chọn cho một kích thước khối tối ưu được xác định bởi Hệ số biến thiên ( CV ) cho thời gian tính toán trên mỗi nhiệm vụ.

Hai kịch bản cực đoan trên quy mô, sau đây từ mức độ của biến thể này là:

  1. Tất cả các nhiệm vụ cần thời gian tính toán chính xác như nhau.
  2. Nhiệm vụ có thể mất vài giây hoặc vài ngày để hoàn thành.

Để có khả năng ghi nhớ tốt hơn, tôi sẽ gọi các trường hợp sau là:

  1. Kịch bản dày đặc
  2. Kịch bản rộng


Kịch bản dày đặc

Trong một Kịch bản dày đặc, bạn nên phân phối tất cả các nhiệm vụ cùng một lúc, để giữ IPC cần thiết và chuyển đổi ngữ cảnh ở mức tối thiểu. Điều này có nghĩa là chúng tôi chỉ muốn tạo càng nhiều khối, càng nhiều quy trình công nhân càng có nhiều. Như đã nêu ở trên, trọng lượng của PO tăng lên khi thời gian tính toán ngắn hơn cho mỗi tác vụ.

Để có thông lượng tối đa, chúng tôi cũng muốn tất cả các quy trình của nhân viên bận rộn cho đến khi tất cả các tác vụ được xử lý (không có công nhân chạy không tải). Đối với mục tiêu này, các khối được phân phối phải có kích thước bằng hoặc gần bằng.


Kịch bản rộng

Ví dụ điển hình cho một Kịch bản rộng sẽ là một vấn đề tối ưu hóa, trong đó kết quả hội tụ nhanh chóng hoặc việc tính toán có thể mất hàng giờ, nếu không phải vài ngày. Thông thường, không thể dự đoán được hỗn hợp của "nhiệm vụ nhẹ" và "nhiệm vụ nặng" mà một nhiệm vụ sẽ chứa trong trường hợp này, do đó không nên phân phối quá nhiều nhiệm vụ trong một loạt tác vụ cùng một lúc. Phân phối ít nhiệm vụ cùng một lúc hơn có thể, có nghĩa là tăng tính linh hoạt trong việc lập lịch trình. Điều này là cần thiết ở đây để đạt được mục tiêu phụ của chúng tôi là sử dụng cao tất cả các lõi.

Nếu Poolcác phương pháp, theo mặc định, sẽ được tối ưu hóa hoàn toàn cho Kịch bản dày đặc, thì chúng sẽ ngày càng tạo ra thời gian tối ưu cho mọi vấn đề ở gần Kịch bản rộng hơn.


4. Rủi ro của Chunksize> 1

Hãy xem xét ví dụ về mã giả được đơn giản hóa này về một Kịch bản rộng-có thể xác định được, mà chúng tôi muốn chuyển vào một phương thức gộp:

good_luck_iterable = [60, 60, 86400, 60, 86400, 60, 60, 84600]

Thay vì các giá trị thực tế, chúng tôi giả vờ xem thời gian tính toán cần thiết tính bằng giây, đơn giản là chỉ 1 phút hoặc 1 ngày. Chúng tôi giả định rằng nhóm có bốn quy trình công nhân (trên bốn lõi) và chunksizeđược đặt thành 2. Bởi vì đơn đặt hàng sẽ được giữ, các khối gửi cho người lao động sẽ là:

[(60, 60), (86400, 60), (86400, 60), (60, 84600)]

Vì chúng tôi có đủ công nhân và thời gian tính toán đủ cao, chúng tôi có thể nói, rằng mọi quy trình công nhân sẽ có một phần nhỏ để làm việc ngay từ đầu. (Điều này không nhất thiết phải xảy ra khi hoàn thành nhiệm vụ nhanh chóng). Hơn nữa, chúng tôi có thể nói, toàn bộ quá trình xử lý sẽ mất khoảng 86400 + 60 giây, bởi vì đó là tổng thời gian tính toán cao nhất cho một đoạn trong kịch bản nhân tạo này và chúng tôi chỉ phân phối các đoạn một lần.

Bây giờ hãy xem xét điều này có thể lặp lại, chỉ có một phần tử chuyển đổi vị trí của nó so với phần tử có thể lặp lại trước đó:

bad_luck_iterable = [60, 60, 86400, 86400, 60, 60, 60, 84600]

... và các phần tương ứng:

[(60, 60), (86400, 86400), (60, 60), (60, 84600)]

Thật không may mắn với việc sắp xếp các tệp có thể lặp lại đã tăng gần gấp đôi (86400 + 86400) tổng thời gian xử lý của chúng tôi! Công nhân nhận được -chunk xấu xa (86400, 86400) đang chặn nhiệm vụ nặng thứ hai trong nhiệm vụ của nó để phân phối cho một trong những công nhân đang chạy không tải đã hoàn thành -chunks (60, 60) của họ. Chúng tôi rõ ràng sẽ không mạo hiểm với một kết quả khó chịu như vậy nếu chúng tôi đặt chunksize=1.

Đây là nguy cơ của các khối lớn hơn. Với kích thước cao hơn, chúng tôi giao dịch tính linh hoạt trong việc lập lịch trình để có ít chi phí hơn và trong những trường hợp như trên, đó là một thỏa thuận tồi.

Chúng ta sẽ thấy như thế nào trong chương 6. Định lượng Hiệu quả Thuật toán , các khối lớn hơn cũng có thể dẫn đến kết quả không tối ưu cho các Kịch bản dày đặc .


5. Thuật toán Chunksize-Algorithm của Pool

Dưới đây, bạn sẽ tìm thấy một phiên bản được sửa đổi một chút của thuật toán bên trong mã nguồn. Như bạn có thể thấy, tôi đã cắt phần dưới và gói nó thành một hàm để tính toán chunksizeđối số bên ngoài. Tôi cũng thay thế 4bằng một factortham số và thuê ngoài các len()cuộc gọi.

# mp_utils.py

def calc_chunksize(n_workers, len_iterable, factor=4):
    """Calculate chunksize argument for Pool-methods.

    Resembles source-code within `multiprocessing.pool.Pool._map_async`.
    """
    chunksize, extra = divmod(len_iterable, n_workers * factor)
    if extra:
        chunksize += 1
    return chunksize

Để đảm bảo tất cả chúng ta đều ở trên cùng một trang, đây là những gì divmod:

divmod(x, y)là một hàm nội trang trả về (x//y, x%y). x // ylà phép chia tầng, trả về thương số làm tròn xuống từ x / y, trong khi x % yphép toán modulo trả về phần còn lại từ đó x / y. Do đó, ví dụ như divmod(10, 3)lợi nhuận (3, 1).

Bây giờ khi bạn nhìn vào chunksize, extra = divmod(len_iterable, n_workers * 4), bạn sẽ thấy n_workersđây là số chia ytrong x / yvà phép nhân bằng 4, mà không điều chỉnh hơn nữa thông qua if extra: chunksize +=1sau này, dẫn đến một chunksize ban đầu ít nhất nhỏ hơn gấp bốn lần (cho len_iterable >= n_workers * 4) hơn nó sẽ khác.

Để xem ảnh hưởng của phép nhân với 4kết quả kích thước khối trung gian, hãy xem xét hàm này:

def compare_chunksizes(len_iterable, n_workers=4):
    """Calculate naive chunksize, Pool's stage-1 chunksize and the chunksize
    for Pool's complete algorithm. Return chunksizes and the real factors by
    which naive chunksizes are bigger.
    """
    cs_naive = len_iterable // n_workers or 1  # naive approach
    cs_pool1 = len_iterable // (n_workers * 4) or 1  # incomplete pool algo.
    cs_pool2 = calc_chunksize(n_workers, len_iterable)

    real_factor_pool1 = cs_naive / cs_pool1
    real_factor_pool2 = cs_naive / cs_pool2

    return cs_naive, cs_pool1, cs_pool2, real_factor_pool1, real_factor_pool2

Hàm trên tính toán kích thước khối ngây thơ ( cs_naive) và kích thước khối bước đầu tiên của thuật toán chunksize-Pool ( cs_pool1), cũng như kích thước khối cho thuật toán Pool-hoàn chỉnh ( cs_pool2). Hơn nữa, nó tính toán các yếu tố thực rf_pool1 = cs_naive / cs_pool1rf_pool2 = cs_naive / cs_pool2cho chúng ta biết số lần các khối được tính toán nguyên bản lớn hơn (các) phiên bản nội bộ của Pool.

Dưới đây, bạn thấy hai hình được tạo với đầu ra từ chức năng này. Hình bên trái chỉ cho thấy các khối cho n_workers=4đến khi có độ dài có thể lặp lại là 500. Hình bên phải hiển thị các giá trị cho rf_pool1. Đối với độ dài có thể lặp lại 16, yếu tố thực trở thành >=4(for len_iterable >= n_workers * 4) và giá trị lớn nhất của nó là 7đối với độ dài có thể lặp lại 28-31. Đó là một độ lệch lớn so với yếu tố ban đầu 4mà thuật toán hội tụ cho các lần lặp dài hơn. 'Dài hơn' ở đây là tương đối và phụ thuộc vào số lượng công nhân cụ thể.

Hình 1

Hãy nhớ chunksize cs_pool1vẫn thiếu phần điều chỉnh- extravới phần còn lại từ được divmodchứa trong cs_pool2thuật toán hoàn chỉnh.

Thuật toán tiếp tục với:

if extra:
    chunksize += 1

Bây giờ trong trường hợp đã có một phần còn lại (một extratừ divmod tác), tăng chunksize bởi 1 rõ ràng là không thể làm việc ra cho mỗi nhiệm vụ. Rốt cuộc, nếu có, sẽ không có phần còn lại để bắt đầu.

Làm thế nào bạn có thể nhìn thấy trong hình dưới đây, các " ngoại điều trị " có hiệu lực thi hành, rằng yếu tố thực cho rf_pool2hiện nay hội tụ về phía 4từ bên dưới 4 và độ lệch có phần mượt mà hơn. Độ lệch chuẩn cho n_workers=4len_iterable=500giảm từ 0.5233cho rf_pool1đến 0.4115cho rf_pool2.

Hình 2

Cuối cùng, tăng chunksize1 có tác dụng, tác vụ cuối cùng được truyền chỉ có kích thước là len_iterable % chunksize or chunksize.

Tuy nhiên, điều thú vị hơn và chúng ta sẽ thấy như thế nào sau này, do hậu quả, tác động của việc xử lý bổ sung có thể được quan sát đối với số lượng các khối được tạo ( n_chunks). Đối với các đoạn lặp đủ dài, thuật toán kích thước khối đã hoàn thành của Pool ( n_pool2trong hình bên dưới) sẽ ổn định số lượng khối tại n_chunks == n_workers * 4. Ngược lại, thuật toán ngây thơ (sau một lần ợ hơi ban đầu) tiếp tục xen kẽ giữa n_chunks == n_workersn_chunks == n_workers + 1khi độ dài của tệp có thể lặp lại tăng lên.

hình 3

Dưới đây, bạn sẽ tìm thấy hai hàm thông tin nâng cao cho Pool và thuật toán kích thước khối ngây thơ. Đầu ra của các hàm này sẽ cần thiết trong chương tiếp theo.

# mp_utils.py

from collections import namedtuple


Chunkinfo = namedtuple(
    'Chunkinfo', ['n_workers', 'len_iterable', 'n_chunks',
                  'chunksize', 'last_chunk']
)

def calc_chunksize_info(n_workers, len_iterable, factor=4):
    """Calculate chunksize numbers."""
    chunksize, extra = divmod(len_iterable, n_workers * factor)
    if extra:
        chunksize += 1
    # `+ (len_iterable % chunksize > 0)` exploits that `True == 1`
    n_chunks = len_iterable // chunksize + (len_iterable % chunksize > 0)
    # exploit `0 == False`
    last_chunk = len_iterable % chunksize or chunksize

    return Chunkinfo(
        n_workers, len_iterable, n_chunks, chunksize, last_chunk
    )

Đừng bối rối bởi cái nhìn có thể bất ngờ của calc_naive_chunksize_info. Các extratừ divmodkhông được sử dụng để tính chunksize.

def calc_naive_chunksize_info(n_workers, len_iterable):
    """Calculate naive chunksize numbers."""
    chunksize, extra = divmod(len_iterable, n_workers)
    if chunksize == 0:
        chunksize = 1
        n_chunks = extra
        last_chunk = chunksize
    else:
        n_chunks = len_iterable // chunksize + (len_iterable % chunksize > 0)
        last_chunk = len_iterable % chunksize or chunksize

    return Chunkinfo(
        n_workers, len_iterable, n_chunks, chunksize, last_chunk
    )

6. Định lượng hiệu quả thuật toán

Bây giờ, sau khi chúng ta đã thấy đầu ra của Poolthuật toán chunksize trông khác như thế nào so với đầu ra từ thuật toán ngây thơ ...

  • Làm thế nào để biết liệu phương pháp của Pool có thực sự cải thiện điều gì đó không?
  • Và chính xác thì thứ này có thể là gì?

Như đã trình bày trong chương trước, đối với các đoạn lặp dài hơn (số lượng nhiệm vụ lớn hơn), thuật toán phân khối của Pool chia khoảng có thể lặp lại thành nhiều phần nhiều hơn bốn lần so với phương pháp đơn giản. Các khối nhỏ hơn có nghĩa là nhiều nhiệm vụ hơn và nhiều nhiệm vụ hơn có nghĩa là Chi phí Song song (PO) nhiều hơn , một chi phí phải được cân nhắc dựa trên lợi ích của việc tăng tính linh hoạt trong lịch trình (nhớ lại "Rủi ro của Chunksize> 1" ).

Vì những lý do khá rõ ràng, thuật toán phân khối cơ bản của Pool không thể cân nhắc tính linh hoạt của việc lập lịch so với PO đối với chúng tôi. IPC-overhead phụ thuộc vào hệ điều hành, phần cứng- và kích thước dữ liệu. Thuật toán không thể biết chúng tôi chạy mã của mình trên phần cứng nào, cũng như không có manh mối về thời gian một nhiệm vụ sẽ hoàn thành. Đó là một heuristic cung cấp chức năng cơ bản cho tất cả các tình huống có thể xảy ra. Điều này có nghĩa là nó không thể được tối ưu hóa cho bất kỳ trường hợp cụ thể nào. Như đã đề cập trước đây, PO cũng ngày càng trở nên ít được quan tâm hơn với việc tăng thời gian tính toán trên mỗi nhiệm vụ (tương quan nghịch).

Khi bạn nhớ lại Mục tiêu song song từ chương 2, một gạch đầu dòng là:

  • sử dụng cao trên tất cả các lõi cpu

Điều đã đề cập trước đó , thuật toán phân khối của Pool có thể cố gắng cải thiện là giảm thiểu các quy trình chạy không tải của nhân viên , tương ứng là việc sử dụng các lõi cpu .

Một câu hỏi lặp lại trên SO liên quan đến multiprocessing.Poolđược hỏi bởi những người thắc mắc về các lõi không sử dụng / các quy trình công nhân chạy không tải trong các tình huống mà bạn mong đợi tất cả các quy trình công nhân đều bận rộn. Mặc dù điều này có thể có nhiều lý do, các quy trình công nhân chạy không tải cho đến khi kết thúc tính toán là một quan sát mà chúng ta thường có thể thực hiện, ngay cả với các Kịch bản dày đặc (thời gian tính toán bằng nhau trên mỗi tác vụ) trong trường hợp số lượng công nhân không phải là ước của số của khối ( n_chunks % n_workers > 0).

Câu hỏi bây giờ là:

Làm thế nào chúng ta thực tế có thể chuyển sự hiểu biết của chúng ta về các khối thành một thứ gì đó cho phép chúng ta giải thích việc sử dụng nhân viên được quan sát, hoặc thậm chí so sánh hiệu quả của các thuật toán khác nhau về vấn đề đó?


6.1 Mô hình

Để có những hiểu biết sâu sắc hơn ở đây, chúng ta cần một dạng trừu tượng của các phép tính song song giúp đơn giản hóa thực tế quá phức tạp xuống mức độ phức tạp có thể quản lý được, đồng thời duy trì ý nghĩa trong các ranh giới xác định. Một sự trừu tượng như vậy được gọi là một mô hình . Việc triển khai " Mô hình song song" (PM) như vậy sẽ tạo ra siêu dữ liệu được ánh xạ công nhân (dấu thời gian) giống như các phép tính thực tế, nếu dữ liệu được thu thập. Siêu dữ liệu do mô hình tạo ra cho phép dự đoán các số liệu của các phép tính song song theo các ràng buộc nhất định.

hinh 4

Một trong hai mô hình con trong PM được xác định ở đây là Mô hình phân phối (DM) . Các DM giải thích cách các đơn vị công tác (taskels) nguyên tử được phân phối qua công nhân song song và thời gian , khi không có các yếu tố khác hơn chunksize-thuật toán tương ứng, số lượng người lao động, đầu vào-iterable (số taskels) và thời gian tính toán của họ được coi . Điều này có nghĩa bất kỳ hình thức chi phí được không bao gồm.

Để có được PM hoàn chỉnh , DM được mở rộng với Mô hình trên không (OM) , đại diện cho nhiều dạng khác nhau của Chi phí song song (PO) . Một mô hình như vậy cần được hiệu chỉnh cho từng nút riêng lẻ (phần cứng, phụ thuộc hệ điều hành). Có bao nhiêu dạng chi phí được biểu diễn trong một OM được bỏ ngỏ và vì vậy có thể tồn tại nhiều OM với mức độ phức tạp khác nhau. Mức độ chính xác mà OM đã thực hiện cần được xác định bởi trọng số tổng thể của PO cho phép tính cụ thể. Nguyên công ngắn hơn dẫn đến trọng lượng PO cao hơn , do đó đòi hỏi OM chính xác hơn nếu chúng tôi cố gắng dự đoán Hiệu quả song song (PE) .


6.2 Lịch trình song song (PS)

Các Schedule Parallel là một đại diện hai chiều của việc tính toán song song, nơi trục x đại diện cho thời gian và trục y đại diện cho một hồ bơi của người lao động song song. Số lượng công nhân và tổng thời gian tính toán đánh dấu phần mở rộng của một hình chữ nhật, trong đó các hình chữ nhật nhỏ hơn được vẽ vào. Những hình chữ nhật nhỏ hơn này đại diện cho các đơn vị nguyên tử của công việc (nhiệm vụ).

Dưới đây, bạn tìm thấy hình ảnh của một PS được vẽ bằng dữ liệu từ thuật toán kích thước khối của DM of Pool cho Kịch bản dày đặc .

hình 5

  • Trục x được chia thành các đơn vị thời gian bằng nhau, trong đó mỗi đơn vị tượng trưng cho thời gian tính toán mà nhiệm vụ yêu cầu.
  • Trục y được chia thành số lượng xử lý công nhân mà nhóm sử dụng.
  • Nhiệm vụ ở đây được hiển thị dưới dạng hình chữ nhật nhỏ nhất có màu lục lam, được đưa vào dòng thời gian (lịch trình) của một quy trình công nhân ẩn danh.
  • Nhiệm vụ là một hoặc nhiều nhiệm vụ trong dòng thời gian của worker liên tục được đánh dấu với cùng một màu sắc.
  • Đơn vị thời gian chạy không tải được thể hiện thông qua các ô màu đỏ.
  • Lịch trình song song được chia thành nhiều phần. Phần cuối cùng là phần đuôi.

Tên của các bộ phận được sáng tác có thể được nhìn thấy trong hình dưới đây.

hình 6

Trong một PM hoàn chỉnh bao gồm OM , Idling Share không giới hạn ở phần đuôi, mà còn bao gồm không gian giữa các nhiệm vụ và thậm chí giữa các nhiệm vụ.


6.3 Hiệu quả

Các Mô hình được giới thiệu ở trên cho phép định lượng tỷ lệ sử dụng công nhân. Chúng ta có thể phân biệt:

  • Hiệu quả phân phối (DE) - được tính với sự trợ giúp của DM (hoặc một phương pháp đơn giản hóa cho Kịch bản dày đặc ).
  • Hiệu quả song song (PE) - được tính toán với sự trợ giúp của PM đã hiệu chỉnh (dự đoán) hoặc được tính toán từ siêu dữ liệu của các phép tính thực.

Điều quan trọng cần lưu ý là hiệu quả được tính toán không tự động tương quan với tính toán tổng thể nhanh hơn cho một vấn đề song song nhất định. Việc sử dụng công nhân trong ngữ cảnh này chỉ phân biệt giữa công nhân có nhiệm vụ đã bắt đầu nhưng chưa hoàn thành và công nhân không có nhiệm vụ "mở" như vậy. Điều đó có nghĩa là, khả năng chạy không tải trong khoảng thời gian của tác vụ không được đăng ký.

Tất cả các hiệu quả được đề cập ở trên về cơ bản có được bằng cách tính toán thương số của bộ phận Chia sẻ bận rộn / Lịch trình song song . Sự khác biệt giữa DEPE đi kèm với Tỷ lệ bận rộn chiếm một phần nhỏ hơn của Lịch trình song song tổng thể cho PM kéo dài trên không .

Câu trả lời này sẽ chỉ thảo luận thêm về một phương pháp đơn giản để tính DE cho Kịch bản dày đặc. Điều này đủ thích hợp để so sánh các thuật toán kích thước khối khác nhau, vì ...

  1. ... DM là một phần của PM , thay đổi với các thuật toán kích thước khối khác nhau được sử dụng.
  2. ... Kịch bản dày đặc với thời lượng tính toán bằng nhau trên mỗi tác vụ mô tả "trạng thái ổn định", mà các khoảng thời gian này không nằm ngoài phương trình. Bất kỳ kịch bản nào khác sẽ chỉ dẫn đến kết quả ngẫu nhiên vì thứ tự của các nguyên công sẽ quan trọng.

6.3.1 Hiệu quả phân phối tuyệt đối (ADE)

Nói chung, hiệu quả cơ bản này có thể được tính bằng cách chia Tỷ lệ bận rộn cho toàn bộ tiềm năng của Lịch trình song song :

Hiệu quả phân phối tuyệt đối (ADE) = Chia sẻ bận rộn / Lịch trình song song

Đối với Kịch bản dày đặc , mã tính toán đơn giản trông giống như sau:

# mp_utils.py

def calc_ade(n_workers, len_iterable, n_chunks, chunksize, last_chunk):
    """Calculate Absolute Distribution Efficiency (ADE).

    `len_iterable` is not used, but contained to keep a consistent signature
    with `calc_rde`.
    """
    if n_workers == 1:
        return 1

    potential = (
        ((n_chunks // n_workers + (n_chunks % n_workers > 1)) * chunksize)
        + (n_chunks % n_workers == 1) * last_chunk
    ) * n_workers

    n_full_chunks = n_chunks - (chunksize > last_chunk)
    taskels_in_regular_chunks = n_full_chunks * chunksize
    real = taskels_in_regular_chunks + (chunksize > last_chunk) * last_chunk
    ade = real / potential

    return ade

Nếu không có Chia sẻ không hoạt động , Chia sẻ bận sẽ bằng với Lịch trình song song , do đó chúng tôi nhận được ADE là 100%. Trong mô hình đơn giản của chúng tôi, đây là một kịch bản mà tất cả các quy trình có sẵn sẽ bận rộn trong suốt thời gian cần thiết để xử lý tất cả các tác vụ. Nói cách khác, toàn bộ công việc đạt hiệu quả song song 100%.

Nhưng tại sao ở đây tôi cứ gọi PEPE tuyệt đối ?

Để hiểu được điều đó, chúng ta phải xem xét một trường hợp có thể xảy ra đối với chunksize (cs) đảm bảo tính linh hoạt trong lịch trình tối đa (ngoài ra, số lượng người Tây Nguyên có thể có. Có trùng hợp không?):

__________________________________ ~ MỘT ~ __________________________________

Ví dụ: nếu chúng ta có bốn quy trình công nhân và 37 nhiệm vụ, sẽ có công nhân chạy không tải ngay cả với chunksize=1, chỉ vì n_workers=4không phải là ước của 37. Phần còn lại của phép chia 37/4 là 1. Nhiệm vụ duy nhất còn lại này sẽ phải là được xử lý bởi một công nhân duy nhất, trong khi ba người còn lại đang chạy không tải.

Tương tự như vậy, vẫn sẽ có một công nhân chạy không tải với 39 nguyên công, bạn có thể xem hình bên dưới.

hình 7

Khi bạn so sánh Lịch biểu song song phía trên cho chunksize=1với phiên bản bên dưới cho chunksize=3, bạn sẽ nhận thấy rằng Lịch biểu song song phía trên nhỏ hơn, dòng thời gian trên trục x ngắn hơn. Bây giờ nó sẽ trở nên rõ ràng, các khối lớn hơn bất ngờ như thế nào cũng có thể dẫn đến tăng thời gian tính toán tổng thể, ngay cả đối với các Kịch bản dày đặc .

Nhưng tại sao không chỉ sử dụng độ dài của trục x để tính toán hiệu quả?

Bởi vì chi phí không được chứa trong mô hình này. Nó sẽ khác nhau đối với cả hai khối, do đó trục x không thực sự so sánh trực tiếp. Chi phí vẫn có thể dẫn đến tổng thời gian tính toán lâu hơn như thể hiện trong trường hợp 2 từ hình bên dưới.

hình 8


6.3.2 Hiệu quả phân phối tương đối (RDE)

Các ADE giá trị không chứa các thông tin nếu một tốt hơn phân phối taskels có thể với bộ chunksize để 1. Better đây vẫn là một nhỏ Idling Share .

Để có được một giá trị DE được điều chỉnh cho DE lớn nhất có thể , chúng ta phải chia ADE được xem xét cho ADE mà chúng ta nhận được chunksize=1.

Hiệu quả phân phối tương đối (RDE) = ADE_cs_x / ADE_cs_1

Đây là cách điều này trông như thế nào trong mã:

# mp_utils.py

def calc_rde(n_workers, len_iterable, n_chunks, chunksize, last_chunk):
    """Calculate Relative Distribution Efficiency (RDE)."""
    ade_cs1 = calc_ade(
        n_workers, len_iterable, n_chunks=len_iterable,
        chunksize=1, last_chunk=1
    )
    ade = calc_ade(n_workers, len_iterable, n_chunks, chunksize, last_chunk)
    rde = ade / ade_cs1

    return rde

RDE , được định nghĩa ở đây như thế nào, về bản chất là một câu chuyện về phần đuôi của một Lịch trình song song . RDE bị ảnh hưởng bởi kích thước khối hiệu quả tối đa có trong phần đuôi. (Đuôi này có thể có độ dài trục x chunksizehoặc last_chunk.) Điều này dẫn đến kết quả là RDE tự nhiên hội tụ 100% (thậm chí) cho tất cả các loại "đuôi trông" như thể hiện trong hình bên dưới.

hình 9

RDE thấp ...

  • là một gợi ý mạnh mẽ cho tiềm năng tối ưu hóa.
  • tự nhiên ít có khả năng lặp lại hơn, vì phần đuôi tương đối của Lịch biểu song song tổng thể thu hẹp lại.

Vui lòng tìm Phần II của câu trả lời này tại đây .


51
Một trong những câu trả lời hoành tráng nhất mà tôi đã thấy trên SO.
Christian Long

4
Oh này đã trả lời ngắn gọn của bạn: P
d_kennetz

1
Nhưng thực tế .. đây là một câu trả lời xuất sắc. Tôi đã gắn dấu sao câu hỏi cho các trường hợp trong tương lai mà tôi muốn hiểu rõ hơn điều này. Đọc lướt qua nó đã dạy tôi rất nhiều điều rồi! Cảm ơn
d_kennetz

2
@ L.Iridium Không có chi! Tôi đã sử dụng matplotlib nếu có thể và nếu không thì ... LibreOffice calc + Pinta (chỉnh sửa hình ảnh cơ bản). Vâng, tôi biết ... nhưng nó hoạt động, bằng cách nào đó. ;)
Darkonaut

2
Câu trả lời đầu tiên với một bảng nội dung được xem trên SO.
tly_alex

51

Về câu trả lời này

Câu trả lời này là Phần II của câu trả lời được chấp nhận ở trên .


7. Thuật toán Chunksize-Algorithm của Naive vs.

Trước khi đi vào chi tiết, hãy xem xét hai gif dưới đây. Đối với một loạt các iterableđộ dài khác nhau , chúng chỉ ra cách hai thuật toán so sánh phân chia đoạn được truyền iterable(lúc đó sẽ là một chuỗi) và cách các tác vụ kết quả có thể được phân phối. Thứ tự của các công nhân là ngẫu nhiên và số lượng nhiệm vụ được phân phối cho mỗi công nhân trong thực tế có thể khác với hình ảnh này đối với các tác vụ nhẹ và hoặc các nhiệm vụ trong Kịch bản rộng. Như đã đề cập trước đó, chi phí cũng không được bao gồm ở đây. Tuy nhiên, đối với các tác vụ đủ nặng trong một Kịch bản dày đặc với kích thước dữ liệu được truyền có thể bỏ qua, các phép tính thực tế sẽ vẽ ra một bức tranh rất giống nhau.

cs_4_50

cs_200_250

Như đã trình bày trong chương " 5. Thuật toán kích thước khối của Pool ", với thuật toán kích thước khối của Pool, số lượng khối sẽ ổn định ở mức n_chunks == n_workers * 4cho các đoạn lặp đủ lớn, trong khi nó tiếp tục chuyển đổi giữa n_chunks == n_workersn_chunks == n_workers + 1với cách tiếp cận đơn giản. Đối với thuật toán ngây thơ được áp dụng: Bởi vì n_chunks % n_workers == 1Truecho n_chunks == n_workers + 1, một phần mới sẽ được tạo ra nơi chỉ một công nhân duy nhất sẽ được sử dụng.

Naive Chunksize-Thuật toán:

Bạn có thể nghĩ rằng bạn đã tạo các nhiệm vụ với cùng một số lượng nhân công, nhưng điều này sẽ chỉ đúng đối với các trường hợp không có phần còn lại cho len_iterable / n_workers. Nếu có một phần còn lại, sẽ có một phần mới chỉ có một nhiệm vụ cho một nhân viên duy nhất. Lúc đó việc tính toán của bạn sẽ không còn song song nữa.

Dưới đây, bạn thấy một hình tương tự như hình được hiển thị trong chương 5, nhưng hiển thị số phần thay vì số phần. Đối với thuật toán chunksize-đầy đủ của Pool ( n_pool2), n_sectionssẽ ổn định ở yếu tố được mã hóa cứng, khét tiếng 4. Đối với thuật toán ngây thơ, n_sectionssẽ xen kẽ giữa một và hai.

hình 10

Đối với thuật toán kích thước khối của Pool, việc ổn định n_chunks = n_workers * 4thông qua xử lý bổ sung được đề cập trước đó , ngăn cản việc tạo phần mới ở đây và giữ cho Idling Share giới hạn ở một công nhân trong thời gian lặp lại đủ lâu. Không chỉ vậy, thuật toán sẽ tiếp tục thu nhỏ kích thước tương đối của Idling Share , dẫn đến giá trị RDE hội tụ thành 100%.

"Đủ dài" cho n_workers=4len_iterable=210ví dụ. Đối với các vòng lặp bằng hoặc lớn hơn thế, Idling Share sẽ bị giới hạn ở một công nhân, một đặc điểm ban đầu bị mất do 4-multiplication trong thuật toán chunksize-ngay từ đầu.

hình 11

Thuật toán phân khối ngây thơ cũng hội tụ 100%, nhưng nó hoạt động chậm hơn. Hiệu ứng hội tụ chỉ phụ thuộc vào thực tế là phần tương đối của đuôi co lại trong trường hợp sẽ có hai phần. Đuôi này chỉ có một công nhân được tuyển dụng bị giới hạn ở độ dài trục x n_workers - 1, phần còn lại tối đa có thể cho len_iterable / n_workers.

Các giá trị RDE thực tế khác nhau như thế nào đối với thuật toán kích thước khối ngây thơ và của Pool?

Bên dưới, bạn tìm thấy hai bản đồ nhiệt hiển thị các giá trị RDE cho tất cả độ dài có thể lặp lại lên đến 5000, cho tất cả số lượng công nhân từ 2 đến 100. Thang màu đi từ 0,5 đến 1 (50% -100%). Bạn sẽ nhận thấy nhiều vùng tối hơn (giá trị RDE thấp hơn) cho thuật toán ngây thơ trong bản đồ nhiệt bên trái. Ngược lại, thuật toán kích thước khối của Pool ở bên phải vẽ ra một bức tranh nhiều ánh nắng hơn.

hình 12

Độ dốc đường chéo của góc tối bên trái phía dưới so với góc sáng phía trên bên phải, một lần nữa cho thấy sự phụ thuộc vào số lượng công nhân để gọi là "có thể lặp lại dài".

Nó có thể trở nên tồi tệ như thế nào với mỗi thuật toán?

Với thuật toán kích thước khối của Pool, giá trị RDE là 81,25% là giá trị thấp nhất cho phạm vi công nhân và độ dài có thể lặp được chỉ định ở trên:

hình 13

Với thuật toán kích thước khối ngây thơ, mọi thứ có thể trở nên tồi tệ hơn nhiều. RDE được tính toán thấp nhất ở đây là 50,72%. Trong trường hợp này, gần một nửa thời gian tính toán chỉ có một nhân viên chạy! Vì vậy, hãy coi chừng, những chủ nhân kiêu hãnh của Knights Landing . ;)

hình 14


8. Kiểm tra thực tế

Trong các chương trước, chúng tôi đã coi là một mô hình đơn giản hóa cho vấn đề phân phối thuần túy toán học, được loại bỏ khỏi các chi tiết phức tạp khiến đa xử lý trở thành một chủ đề hóc búa ngay từ đầu. Để hiểu rõ hơn về cách xa phân phối Model (DM) một mình có thể đóng góp để giải thích việc sử dụng lao động quan sát trong thực tế, bây giờ chúng ta sẽ mất một cái nhìn tại Lịch Parallel được vẽ bởi thực tính toán.

Thiết lập

Tất cả các biểu đồ sau đây đều đề cập đến việc thực thi song song một hàm giả đơn giản, ràng buộc cpu, hàm này được gọi với nhiều đối số khác nhau để chúng ta có thể quan sát Biểu đồ song song được vẽ thay đổi như thế nào tùy thuộc vào các giá trị đầu vào. "Công việc" bên trong hàm này chỉ bao gồm lặp lại trên một đối tượng phạm vi. Điều này đã đủ để giữ cho một lõi bận rộn vì chúng tôi chuyển một số lượng lớn vào. Tùy chọn hàm có thêm một số tác vụ-duy nhất datamà chỉ được trả về không thay đổi. Vì mọi nhiệm vụ đều bao gồm cùng một lượng công việc chính xác, chúng tôi vẫn đang xử lý một Kịch bản dày đặc ở đây.

Hàm được trang trí bằng một trình bao bọc lấy dấu thời gian với độ phân giải ns (Python 3.7+). Dấu thời gian được sử dụng để tính toán khoảng thời gian của một nhiệm vụ và do đó cho phép vẽ một Lịch trình song song theo kinh nghiệm.

@stamp_taskel
def busy_foo(i, it, data=None):
    """Dummy function for CPU-bound work."""
    for _ in range(int(it)):
        pass
    return i, data


def stamp_taskel(func):
    """Decorator for taking timestamps on start and end of decorated
    function execution.
    """
    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time_ns()
        result = func(*args, **kwargs)
        end_time = time_ns()
        return (current_process().name, (start_time, end_time)), result
    return wrapper

Phương thức bản đồ sao của Pool cũng được trang trí theo cách mà chỉ bản đồ sao gọi chính nó được tính thời gian. "Bắt đầu" và "kết thúc" của lệnh gọi này xác định tối thiểu và tối đa trên trục x của Lịch trình song song được tạo.

Chúng ta sẽ quan sát tính toán của 40 tác vụ trên bốn quy trình công nhân trên một máy với các thông số kỹ thuật sau: Python 3.7.1, Ubuntu 18.04.2, CPU Intel® Core ™ i7-2600K @ 3,40GHz × 8

Các giá trị đầu vào sẽ khác nhau là số lần lặp lại trong vòng lặp (30k, 30M, 600M) và kích thước dữ liệu gửi bổ sung (mỗi tác vụ, numpy-ndarray: 0 MiB, 50 MiB).

...
N_WORKERS = 4
LEN_ITERABLE = 40
ITERATIONS = 30e3  # 30e6, 600e6
DATA_MiB = 0  # 50

iterable = [
    # extra created data per taskel
    (i, ITERATIONS, np.arange(int(DATA_MiB * 2**20 / 8)))  # taskel args
    for i in range(LEN_ITERABLE)
]


with Pool(N_WORKERS) as pool:
    results = pool.starmap(busy_foo, iterable)

Các lần chạy được hiển thị bên dưới đã được lựa chọn cẩn thận để có cùng thứ tự các phần để bạn có thể phát hiện ra sự khác biệt tốt hơn so với Lịch trình song song từ Mô hình phân phối, nhưng đừng quên thứ tự mà công nhân nhận nhiệm vụ của họ là không xác định.

Dự đoán DM

Để nhắc lại, Mô hình Phân phối "dự đoán" một Lịch trình Song song như chúng ta đã thấy trước đây trong chương 6.2:

hình 15

RUN đầu tiên: 30k lần lặp & 0 dữ liệu MiB cho mỗi tác vụ

hình 16

Lần chạy đầu tiên của chúng tôi ở đây là rất ngắn, các nhiệm vụ rất "nhẹ". Toàn bộ cuộc gọi pool.starmap()chỉ mất tổng cộng 14,5 mili giây. Bạn sẽ nhận thấy, trái ngược với DM , việc chạy không tải không bị giới hạn ở phần đuôi, mà còn diễn ra giữa các tác vụ và thậm chí giữa các nguyên công. Đó là bởi vì lịch trình thực sự của chúng tôi ở đây tự nhiên bao gồm tất cả các loại chi phí. Chạy không tải ở đây có nghĩa là mọi thứ bên ngoài nhiệm vụ. Việc chạy không tải thực sự có thể xảy ra trong một nhiệm vụ không được nắm bắt như thế nào đã được đề cập trước đây.

Hơn nữa, bạn có thể thấy rằng không phải tất cả công nhân đều nhận được nhiệm vụ của họ cùng một lúc. Đó là do thực tế là tất cả công nhân đều được cho ăn qua một phần chung inqueuevà mỗi lần chỉ có một công nhân có thể đọc từ đó. Điều tương tự cũng áp dụng cho outqueue. Điều này có thể gây ra những xáo trộn lớn hơn ngay khi bạn đang truyền dữ liệu có kích thước không biên như thế nào chúng ta sẽ thấy sau.

Hơn nữa, bạn có thể thấy rằng mặc dù thực tế là mọi tác vụ đều bao gồm cùng một lượng công việc, nhưng khoảng thời gian đo được thực tế cho một nhiệm vụ khác nhau rất nhiều. Các nhiệm vụ được phân phối cho worker-3 và worker-4 cần nhiều thời gian hơn các nhiệm vụ được xử lý bởi hai công nhân đầu tiên. Đối với lần chạy này, tôi nghi ngờ đó là do tính năng tăng áp không còn khả dụng trên các lõi của worker-3/4 tại thời điểm đó, vì vậy chúng đã xử lý các tác vụ của mình với tốc độ xung nhịp thấp hơn.

Toàn bộ tính toán nhẹ đến mức các yếu tố hỗn loạn do phần cứng hoặc hệ điều hành đưa vào có thể làm lệch PS đáng kể. Việc tính toán là "lá trên gió" và công nghệ DM có rất ít ý nghĩa, ngay cả đối với một kịch bản phù hợp về mặt lý thuyết.

RUN thứ 2: 30 triệu lần lặp & 0 dữ liệu MiB cho mỗi tác vụ

hình 17

Tăng số lần lặp lại trong vòng lặp từ 30.000 lên 30 triệu, dẫn đến một Lịch trình song song thực sự gần giống với một lịch trình hoàn hảo được dự đoán bởi dữ liệu do DM cung cấp , xin lỗi! Tính toán cho mỗi tác vụ hiện đủ nặng để loại bỏ các bộ phận chạy không tải ở đầu và ở giữa, cho phép chỉ hiển thị Chia sẻ không tải lớn mà DM đã dự đoán.

RUN thứ 3: 30 triệu lần lặp & 50 MiB dữ liệu cho mỗi tác vụ

hình18

Giữ 30 triệu lần lặp, nhưng bổ sung gửi 50 MiB cho mỗi nhiệm vụ qua lại sẽ làm lệch hình ảnh một lần nữa. Ở đây, hiệu ứng xếp hàng có thể nhìn thấy rõ. Worker-4 cần đợi nhiệm vụ thứ hai lâu hơn Worker-1. Bây giờ hãy tưởng tượng lịch trình này với 70 công nhân!

Trong trường hợp các tác vụ rất nhẹ về mặt tính toán, nhưng đủ khả năng cung cấp một lượng dữ liệu đáng kể dưới dạng tải trọng, thì nút cổ chai của một hàng đợi được chia sẻ duy nhất có thể ngăn cản bất kỳ lợi ích bổ sung nào của việc thêm nhiều nhân viên vào Pool, ngay cả khi chúng được hỗ trợ bởi các lõi vật lý. Trong trường hợp như vậy, Worker-1 có thể được thực hiện với nhiệm vụ đầu tiên của nó và đang chờ một nhiệm vụ mới ngay cả trước khi Worker-40 nhận được nhiệm vụ đầu tiên.

Bây giờ sẽ trở nên rõ ràng tại sao thời gian tính toán Poolkhông phải lúc nào cũng giảm theo số lượng công nhân. Việc gửi một lượng dữ liệu tương đối lớn có thể dẫn đến các tình huống trong đó phần lớn thời gian dành cho việc đợi dữ liệu được sao chép vào không gian địa chỉ của một nhân viên và chỉ một nhân viên có thể được cung cấp cùng một lúc.

RUN thứ 4: 600M lần lặp & 50 MiB dữ liệu cho mỗi tác vụ

hình 19

Ở đây chúng tôi gửi lại 50 MiB, nhưng tăng số lần lặp từ 30M lên 600M, nâng tổng thời gian tính toán từ 10 giây lên 152 giây. Lịch trình song song được vẽ lại , gần khớp hoàn hảo với lịch trình dự đoán, chi phí thông qua việc sao chép dữ liệu bị gạt ra ngoài lề.


9. Kết luận

Phép nhân được thảo luận bằng cách 4tăng tính linh hoạt trong lập kế hoạch, nhưng cũng thúc đẩy sự không đồng đều trong các phân phối nguyên công. Nếu không có phép nhân này, Idling Share sẽ được giới hạn cho một công nhân ngay cả đối với các đoạn lặp ngắn (đối với DM có Kịch bản dày đặc). Thuật toán phân khối của Pool cần các đầu vào-lặp lại có kích thước nhất định để lấy lại đặc điểm đó.

Như câu trả lời này hy vọng đã hiển thị, thuật toán phân khối của Pool dẫn đến việc sử dụng lõi tốt hơn trung bình so với cách tiếp cận ngây thơ, ít nhất là đối với trường hợp trung bình và miễn là không tính đến chi phí. Thuật toán ngây thơ ở đây có thể có Hiệu quả phân phối (DE) thấp đến ~ 51%, trong khi thuật toán phân khối của Pool có mức thấp nhất là ~ 81%. Tuy nhiên, DE không bao gồm Chi phí song song (PO) như IPC. Chương 8 đã chỉ ra rằng DE vẫn có thể có sức mạnh dự đoán tuyệt vời cho Kịch bản dày đặc với chi phí thấp hơn.

Mặc dù thực tế là thuật toán phân khối của Pool đạt được DE cao hơn so với cách tiếp cận ngây thơ, nhưng nó không cung cấp các phân phối nguyên công tối ưu cho mọi chòm sao đầu vào. Mặc dù thuật toán phân khúc tĩnh đơn giản không thể tối ưu hóa (bao gồm cả chi phí) Hiệu quả song song (PE), không có lý do cố hữu nào khiến nó không thể luôn cung cấp Hiệu quả phân phối tương đối (RDE) là 100%, điều đó có nghĩa là DE giống như với chunksize=1. Một thuật toán kích thước khối đơn giản chỉ bao gồm toán học cơ bản và có thể tự do "cắt miếng bánh" theo bất kỳ cách nào.

Không giống như việc Pool triển khai thuật toán "phân khúc kích thước bằng nhau", thuật toán "phân khúc kích thước chẵn" sẽ cung cấp RDE là 100% cho mọi len_iterable/ n_workerskết hợp. Thuật toán phân chia kích thước chẵn sẽ phức tạp hơn một chút để triển khai trong nguồn của Pool, nhưng có thể được điều chỉnh dựa trên thuật toán hiện có chỉ bằng cách đóng gói các tác vụ ra bên ngoài (Tôi sẽ liên kết từ đây trong trường hợp tôi đánh rơi Q / A trên làm thế nào để làm điều đó).


6

Tôi nghĩ rằng một phần của những gì bạn đang thiếu là ước tính ngây thơ của bạn giả định rằng mỗi đơn vị công việc mất cùng một khoảng thời gian, trong trường hợp đó chiến lược của bạn sẽ là tốt nhất. Nhưng nếu một số công việc hoàn thành sớm hơn những công việc khác thì một số lõi có thể trở nên nhàn rỗi chờ các công việc chậm hoàn thành.

Do đó, bằng cách chia nhỏ các khối thành 4 lần nhiều hơn, thì nếu một đoạn hoàn thành sớm thì lõi đó có thể bắt đầu đoạn tiếp theo (trong khi các lõi khác tiếp tục hoạt động trên đoạn chậm hơn của chúng).

Tôi không biết tại sao họ lại chọn chính xác hệ số 4 nhưng sẽ là sự đánh đổi giữa việc giảm thiểu chi phí của mã bản đồ (muốn các phần lớn nhất có thể) và cân bằng các phần mất nhiều thời gian khác nhau (muốn phần nhỏ nhất có thể ).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.