Linux: agendando processos e encadeamentos

1

Quando há vários processos single-threaded e alguns processos multi-threaded, todos com uso intensivo de CPU, como o tempo é dividido entre eles (assumindo que todos tenham a mesma prioridade)?

Por exemplo, na máquina de 48 núcleos, eu tenho 48 processos single-threaded e um processo com 48 threads. Todos os threads estão prontos para usar a CPU. Minha expectativa era que 48 processos single-threaded recebessem 1/2 de CPU disponível, e 48 threads receberiam mais 1/2 de CPU, ou seja, cada thread (independentemente de ser de um processo single-thread ou de um processo multi-thread). ) obteria o mesmo tempo de CPU.

Mas parece que o tempo é dividido primeiro entre os processos e cada processo recebe 1/49 da CPU e, em seguida, essa parte é dividida entre os segmentos no processo. Como resultado, os encadeamentos no processo de vários encadeamentos obtêm apenas 1/48 do tempo atribuído a um encadeamento no processo de encadeamento único.

Perguntas: 1) Como funciona o agendador? 2) É possível forçar o scheduler a dar tempo igual a cada thread, independentemente do processo de onde este thread vem?

    
por Mikhail Kovtun 05.03.2018 / 22:06

2 respostas

1

Eu testei sua observação e pelo menos em kernels recentes é falso. Eu escrevi este código.

#define _GNU_SOURCE
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <err.h>

#include <pthread.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/resource.h>

#define TIMEOUT 4

void print_usage(
    char *type)
{
  struct rusage use;

  getrusage(RUSAGE_THREAD, &use);

  float total_time = 0;
  long total_sw = 0;
  total_time += use.ru_utime.tv_sec + ((float)use.ru_utime.tv_usec / 1000000);
  total_time += use.ru_stime.tv_sec + ((float)use.ru_stime.tv_usec / 1000000);

  total_sw = use.ru_nvcsw + use.ru_nivcsw;

  printf("Type: %s, CPU Time: %.3f seconds, Total context switches: %d\n",
         type, total_time, total_sw);

  return;
} 

struct worksync {
  pthread_spinlock_t spin;
};

void * spinner_thread(
    void *data)
{
  struct worksync *sync = (struct worksync *)data;

  pthread_spin_lock(&sync->spin);
  print_usage("Thread");
  pthread_spin_unlock(&sync->spin);

  pthread_exit(0);
}

void spawn_threaded_worker(
    int ncpu,
    int timeout)
{
  pid_t pid;

  pid = fork();
  if (pid < 0)
    err(EXIT_FAILURE, "fork failed");
  if (pid == 0) {

    /* allocate and initialize structures */
    pthread_t *threads = alloca(sizeof(pthread_t) * ncpu);
    struct worksync sync;
    int i;

    pthread_spin_init(&sync.spin, PTHREAD_PROCESS_PRIVATE);

    assert(threads);

    for (i=0; i < ncpu; i++) {
      pthread_create(&threads[i], NULL, spinner_thread, (void *)&sync);
    }

    pthread_spin_lock(&sync.spin);

    sleep(timeout);
    pthread_spin_unlock(&sync.spin);

    for (i=0; i < ncpu; i++) 
      pthread_join(threads[i], NULL);

    exit(0);
  }
}

void spinner_process(
    struct worksync *sync)
{
  pthread_spin_lock(&sync->spin);
  print_usage("Process");
  pthread_spin_unlock(&sync->spin);
  exit(0);
}

void spawn_forked_worker(
    int ncpu,
    int timeout)
{
  int i;
  int status;
  pid_t pid;
  pid = fork();
  if (pid < 0)
    err(EXIT_FAILURE, "fork failed");

  if (pid == 0) {
    pid_t *pids = alloca(sizeof(pid_t) * ncpu);
    struct worksync *sync = mmap(NULL, sizeof(struct worksync),
                           PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_SHARED, -1, 0);
    assert(sync != MAP_FAILED);
    pthread_spin_init(&sync->spin, PTHREAD_PROCESS_SHARED);
    pthread_spin_lock(&sync->spin);

    for (i=0; i < ncpu; i++) {
      pids[i] = fork();
      if (pids[i] < 0)
        abort();

      if (pids[i] == 0)
        spinner_process(sync);
    }

    sleep(timeout);
    pthread_spin_unlock(&sync->spin);

    for (i=0; i < ncpu; i++) 
       wait(&status);
    exit(0);
  }
}


int main(
    void)
{
  int ncpu;
  int status;
  ncpu = sysconf(_SC_NPROCESSORS_ONLN);
  assert(ncpu > 0);

  printf("Running %d threads and %d processes for %d seconds\n", ncpu, ncpu, TIMEOUT);
  spawn_threaded_worker(ncpu, TIMEOUT);
  spawn_forked_worker(ncpu, TIMEOUT);

  wait(&status);
  wait(&status);

  exit(0);
}

Mede o tempo gasto da CPU na execução de um trabalho intensivo da CPU (girando em um spinlock) em um modelo encadeado e em um modelo bifurcado, ambos ao mesmo tempo, usando todas as CPUs do sistema. Em seguida, relata as estatísticas da CPU.

Meus resultados são exibidos em uma caixa de 4 CPUs:

Com o autogrupo DESABILITADO

$ ./schedtest 
Running 4 threads and 4 processes for 4 seconds
Type: Thread, CPU Time: 1.754 seconds, Total context switches: 213
Type: Thread, CPU Time: 1.758 seconds, Total context switches: 208
Type: Thread, CPU Time: 1.755 seconds, Total context switches: 217
Type: Process, CPU Time: 1.768 seconds, Total context switches: 251
Type: Process, CPU Time: 1.759 seconds, Total context switches: 209
Type: Thread, CPU Time: 1.772 seconds, Total context switches: 258
Type: Process, CPU Time: 1.752 seconds, Total context switches: 215
Type: Process, CPU Time: 1.756 seconds, Total context switches: 225

Com o autogrupo HABILITADO

$ ./schedtest 
Running 4 threads and 4 processes for 4 seconds
Type: Thread, CPU Time: 0.495 seconds, Total context switches: 167
Type: Thread, CPU Time: 0.496 seconds, Total context switches: 167
Type: Thread, CPU Time: 0.430 seconds, Total context switches: 145
Type: Process, CPU Time: 0.430 seconds, Total context switches: 148
Type: Process, CPU Time: 0.440 seconds, Total context switches: 149
Type: Process, CPU Time: 0.440 seconds, Total context switches: 150
Type: Thread, CPU Time: 0.457 seconds, Total context switches: 153
Type: Process, CPU Time: 0.430 seconds, Total context switches: 144

Você pode ver claramente que não há distinção entre os segmentos e os processos.

Eu não tenho ideia do que você está fazendo, mas o que quer que seja não está de acordo com a maneira como o Linux funciona, pelo menos para mim.

    
por 05.03.2018 / 23:42
0

Acho que o que você está vendo é um efeito do recurso "autogrupo" do CFS , que tenta agrupar processos (e encadeamentos) que compartilham a mesma" sessão "(como nas sessões iniciadas chamando setsid() . )

(A suposição que estou fazendo aqui é que você está iniciando os 48 processos single-thread em uma sessão separada cada.)

Você pode tentar desativar o recurso "autogroup" com este comando para ver se ele muda o comportamento que você está vendo:

echo 0 >/proc/sys/kernel/sched_autogroup_enabled

Consulte a seção sobre o autogrupo na página man do sched (7) para mais detalhes.

    
por 05.03.2018 / 23:03