2015-03-11 22 views
38

Bối cảnh:Làm thế nào để coroutines stackless khác với coroutines stackful?

Tôi hỏi điều này vì hiện tại tôi có một ứng dụng có nhiều (hàng trăm đến hàng nghìn) chủ đề. Hầu hết các chủ đề đó là nhàn rỗi một phần lớn thời gian, chờ đợi các mục công việc được đặt trong hàng đợi. Khi một mục công việc có sẵn, nó được xử lý bằng cách gọi một số mã độc lập phức tạp tùy ý. Trên một số cấu hình hệ điều hành, ứng dụng gặp phải các thông số hạt nhân điều chỉnh số lượng quy trình người dùng tối đa, vì vậy tôi muốn thử nghiệm với phương tiện để giảm số lượng chuỗi công việc.

giải pháp đề xuất của tôi:

Nó có vẻ như một cách tiếp cận dựa trên coroutine, nơi tôi thay thế mỗi sợi nhân với một coroutine, sẽ giúp đỡ để thực hiện điều này. Sau đó tôi có thể có một hàng đợi công việc được hỗ trợ bởi một nhóm các luồng công nhân (kernel) thực tế. Khi một mục được đặt trong hàng đợi của một coroutine cụ thể để xử lý, một mục sẽ được đặt vào hàng đợi của nhóm luồng. Sau đó nó sẽ tiếp tục coroutine tương ứng, xử lý dữ liệu xếp hàng đợi của nó, và sau đó đình chỉ nó một lần nữa, giải phóng luồng công nhân để thực hiện công việc khác.

chi tiết

Thực hiện:

Trong suy nghĩ về làm thế nào tôi sẽ làm điều này, tôi đang gặp rắc rối tìm hiểu sự khác biệt về chức năng giữa Stackless và coroutines stackful. Tôi có một số kinh nghiệm sử dụng coroutines xếp chồng lên nhau bằng cách sử dụng thư viện Boost.Coroutine. Tôi thấy nó tương đối dễ hiểu từ một mức khái niệm: cho mỗi coroutine, nó duy trì một bản sao của bối cảnh CPU và ngăn xếp, và khi bạn chuyển sang coroutine, nó chuyển sang bối cảnh đã lưu đó (giống như một bộ lập lịch chế độ hạt nhân).

Điều ít rõ ràng đối với tôi là làm thế nào một coroutine không có thứ tự khác với điều này. Trong ứng dụng của tôi, số tiền trên không liên quan đến việc xếp hàng các mục công việc được mô tả ở trên là rất quan trọng. Hầu hết các triển khai mà tôi đã thấy, chẳng hạn như the new CO2 library cho thấy rằng các coroutines không có thứ tự cung cấp nhiều công tắc ngữ cảnh phía trên thấp hơn nhiều.

Vì vậy, tôi muốn hiểu sự khác biệt về chức năng giữa các coroutines không có ngăn xếp và xếp chồng lên nhau rõ ràng hơn. Cụ thể, tôi nghĩ về những câu hỏi này:

  • References like this one gợi ý rằng sự khác biệt nằm ở chỗ, nơi bạn có thể mang lại/resume trong một stackful vs Stackless coroutine. Đây có phải là trường hợp không? Có một ví dụ đơn giản về một cái gì đó mà tôi có thể làm trong một coroutine stackful nhưng không phải trong một stackless?

  • Có bất kỳ giới hạn nào về việc sử dụng biến lưu trữ tự động (tức là các biến "trên ngăn xếp") không?

  • Có bất kỳ hạn chế nào đối với những chức năng nào tôi có thể gọi từ một coroutine không có ngăn xếp không?

  • Nếu không có tiết kiệm bối cảnh ngăn xếp cho một coroutine stackless, nơi mà các biến lưu trữ tự động đi khi coroutine đang chạy?

+2

'Hầu hết các chủ đề này không hoạt động trong một phần lớn thời gian, chờ đợi các mục công việc được đặt trong hàng đợi' - nếu đây là trường hợp, tại sao có quá nhiều chủ đề? –

+1

@MartinJames: Vì lý do cũ. Tôi không tuyên bố rằng đó là một thiết kế tốt như vậy, do đó mong muốn của tôi để cải thiện nó. Tái cấu trúc toàn bộ bán buôn ứng dụng không phải là một lựa chọn ngắn hạn, vì vậy tôi đang tìm kiếm các phần nâng cấp tương đối đơn giản để bắt đầu. Những thứ có khả năng làm phức tạp hơn nữa, cuộc gọi chặn đến hàng đợi thường được thực hiện một vài cấp độ sâu trong ngăn xếp cuộc gọi (tức là không ở chức năng cấp cao nhất của chuỗi công nhân). Tôi * nghĩ * điều này sẽ ngăn cản việc sử dụng các chủ đề không xếp chồng trong bối cảnh cụ thể này. –

Trả lời

34

Đầu tiên, cảm ơn bạn đã dành một cái nhìn tại CO2 :)

Các Boost.Coroutine doc mô tả các lợi thế của coroutine stackful tốt:

stackfulness

Ngược lại với một coroutine Stackless một coroutine stackful có thể bị đình chỉ từ bên trong một StackFrame lồng nhau. Execution tiếp tục tại chính xác cùng một điểm trong mã mà nó đã bị treo trước đó. Với một coroutine không có ngăn xếp, chỉ có thói quen cấp cao nhất có thể bị đình chỉ. Bất kỳ thường trình nào được gọi là thông lệ cấp cao nhất có thể không tự treo. Điều này cấm cung cấp các hoạt động tạm ngưng/tiếp tục trong các thói quen trong phạm vi một thư viện đa năng.

hạng nhất tiếp tục

Một tiếp tục hạng nhất có thể được thông qua như một cuộc tranh cãi, được trả về bởi một hàm và lưu trữ trong một cấu trúc dữ liệu để được sử dụng sau. Trong một số triển khai (ví dụ năng suất C#), việc tiếp tục không thể được truy cập trực tiếp hoặc trực tiếp thao tác.

Không có tính cộng dồn và ngữ nghĩa hạng nhất, một số thực thi hữu ích luồng kiểm soát không thể được hỗ trợ (ví dụ: hợp tác đa nhiệm hoặc kiểm tra).

Điều đó có ý nghĩa gì với bạn? ví dụ, hãy tưởng tượng bạn có một chức năng mà phải mất một lượt truy cập:

template<class Visitor> 
void f(Visitor& v); 

Bạn muốn chuyển đổi nó để iterator, với coroutine stackful, bạn có thể:

asymmetric_coroutine<T>::pull_type pull_from([](asymmetric_coroutine<T>::push_type& yield) 
{ 
    f(yield); 
}); 

Nhưng với Stackless coroutine, không có cách nào để làm như vậy:

generator<T> pull_from() 
{ 
    // yield can only be used here, cannot pass to f 
    f(???); 
} 

Nói chung, coroutine xếp chồng lên nhau mạnh hơn coroutine không có ngăn xếp. Vì vậy, tại sao chúng ta muốn coroutine stackless? câu trả lời ngắn gọn: hiệu quả.

coroutine xếp chồng thường cần phân bổ một lượng bộ nhớ nhất định để chứa thời gian chạy của ngăn xếp (phải đủ lớn) và công tắc ngữ cảnh đắt hơn so với bộ nhớ ẩn, ví dụ: Boost.Coroutine mất 40 chu kỳ trong khi CO2 chỉ mất trung bình 7 chu kỳ trên máy tính của tôi, bởi vì điều duy nhất mà một coroutine không cần thiết phải khôi phục lại là bộ đếm chương trình.

Điều đó nói rằng, với sự hỗ trợ ngôn ngữ, coroutine có thể cũng có lợi dụng kích thước tối đa của trình biên dịch cho stack miễn là không có đệ quy trong coroutine, do đó việc sử dụng bộ nhớ cũng có thể được cải thiện.

Nói về coroutine Stackless, hãy nhớ rằng nó không có nghĩa là không có thời gian chạy-stack ở tất cả, nó chỉ có nghĩa là nó sử dụng cùng một runtime stack như phía chủ nhà, vì vậy bạn có thể gọi hàm đệ quy như tốt, chỉ là tất cả các cuộc khảo sát sẽ xảy ra trên máy chủ thời gian chạy-stack. Ngược lại, với coroutine xếp chồng lên nhau, khi bạn gọi hàm đệ quy, việc thu thập lại sẽ xảy ra trên ngăn xếp của coroutine.

Để trả lời các câu hỏi:

  • Có bất kỳ hạn chế về việc sử dụng các biến tự động lưu trữ (biến tức là "trên stack")?

No. Đó là giới hạn thi đua của CO2. Với hỗ trợ ngôn ngữ, các biến lưu trữ tự động hiển thị cho coroutine sẽ được đặt vào bộ nhớ trong của coroutine. Lưu ý sự nhấn mạnh của tôi về "có thể nhìn thấy coroutine", nếu coroutine gọi một hàm sử dụng các biến lưu trữ tự động trong nội bộ, thì các biến đó sẽ được đặt trên ngăn xếp thời gian chạy. Cụ thể hơn, coroutine stackless chỉ phải bảo tồn các biến/thời gian có thể được sử dụng sau khi tiếp tục.

Để được rõ ràng, bạn có thể sử dụng các biến lưu trữ tự động trong cơ thể coroutine CO2 của cũng như:

auto f() CO2_RET(co2::task<>,()) 
{ 
    int a = 1; // not ok 
    CO2_AWAIT(co2::suspend_always{}); 
    { 
     int b = 2; // ok 
     doSomething(b); 
    } 
    CO2_AWAIT(co2::suspend_always{}); 
    int c = 3; // ok 
    doSomething(c); 
} CO2_END 

Chừng nào định nghĩa không trước bất kỳ await.

  • Có bất kỳ hạn chế nào đối với những chức năng nào tôi có thể gọi từ một coroutine không ngăn xếp không?

số

  • Nếu không có tiết kiệm của chồng bối cảnh cho một coroutine Stackless, nơi nào biến lưu trữ tự động đi khi coroutine là chạy?

Đã trả lời ở trên, một coroutine không quan tâm không quan tâm đến các biến lưu trữ tự động được sử dụng trong các hàm được gọi, chúng sẽ chỉ được đặt trên ngăn xếp thời gian chạy bình thường.

Nếu bạn có bất kỳ nghi ngờ gì nữa, chỉ cần kiểm tra mã nguồn CO2, nó có thể giúp bạn hiểu được cơ chế dưới mui xe;)

