Skip to content

High CPU usage in scheduler #2033

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

Closed
tanjianfeng opened this issue Mar 1, 2020 · 7 comments
Closed

High CPU usage in scheduler #2033

tanjianfeng opened this issue Mar 1, 2020 · 7 comments
Assignees
Labels
area: performance Issue related to performance & benchmarks auto-closed stale-issue This issue has not been updated in 120 days. type: bug Something isn't working

Comments

@tanjianfeng
Copy link
Contributor

With below program, we find that the scheduler shows ~380% CPU usage vs ~200% in runc.

How to test:
$ docker run -it --cpu-period=1000 --cpu-quota=8000 ...
$ gcc -o threads thread.c -lpthread
$ ./threads 1024 100000

#include <stdio.h>
#include <pthread.h>
#include <sys/time.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>

struct worker_arg {
        int seq;
        int iterations;

        pthread_mutex_t count_lock;
        pthread_cond_t count_nonzero;
        unsigned count;
};


static void
park(struct worker_arg *arg)
{
        pthread_mutex_lock(&arg->count_lock);
        while (arg->count == 0)
                pthread_cond_wait(&arg->count_nonzero, &arg->count_lock);
        arg->count--;
        pthread_mutex_unlock(&arg->count_lock);
}

static void
unpark(struct worker_arg *arg)
{
        pthread_mutex_lock(&arg->count_lock);
        if (arg->count == 0)
                pthread_cond_signal(&arg->count_nonzero);
        arg->count++;
        pthread_mutex_unlock(&arg->count_lock);
}

static void *
worker(void *p)
{
        struct worker_arg *arg = (struct worker_arg *)p;

        while (arg->iterations--) {
                /* do some work */
                //printf("%d %d\n", arg->seq, arg->iterations);
                park(arg);
        }

        return NULL;
}

int
main(int argc, char **argv)
{
        int i, j, N, M;
        void *dummy;
        struct worker_arg *args;
        pthread_t *tids;

        if (argc != 3) {
                fprintf(stderr, "Usage: %s <number of threads> <iterations>\n", argv[0]);
                exit(1);
        }

        N = atoi(argv[1]);
        M = atoi(argv[2]);

        tids = malloc(sizeof(*tids) * N);
        args = malloc(sizeof(*args) * N);

        for (i = 0; i < N; i++) {
                args[i].seq = i;
                args[i].iterations = M;

                args[i].count = 0;
                pthread_mutex_init(&args[i].count_lock, NULL);
                pthread_cond_init(&args[i].count_nonzero, NULL);

                pthread_create(&tids[i], NULL, worker, &args[i]);
        }

        for (i = 0; i < M; i++) {
                for (j = 0; j < N; j++)
                        unpark(&args[j]);
        }

        for (i = 0; i < N; i++)
                pthread_join(tids[i], &dummy);
}
@prattmic prattmic added area: performance Issue related to performance & benchmarks type: bug Something isn't working labels Mar 2, 2020
@prattmic
Copy link
Member

prattmic commented Mar 2, 2020

When you say 'CPU in the scheduler', I assume you mean the host Linux kernel scheduler?

Which platform (or platforms) do you see this behavior on?

@tanjianfeng
Copy link
Contributor Author

When you say 'CPU in the scheduler', I assume you mean the host Linux kernel scheduler?

I mean scheduler in broader meaning. The example above uses futex syscall to wait/wakeup threads. So it involves (1) futex implementation in sentry; (2) golang sheduler; (3) maybe also linux host CFS.

Which platform (or platforms) do you see this behavior on?

kvm platorm.

@prattmic
Copy link
Member

For reference, this workload tends to suffer from golang/go#43997.

GuhuangLS added a commit to GuhuangLS/go that referenced this issue May 25, 2021
Background: channel is designed to be a data transfer pipe between
go routines. A scenario in gVisor is: block/wake a G (stands for a
task) by using channel, wherein there's no need for data transfer.
Channel is relatively too heavy for this case.
Channel manages Gs in a list, and has capacity. When the channel is
full, the sender will bock, and  the channel is clear, the receiver
is blocked. The receiver pushed to chan list, then schedule to run
next. The sender uses goready to wake Gs in the chan list.

A summary on our idea: introduce a new set of APIs for goroutine
wake/block.

Details:

We propose three new APIs:

GetG() -  used to get the address of goroutine.  By address, find
the goroutine G easier at go program.

WakeG() - can be used to wake one G, which can be in running/blocked
status.

BlockG() - can be used to block goroutine by itself.

How we use this in gVisor:

In futex()/epoll_wait(), we can modify it to use the new mechanism
for block and wake. Between sentry and go runtime, we maintain the
status of task Gs. Let's use futex as an example, add running status
at goruntime, NoWake,Waked,Blocked.

At sentry, one task/G can use BlockG() to block, like   <-chan. Other
tasks/Gs can use WakeG() to wake the task/G which is blocked by BlockG()
, like chan <-. Based on a basic prototype of Go and gVisor, we use the
program in google/gvisor#2033(comment) as the test program.

We can see 22% improvement by test case:
google/gvisor#2033.

Signed-off-by: liushi <[email protected]>
GuhuangLS added a commit to GuhuangLS/go that referenced this issue May 26, 2021
Background: channel is designed to be a data transfer pipe between
go routines. A scenario in gVisor is: block/wake a G (stands for a
task) by using channel, wherein there's no need for data transfer.
Channel is relatively too heavy for this case.
Channel manages Gs in a list, and has capacity. When the channel is
full, the sender will bock, and  the channel is clear, the receiver
is blocked. The receiver pushed to chan list, then schedule to run
next. The sender uses goready to wake Gs in the chan list.

A summary on our idea: introduce a new set of APIs for goroutine
wake/block.

Details:

We propose three new APIs:

GetG() -  used to get the address of goroutine.  By address, find
the goroutine G easier at go program.

WakeG() - can be used to wake one G, which can be in running/blocked
status.

BlockG() - can be used to block goroutine by itself.

How we use this in gVisor:

GuhuangLS/gvisor@97e0e6c

In futex()/epoll_wait(), we can modify it to use the new mechanism
for block and wake. Between sentry and go runtime, we maintain the
status of task Gs. Let's use futex as an example, add running status
at goruntime, NoWake,Waked,Blocked.

At sentry, one task/G can use BlockG() to block, like   <-chan. Other
tasks/Gs can use WakeG() to wake the task/G which is blocked by BlockG()
, like chan <-. Based on a basic prototype of Go and gVisor, we use the
program in google/gvisor#2033(comment) as the test program.

We can see 22% improvement by test case:
google/gvisor#2033.

Signed-off-by: liushi <[email protected]>
@github-actions
Copy link

A friendly reminder that this issue had no activity for 120 days.

@github-actions github-actions bot added the stale-issue This issue has not been updated in 120 days. label Sep 15, 2023
@ayushr2
Copy link
Collaborator

ayushr2 commented Sep 15, 2023

golang/go@ecfce58 must have helped with this. Is this still an issue?

@github-actions github-actions bot removed the stale-issue This issue has not been updated in 120 days. label Sep 16, 2023
Copy link

A friendly reminder that this issue had no activity for 120 days.

@github-actions github-actions bot added the stale-issue This issue has not been updated in 120 days. label Jan 14, 2024
Copy link

This issue has been closed due to lack of activity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: performance Issue related to performance & benchmarks auto-closed stale-issue This issue has not been updated in 120 days. type: bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants