Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

unifex task/timed_single_thread_context cannot run 1 million concurrent tasks #648

Open
ccotter opened this issue Jan 6, 2025 · 3 comments · May be fixed by #650
Open

unifex task/timed_single_thread_context cannot run 1 million concurrent tasks #648

ccotter opened this issue Jan 6, 2025 · 3 comments · May be fixed by #650

Comments

@ccotter
Copy link
Contributor

ccotter commented Jan 6, 2025

https://github.com/Xavorim/cpp_tasks_bench?tab=readme-ov-file#results-on-my-system benchmarks libunifex against a few other concurrency libraries. The benchmark attempts to launch 1 million concurrent tasks. Unfortunately, the benchmark states libunifex hangs at just 100K tasks. I am able to replicate this myself, with the benchmark program at 100% for a minute until I killed the program.

I reimplemented the benchmark with async_manual_reset_event instead of timed_single_thread_context, and I am able to launch 1 million tasks in about 4 seconds: Xavorim/cpp_tasks_bench#1

I'm entering this issue to see if the timer scheduler can be improved. I'll also compare against stdexec (as the benchmark repo doesn't include it currently).

stdexec benchmark

stdexec appears to be able to run 1 million tasks with exec::timed_thread_context

#include <stdexec/execution.hpp>
#include <exec/timed_thread_scheduler.hpp>
#include <exec/task.hpp>
#include <exec/async_scope.hpp>

#include <chrono>
#include <cstdio>
#include <iostream>
#include <cstdlib>
#include <ranges>

exec::timed_thread_context TIMER;

auto async_sleep(auto s) { return exec::schedule_after(TIMER.get_scheduler(), s); }

int main(int argc, char **argv) {
    if (argc < 2) {
        fprintf(stderr,
                "Please specify the number of tasks\n"
                "example: %s 10000\n",
                argv[0]);
        return EXIT_FAILURE;
    }

    exec::async_scope scope;
    for (auto i : std::views::iota(0, std::stoi(argv[1]))) {
        scope.spawn([]() -> exec::task<void> { co_await async_sleep(std::chrono::seconds(10)); }());
    }

    stdexec::sync_wait(scope.on_empty());
    return 0;
}
$ time ./a.out 1000000

real    0m17.098s
user    0m11.241s
sys     0m4.150s
@ericniebler
Copy link
Collaborator

I'll also compare against stdexec (as the benchmark repo doesn't include it currently).

stdexec doesn't have an async_manual_reset_event yet, but it probably wouldn't be hard to port the one from libunifex.

@ccotter
Copy link
Contributor Author

ccotter commented Jan 6, 2025

Ah ok, libunifex's timed_single_thread_context enqueues into a sorted linked list, and enqueueing each item must traverse each element to find the correct location to store the item. stdexec uses an intrusive heap, so enqueueing 1 million items is far more efficient.

ccotter added a commit to ccotter/libunifex that referenced this issue Jan 6, 2025
Use a heap to avoid possibly linear enqueue operation time.

Fixes facebookexperimental#648
@ccotter ccotter linked a pull request Jan 6, 2025 that will close this issue
@ericniebler
Copy link
Collaborator

I wonder how important the single thread context is. Who uses it and for what?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants