Làm thế nào để chi phí tính toán của một hoạt động mpi_allgather so với một hoạt động thu thập / phân tán?


11

Tôi đang giải quyết một vấn đề có thể được xử lý song song bằng cách sử dụng một thao tác mpi_allgather hoặc một mpi_scatter và một thao tác mpi_gather. Các hoạt động này được gọi trong vòng lặp while, vì vậy chúng có thể được gọi nhiều lần.

Khi triển khai với sơ đồ MPI_allgather, tôi đang thu thập một vectơ phân tán vào tất cả các quy trình để giải quyết ma trận trùng lặp. Trong phần triển khai khác, tôi tập hợp vectơ phân tán vào một bộ xử lý (nút gốc), giải hệ thống tuyến tính trên bộ xử lý này và sau đó phân tán vectơ giải pháp trở lại tất cả các quy trình.

Tôi tò mò muốn biết liệu chi phí của một hoạt động cộng đồng có cao hơn đáng kể so với các hoạt động phân tán và thu thập kết hợp hay không. Liệu độ dài của tin nhắn đóng một vai trò quan trọng trong sự phức tạp của nó? Có khác nhau giữa các triển khai của mpi?

Biên tập:


Vui lòng mô tả cấu trúc của giao tiếp và kích thước liên quan. Một MPI_Scattertheo sau MPI_Gatherkhông cung cấp cùng một ngữ nghĩa giao tiếp như MPI_Allgather. Có lẽ có sự dư thừa liên quan khi bạn thể hiện hoạt động theo một trong hai cách?
Jed Brown

Paul, Jed là đúng, bạn có nghĩa là MPI_Gathertheo sau bởi một MPI_Bcast?
Aron Ahmadia

@JedBrown: Tôi đã thêm một chút thông tin.
Paul

@AronAhmadia: Tôi không nghĩ mình nên sử dụng MPI_Bcast vì tôi đang gửi một phần của vectơ, cho mỗi quy trình, không phải toàn bộ vectơ. Lý do của tôi là một tin nhắn ngắn sẽ gửi nhanh hơn so với tin nhắn lớn hơn, nói chung. Điều này có nghĩa không?
Paul

Là ma trận đã được phân phối dư thừa? Nó đã được bao thanh toán? Do nhiều tiến trình chia sẻ cùng một bộ nhớ cache và bus bộ nhớ? (Điều đó sẽ ảnh hưởng đến tốc độ giải quyết các hệ thống dư thừa.) Các hệ thống lớn / đắt như thế nào? Tại sao phải giải quyết vấn đề?
Jed Brown

Câu trả lời:


9

Đầu tiên, câu trả lời chính xác phụ thuộc vào: (1) cách sử dụng, tức là các đối số đầu vào chức năng, (2) chất lượng và chi tiết triển khai MPI và (3) phần cứng bạn đang sử dụng. Thông thường, (2) và (3) có liên quan, chẳng hạn như khi nhà cung cấp phần cứng tối ưu hóa MPI cho mạng của họ.

Nói chung, việc hợp nhất các tập hợp MPI sẽ tốt hơn cho các tin nhắn nhỏ hơn, vì chi phí khởi động có thể không cần thiết và việc đồng bộ hóa bằng cách chặn tập thể nên được giảm thiểu nếu có sự khác biệt về thời gian tính toán giữa các cuộc gọi. Đối với các tin nhắn lớn hơn, mục tiêu nên là để giảm thiểu lượng dữ liệu được gửi.

Ví dụ, về lý thuyết, MPI_Reduce_scatter_blocknên tốt hơn so với MPI_Reducetheo sau MPI_Scatter, mặc dù cái trước thường được thực hiện theo nghĩa sau, vì vậy không có lợi thế thực sự. Có một mối tương quan giữa chất lượng thực hiện và tần suất sử dụng trong hầu hết các triển khai MPI và các nhà cung cấp rõ ràng tối ưu hóa các chức năng mà hợp đồng máy này yêu cầu.

