2010-02-13 32 views
12

Tôi đang làm việc trên thư viện song song cho ngôn ngữ lập trình D. Bây giờ tôi khá hài lòng với các nguyên thủy cơ bản (song song foreach, bản đồ, giảm và nhiệm vụ/tương lai), tôi bắt đầu nghĩ về một số thuật toán song song cấp cao hơn. Trong số các ứng cử viên rõ ràng hơn cho song song là phân loại.(Khi) là loại thực tế song song và cách bạn viết một cách hiệu quả?

Câu hỏi đầu tiên của tôi là, là các phiên bản song song của thuật toán sắp xếp hữu ích trong thế giới thực hay chúng hầu hết là học thuật? Nếu chúng hữu ích, chúng hữu ích ở đâu? Cá nhân tôi hiếm khi sử dụng chúng trong công việc của tôi, đơn giản bởi vì tôi thường cố định tất cả các lõi của mình ở mức 100% bằng cách sử dụng mức độ song song thô hơn nhiều so với một cuộc gọi() duy nhất. Thứ hai, nó có vẻ như sắp xếp nhanh chóng là gần như lúng túng song song cho mảng lớn, nhưng tôi không thể có được tốc độ gần như tuyến tính tôi tin rằng tôi nên nhận được. Các tính năng chính Để sắp xếp nhanh, phần nối tiếp vốn chỉ là phân vùng đầu tiên. Tôi đã thử song song một sắp xếp nhanh chóng, sau mỗi phân vùng, phân loại hai subarrays song song. Trong giả đơn giản:

// I tweaked this number a bunch. Anything smaller than this and the 
// overhead is smaller than the parallelization gains. 
const smallestToParallelize = 500; 

void quickSort(T)(T[] array) { 
    if(array.length < someConstant) { 
     insertionSort(array); 
     return; 
    } 

    size_t pivotPosition = partition(array); 

    if(array.length >= smallestToParallelize) { 
     // Sort left subarray in a task pool thread. 
     auto myTask = taskPool.execute(quickSort(array[0..pivotPosition])); 
     quickSort(array[pivotPosition + 1..$]); 
     myTask.workWait(); 
    } else { 
     // Regular serial quick sort. 
     quickSort(array[0..pivotPosition]); 
     quickSort(array[pivotPosition + 1..$]); 
    } 
} 

Ngay cả đối với các mảng rất lớn, nơi mà thời gian phân vùng đầu tiên diễn là không đáng kể, tôi chỉ có thể nhận được khoảng một tăng tốc 30% trên một lõi kép, so với một phiên bản hoàn toàn serial của thuật toán . Tôi đoán nút cổ chai là quyền truy cập bộ nhớ dùng chung. Bất kỳ cái nhìn sâu sắc về làm thế nào để loại bỏ nút cổ chai này hoặc những gì khác nút cổ chai có thể được?

Chỉnh sửa: Nhóm tác vụ của tôi có một số chuỗi cố định, bằng số lõi trong hệ thống trừ 1 (vì chuỗi chính cũng hoạt động). Ngoài ra, loại chờ đợi tôi đang sử dụng là chờ đợi công việc, tức là nếu nhiệm vụ được bắt đầu nhưng không hoàn thành, chủ đề gọi workWait() sẽ đánh cắp các công việc khác ngoài hồ bơi và thực hiện chúng cho đến khi công việc chờ đợi xong. Nếu tác vụ không được bắt đầu, nó sẽ được hoàn thành trong chuỗi hiện tại. Điều này có nghĩa là chờ đợi không hiệu quả. Miễn là có công việc phải làm, tất cả các chủ đề sẽ được giữ bận rộn.

+0

Tôi không biết làm thế nào để làm cho quicksort bạn parallelize tốt hơn, nhưng có một biến thể gọi là samplesort mà vốn dĩ là nhanh hơn nhiều so với một quicksort vani, và như xa như tôi có thể thấy, nó nên song song nhau. –

Trả lời

7

Hãy nhớ tôi không phải là một chuyên gia về loại song song, và những người làm cho sự nghiệp nghiên cứu ra khỏi loại song song nhưng ...

1) là họ có ích trong thế giới thực.

Tất nhiên, nếu bạn cần sắp xếp thứ gì đó đắt tiền (như dây hoặc tệ hơn) và bạn không phải là hãy cố định tất cả các lõi.

  • nghĩ rằng mã UI khi bạn cần phải sắp xếp một danh sách lớn năng động của chuỗi dựa trên bối cảnh
  • nghĩ một cái gì đó giống như một Barnes-túp lều n-cơ quan sim khi bạn cần phải sắp xếp các hạt