2

Những gì bạn muốn là chủ đề sử dụng đất/sợi - thường bạn muốn đình chỉ của bạn mã (chạy bằng sợi) trong ngăn xếp cuộc gọi lồng nhau sâu (ví dụ phân tích cú pháp thư từ kết nối TCP). Trong trường hợp này, bạn không thể sử dụng chuyển đổi bối cảnh stackless (ngăn xếp ứng dụng được chia sẻ giữa các coroutines stackless -> ngăn xếp khung của các chương trình con được gọi sẽ bị ghi đè).

Bạn có thể sử dụng thứ gì đó như boost.fiber để triển khai chủ đề/sợi đất của người dùng dựa trên boost.context.

+0

Thách thức chính của tôi với việc thực hiện điều này bằng cách sử dụng sợi hoặc coroutines là vấn đề lập kế hoạch. Tôi muốn thực hiện một mô hình luồng M: N, trong đó các sợi N/coroutine được phục vụ bởi các luồng hạt nhân M, nhưng tôi muốn các luồng M đó có thể phục vụ bất kỳ sợi N/coroutines nào khi cần thiết.Có thể tiếp tục một 'boost :: fiber' từ một chuỗi hạt nhân khác với nó đã bị treo trước đó không? Có một hit hiệu suất để làm như vậy? Còn về 'boost :: asymmetric_coroutine' thì sao? –

+0

di chuyển một sợi từ sợi này sang sợi khác đang được phát triển vào lúc này nhưng tôi không khuyến nghị sử dụng nó. di chuyển một sợi chỉ có ý nghĩa nếu sợi được thực hiện trên một CPU khác. di chuyển sợi từ CPU này sang CPU khác sẽ gây ra lỗi nhớ cache. boost.fiber cung cấp các lớp đồng bộ hóa như biến mutext/condition etc cho các sợi. – olk

+0

Cảm ơn thông tin. Tôi cảm thấy như nó sẽ là một tính năng hữu ích. Như tôi đã nói, trong ứng dụng của tôi, tôi muốn làm M: N luồng. Tôi sẽ có N sợi, mỗi sợi đại diện cho một đường ống xử lý. Những đường ống này được cho ăn không đồng bộ với dữ liệu từ một luồng khác. Do đó, tôi muốn có thể phục vụ từng sợi N bằng cách sử dụng các luồng hạt nhân M (nhỏ hơn nhiều so với N). Điều đó có nghĩa rằng bất cứ khi nào một sợi cụ thể sẵn sàng chạy vì dữ liệu đầu vào mới có sẵn, tôi muốn lấy một trong các luồng hạt nhân trong nhóm và phục vụ các sợi. –

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