Mặt khác, nếu một người MPI_Reduce_scatter_blocksử dụng Blue Gene, thì việc sử dụng MPI_Allreduce, giao tiếp nhiều hơn MPI_ReduceMPI_Scatterkết hợp, thực sự nhanh hơn một chút. Đây là điều mà tôi mới phát hiện ra và là một sự vi phạm thú vị về nguyên tắc tự thống nhất hiệu suất trong MPI (nguyên tắc này được mô tả chi tiết hơn trong "Nguyên tắc thực hiện MPI tự thống nhất" ).

Trong trường hợp cụ thể của phân tán + thu thập so với phân phối, hãy xem xét rằng trước đây, tất cả dữ liệu phải được chuyển đến và từ một quy trình duy nhất, điều này làm cho nó bị nghẽn cổ chai, trong khi đó, tất cả các dữ liệu có thể chảy vào và ra khỏi tất cả các cấp bậc ngay lập tức , bởi vì tất cả các cấp bậc có một số dữ liệu để gửi cho tất cả các cấp bậc khác. Tuy nhiên, gửi dữ liệu từ tất cả các nút cùng một lúc không nhất thiết là một ý tưởng hay trên một số mạng.

Cuối cùng, cách tốt nhất để trả lời câu hỏi này là làm như sau trong mã của bạn và trả lời câu hỏi bằng thí nghiệm.

#ifdef TWO_MPI_CALLS_ARE_BETTER_THAN_ONE
  MPI_Scatter(..)
  MPI_Gather(..)
#else
  MPI_Allgather(..)
#endif

Một tùy chọn thậm chí tốt hơn là để mã của bạn đo lường bằng thực nghiệm trong hai lần lặp đầu tiên, sau đó sử dụng cái nào nhanh hơn cho các lần lặp còn lại:

const int use_allgather = 1;
const int use_scatter_then_gather = 2;

int algorithm = 0;
double t0 = 0.0, t1 = 0.0, dt1 = 0.0, dt2 = 0.0;

while (..)
{
    if ( (iteration==0 && algorithm==0) || algorithm==use_scatter_then_gather )
    {
        t0 = MPI_Wtime();
        MPI_Scatter(..);
        MPI_Gather(..);
        t1 = MPI_Wtime();
        dt1 = t1-t0;
    } 
    else if ( (iteration==1 && algorithm==0) || algorithm==use_allgather)
    {
        t0 = MPI_Wtime();
        MPI_Allgather(..);
        t1 = MPI_Wtime();
        dt2 = t1-t0;
    }

    if (iteration==1)
    {
       dt2<dt1 ? algorithm=use_allgather : algorithm=use_scatter_then_gather;
    }
}

Đó không phải là một ý tưởng tồi ... thời gian cả hai và xác định cái nào nhanh hơn.
Paul

Hầu hết các môi trường HPC hiện đại phần cứng tối ưu hóa nhiều cuộc gọi MPI. Đôi khi điều này dẫn đến sự tăng tốc đáng kinh ngạc, lần khác, những hành vi cực kỳ mờ đục. Hãy cẩn thận!
meawoppl

@Jeff: Tôi mới nhận ra rằng tôi đã bỏ qua một chi tiết quan trọng ... Tôi đang làm việc với một cụm tại Trung tâm Điện toán Nâng cao Texas, nơi họ sử dụng mạng cấu trúc liên kết cây béo. Điều đó có ảnh hưởng đến sự khác biệt về hiệu suất giữa các phương pháp thu thập toàn bộ và thu thập phát sóng không?
Paul

@Paul Topology không phải là yếu tố chi phối ở đây, nhưng một cây mập có băng thông chia đôi đáng kể, điều này sẽ khiến cho tất cả mọi người đều rẻ. Tuy nhiên, thu thập phải luôn luôn rẻ hơn so với allgather. Đối với các tin nhắn lớn hơn, nó có thể ít hơn hệ số 2.
Jeff

5

Jeff hoàn toàn đúng về cách duy nhất để chắc chắn là đo lường - xét cho cùng, chúng tôi là các nhà khoa học, và đây là một câu hỏi thực nghiệm - và đưa ra lời khuyên tuyệt vời về cách thực hiện các phép đo như vậy. Bây giờ hãy để tôi đưa ra một quan điểm trái ngược (hoặc, có thể, bổ sung).