2) Quicksort có vẻ như nó sẽ cung cấp cho một tốc độ tuyến tính, nhưng nó không phải là. Các bước phân vùng là một nút cổ chai tuần tự, bạn sẽ thấy điều này nếu bạn hồ sơ và nó sẽ có xu hướng cap ra tại 2-3x trên một lõi tứ.

Nếu bạn muốn tăng tốc tốt trên hệ thống nhỏ hơn, bạn cần đảm bảo rằng mỗi đầu nhiệm vụ của bạn thực sự nhỏ và lý tưởng bạn sẽ muốn đảm bảo rằng bạn không có quá nhiều luồng chạy, tức là không nhiều hơn 2 trên một lõi kép. Một hồ bơi thread có lẽ không phải là trừu tượng đúng.

Nếu bạn muốn tăng tốc tốt trên hệ thống lớn hơn, bạn sẽ cần phải xem xét các loại quét song song dựa trên quét, có các giấy tờ về điều này. bitonic sắp xếp cũng khá dễ dàng song song như là sắp xếp hợp nhất. Một phân phối song song song song cũng có thể hữu ích, có một trong PPL (nếu bạn không chống lại Visual Studio 11).

3

Tôi không phải chuyên gia nhưng ... đây là những gì tôi muốn xem xét:

Trước hết, tôi đã nghe nói rằng như một quy luật của, các thuật toán mà nhìn vào kích thước nhỏ, một vấn đề ngay từ đầu có xu hướng hoạt động tốt hơn như các thuật toán song song.

Nhìn vào triển khai của bạn, hãy thử chuyển đổi song song/nối tiếp theo cách khác: phân vùng mảng và sắp xếp song song cho đến khi bạn có N phân đoạn, sau đó đi nối tiếp. Nếu bạn ít nhiều nắm lấy một chuỗi mới cho mỗi trường hợp song song, thì N phải là ~ số lõi của bạn. OTOH nếu hồ bơi thread của bạn có kích thước cố định và hoạt động như một hàng đợi các đại biểu sống ngắn, sau đó tôi sẽ sử dụng N ~ 2+ lần số lõi của bạn (để các lõi không hoạt động vì một phân vùng đã hoàn thành nhanh hơn).

chỉnh khác:

  • bỏ qua myTask.wait(); ở cấp địa phương và thay vì có một hàm wrapper mà chờ đợi trên tất cả các nhiệm vụ.
  • Thực hiện việc triển khai nối tiếp riêng biệt của hàm tránh kiểm tra độ sâu.
+0

+1. Lời giải thích tuyệt vời .. – bragboy

1

"Câu hỏi đầu tiên của tôi là, là phiên bản song song của thuật toán sắp xếp hữu ích trong thế giới thực" - phụ thuộc vào kích thước của tập dữ liệu bạn đang làm việc. Đối với các bộ dữ liệu nhỏ, câu trả lời là không. Đối với các tập dữ liệu lớn hơn, nó không chỉ phụ thuộc vào kích thước của tập dữ liệu mà còn phụ thuộc vào kiến ​​trúc cụ thể của hệ thống.

Một trong những yếu tố hạn chế sẽ ngăn sự gia tăng mong đợi về hiệu suất là bố cục bộ nhớ cache của hệ thống. Nếu dữ liệu có thể phù hợp với bộ nhớ cache L1 của lõi, thì có rất ít để đạt được bằng cách phân loại trên nhiều lõi khi bạn phải chịu hình phạt của bộ nhớ cache L1 giữa mỗi lần lặp lại của thuật toán sắp xếp.

Lý do tương tự áp dụng cho các chip có nhiều bộ nhớ cache L2 và kiến ​​trúc NUMA (truy cập bộ nhớ không đồng bộ). Vì vậy, các lõi khác mà bạn muốn phân phối sắp xếp trên, hằng số tinyToParallelize sẽ cần phải được tăng lên tương ứng.

Một yếu tố hạn chế khác mà bạn đã xác định là chia sẻ quyền truy cập bộ nhớ hoặc tranh chấp trên bus bộ nhớ. Vì bus bộ nhớ chỉ có thể đáp ứng một số lượng truy cập bộ nhớ nhất định mỗi giây; có lõi bổ sung mà về cơ bản không có gì nhưng đọc và ghi vào bộ nhớ chính sẽ đặt rất nhiều căng thẳng trên hệ thống bộ nhớ.

Yếu tố cuối cùng mà tôi nên chỉ ra là hồ bơi chính vì nó có thể không hiệu quả như bạn nghĩ. Vì bạn có các chủ đề đánh cắp và tạo ra công việc từ một hàng đợi được chia sẻ, hàng đợi đó đòi hỏi các phương thức đồng bộ hóa; và tùy thuộc vào cách chúng được triển khai, chúng có thể gây ra các phần nối tiếp rất dài trong mã của bạn.

1

Tôi không biết nếu câu trả lời ở đây được áp dụng nữa hoặc nếu lời đề nghị của tôi là áp dụng đối với D.

Dù sao ...

Giả sử D cho phép nó, luôn luôn có khả năng cung cấp gợi ý tìm nạp trước vào cache. Cốt lõi trong câu hỏi yêu cầu dữ liệu sẽ sớm (không ngay lập tức) cần được tải vào một mức bộ nhớ cache nhất định. Trong trường hợp lý tưởng, dữ liệu sẽ được tìm nạp vào lúc lõi bắt đầu làm việc trên đó. Nhiều khả năng quá trình tìm nạp trước sẽ nhiều hơn hoặc ít hơn trên con đường ít nhất sẽ dẫn đến trạng thái chờ đợi ít hơn nếu dữ liệu được tải xuống "lạnh."

Bạn sẽ vẫn bị hạn chế bởi dung lượng lưu trữ toàn bộ bộ nhớ cache đến RAM, do đó bạn sẽ cần phải tổ chức dữ liệu sao cho rất nhiều dữ liệu nằm trong bộ đệm độc quyền của lõi mà nó có thể chi tiêu một số tiền hợp lý

Mã và dữ liệu cần phải được sắp xếp theo khái niệm về các dòng bộ nhớ cache (tìm nạp đơn vị 64 byte mỗi) là đơn vị có kích thước nhỏ nhất trong bộ nhớ cache. trong đó đối với hai lõi, công việc cần phải được tổ chức sao cho hệ thống bộ nhớ hoạt động một nửa trên mỗi lõi (giả sử 100% khả năng mở rộng) như trước đây khi chỉ có một lõi hoạt động và công việc không được tổ chức. Đó là một thách thức nhưng không có nghĩa là không thể, nó chỉ làm giảm ds về cách bạn giàu trí tưởng tượng trong việc tái cấu trúc công việc. Như mọi khi, có những giải pháp không thể hình thành ... cho đến khi có ai đó làm điều đó!

Tôi không biết WYSIWYG D được so sánh với C như thế nào - nhưng nói chung tôi nghĩ rằng quá trình phát triển các ứng dụng có thể mở rộng được cải thiện bởi nhà phát triển có thể ảnh hưởng đến trình biên dịch như thế nào trong quá trình tạo mã máy. Đối với các ngôn ngữ thông dịch, sẽ có rất nhiều công việc bộ nhớ đang diễn ra bởi người phiên dịch mà bạn có nguy cơ không thể nhận ra những cải tiến từ "tiếng ồn nền chung".

Tôi đã từng viết một shellsort đa luồng chạy nhanh hơn 70% trên hai lõi so với một và 100% trên ba lõi so với một. Bốn lõi chạy chậm hơn ba. Vì vậy, tôi biết các tình huống khó xử bạn phải đối mặt.

0

Tôi muốn chỉ cho bạn sắp xếp bên ngoài [1] đối mặt với các sự cố tương tự. Thông thường, lớp thuật toán này được sử dụng chủ yếu để đối phó với khối lượng lớn dữ liệu, nhưng điểm chính của chúng là chúng phân chia các khối lớn thành các vấn đề nhỏ hơn và không liên quan, do đó thực sự tuyệt vời để chạy song song. Bạn "chỉ" cần phải khâu lại với nhau một phần kết quả sau đó, mà không phải là khá song song (nhưng tương đối rẻ so với việc phân loại thực tế).

Sắp xếp hợp nhất bên ngoài cũng sẽ hoạt động thực sự tốt với số lượng luồng không xác định. Bạn chỉ cần phân chia tải công việc một cách tùy ý, và đưa từng phần tử n vào một luồng bất cứ khi nào có một nhàn rỗi, cho đến khi tất cả các đơn vị công việc của bạn được thực hiện, tại thời điểm đó bạn có thể bắt đầu tham gia chúng.

[1] http://en.wikipedia.org/wiki/External_sorting

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