Có một sự khác biệt được thực hiện giữa việc viết một mã được sử dụng rộng rãi và điều chỉnh nó đến một kết thúc cụ thể. Nói chung, chúng tôi đang thực hiện việc đầu tiên - xây dựng mã của chúng tôi để a) chúng tôi có thể sử dụng nó trên nhiều nền tảng khác nhau và b) mã có thể được duy trì và có thể mở rộng trong nhiều năm tới. Nhưng đôi khi chúng tôi đang làm khác - chúng tôi đã phân bổ một năm cho một số máy lớn và chúng tôi đang tăng cường một số mô phỏng lớn cần thiết và chúng tôi cần một đường cơ sở nhất định để thực hiện những gì chúng tôi cần thực hiện trong thời gian thời điểm phân bổ được cấp.

Khi chúng ta viết mã, làm cho nó có thể sử dụng rộng rãi và có thể bảo trì là điều quan trọng hơn nhiều so với việc giảm một vài phần trăm thời gian chạy trên một máy cụ thể. Trong trường hợp này, điều đúng đắn cần làm là hầu như luôn luôn sử dụng thói quen mô tả đúng nhất những gì bạn muốn làm - đây thường là cuộc gọi cụ thể nhất bạn có thể thực hiện theo cách bạn muốn. Ví dụ, nếu một allgather hoặc allgatherv thẳng thực hiện những gì bạn muốn, bạn nên sử dụng nó thay vì tự lăn ra khỏi các hoạt động phân tán / gatter. Những lý do là:

  • Mã bây giờ thể hiện rõ hơn những gì bạn đang cố gắng thực hiện, làm cho nó dễ hiểu hơn đối với người tiếp theo đến mã của bạn vào năm sau mà không biết mã đó phải làm gì (người đó cũng có thể là bạn);
  • Tối ưu hóa có sẵn ở cấp MPI cho trường hợp cụ thể hơn không thuộc trường hợp chung này, vì vậy thư viện MPI của bạn có thể giúp bạn; và
  • Cố gắng tự lăn lộn sẽ có khả năng phản tác dụng; ngay cả khi nó hoạt động tốt hơn trên máy X với triển khai MPI Y.ZZ, nó cũng có thể hoạt động kém hơn nhiều khi bạn chuyển sang máy khác hoặc nâng cấp triển khai MPI của bạn.

Trong trường hợp khá phổ biến này, nếu bạn phát hiện ra rằng một số tập thể MPI hoạt động chậm một cách vô lý trên máy của bạn, điều tốt nhất cần làm là gửi báo cáo lỗi với nhà cung cấp mpi; bạn không muốn làm phức tạp phần mềm của chính mình khi cố gắng khắc phục mã ứng dụng, cái gì cần được sửa ở cấp thư viện MPI.

Tuy nhiên . Nếu bạn đang ở chế độ "điều chỉnh" - bạn có mã làm việc, bạn phải tăng quy mô rất lớn trong một khoảng thời gian ngắn (ví dụ: phân bổ dài một năm) và bạn đã lập hồ sơ mã của mình và phát hiện ra rằng phần đặc biệt này trong mã của bạn là một nút cổ chai, sau đó sẽ rất hợp lý khi bắt đầu thực hiện các điều chỉnh rất cụ thể này. Hy vọng rằng chúng sẽ không phải là một phần dài hạn trong mã của bạn - lý tưởng là những thay đổi này sẽ vẫn nằm trong một số nhánh cụ thể của dự án trong kho lưu trữ của bạn - nhưng bạn có thể cần phải thực hiện chúng. Trong trường hợp đó, mã hóa của hai cách tiếp cận khác nhau được phân biệt bởi các chỉ thị tiền xử lý hoặc cách tiếp cận "tự động hóa" cho một mẫu truyền thông cụ thể - có thể có nhiều ý nghĩa.

Vì vậy, tôi không đồng ý với Jeff, tôi chỉ muốn thêm một số bối cảnh về thời điểm bạn nên quan tâm đủ với các câu hỏi hiệu suất tương đối như vậy để sửa đổi mã của bạn để đối phó với nó.


Tôi nghĩ rằng tôi quan tâm đến tính di động hơn là tối ưu hóa vào thời điểm này, nhưng tôi luôn tò mò muốn biết liệu có một triển khai nào tương tự như di động nhưng nhanh hơn không :)
Paul
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.