Как получить 100% использование ЦП из программы C

Это довольно интересный вопрос, поэтому позвольте мне установить сцену. Я работаю в Национальном музее вычислительной техники, и с 1992 года нам удалось получить суперкомпьютер Cray Y-MP EL, и мы действительно хотим видеть, как быстро он может идти!

Мы решили, что лучший способ сделать это - написать простую программу на языке С, которая будет вычислять простые числа и показывать, сколько времени потребовалось для этого, затем запустить программу на быстром современном настольном ПК и сравнить результаты.

Мы быстро придумали этот код для подсчета простых чисел:

#include <stdio.h>
#include <time.h>

void main() {
    clock_t start, end;
    double runTime;
    start = clock();
    int i, num = 1, primes = 0;

    while (num <= 1000) { 
        i = 2; 
        while (i <= num) { 
            if(num % i == 0)
                break;
            i++; 
        }
        if (i == num)
            primes++;

        system("clear");
        printf("%d prime numbers calculated\n",primes);
        num++;
    }

    end = clock();
    runTime = (end - start) / (double) CLOCKS_PER_SEC;
    printf("This machine calculated all %d prime numbers under 1000 in %g seconds\n", primes, runTime);
}

Что на нашем двухъядерном ноутбуке под управлением Ubuntu (The Cray работает UNICOS), отлично работает, получая 100% -ное использование ЦП и занимает около 10 минут или около того. Когда я вернулся домой, я решил попробовать его на своем шестиъядерном современном игровом ПК, и именно здесь мы получаем первые проблемы.

Сначала я адаптировал код для работы в Windows, так как это то, что использовал игровой ПК, но был опечален, обнаружив, что процесс получает только около 15% от мощности процессора. Я полагал, что Windows должна быть Windows, поэтому я загрузился в Live CD Ubuntu, думая, что Ubuntu позволит процессу работать с полным потенциалом, как это было раньше на моем ноутбуке.

Однако я получил только 5% использования! Итак, мой вопрос: как я могу адаптировать программу для запуска на своем игровом автомате в Windows 7 или Live Linux при 100% использовании процессора? Другое дело, что было бы здорово, но не обязательно, если конечный продукт может быть одним .exe, который может быть легко распространен и запущен на машинах Windows.

Спасибо большое!

P.S. Конечно, эта программа действительно не работала с процессорами Crays 8, и это еще одна проблема... Если вы знаете что-нибудь об оптимизации кода для работы с 90 супермодными компьютерами Cray, дайте нам тоже крик!

Ответ 1

Если вам нужен 100% процессор, вам нужно использовать более 1 ядра. Для этого вам нужно несколько потоков.

Здесь используется параллельная версия с использованием OpenMP:

Мне пришлось увеличить лимит до 1000000, чтобы он занял более 1 секунды на моей машине.

#include <stdio.h>
#include <time.h>
#include <omp.h>

int main() {
    double start, end;
    double runTime;
    start = omp_get_wtime();
    int num = 1,primes = 0;

    int limit = 1000000;

#pragma omp parallel for schedule(dynamic) reduction(+ : primes)
    for (num = 1; num <= limit; num++) { 
        int i = 2; 
        while(i <= num) { 
            if(num % i == 0)
                break;
            i++; 
        }
        if(i == num)
            primes++;
//      printf("%d prime numbers calculated\n",primes);
    }

    end = omp_get_wtime();
    runTime = end - start;
    printf("This machine calculated all %d prime numbers under %d in %g seconds\n",primes,limit,runTime);

    return 0;
}

Вывод:

Эта машина рассчитала все 78498 простых чисел до 1000000 в 29.753 секунд

Здесь ваш 100% процессор:

enter image description here

Ответ 2

Вы запускаете один процесс на многоядерном компьютере, поэтому он работает только на одном ядре.

Решение достаточно просто, так как вы просто пытаетесь привязать процессор - если у вас есть N ядер, запустите свою программу N раз (параллельно, конечно).

Пример

Вот несколько примеров, которые запускают вашу программу NUM_OF_CORES раз параллельно. Это код POSIXy - он использует fork - поэтому вы должны запускать его под Linux. Если то, что я читаю о Cray, правильно, может быть проще переносить этот код, чем код OpenMP, в другой ответ.

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

#define NUM_OF_CORES 8
#define MAX_PRIME 100000

void do_primes()
{
    unsigned long i, num, primes = 0;
    for (num = 1; num <= MAX_PRIME; ++num) {
        for (i = 2; (i <= num) && (num % i != 0); ++i);
        if (i == num)
            ++primes;
    }
    printf("Calculated %d primes.\n", primes);
}

int main(int argc, char ** argv)
{
    time_t start, end;
    time_t run_time;
    unsigned long i;
    pid_t pids[NUM_OF_CORES];

    /* start of test */
    start = time(NULL);
    for (i = 0; i < NUM_OF_CORES; ++i) {
        if (!(pids[i] = fork())) {
            do_primes();
            exit(0);
        }
        if (pids[i] < 0) {
            perror("Fork");
            exit(1);
        }
    }
    for (i = 0; i < NUM_OF_CORES; ++i) {
        waitpid(pids[i], NULL, 0);
    }
    end = time(NULL);
    run_time = (end - start);
    printf("This machine calculated all prime numbers under %d %d times "
           "in %d seconds\n", MAX_PRIME, NUM_OF_CORES, run_time);
    return 0;
}

Выход

$ ./primes 
Calculated 9592 primes.
Calculated 9592 primes.
Calculated 9592 primes.
Calculated 9592 primes.
Calculated 9592 primes.
Calculated 9592 primes.
Calculated 9592 primes.
Calculated 9592 primes.
This machine calculated all prime numbers under 100000 8 times in 8 seconds

Ответ 3

мы действительно хотим видеть, как быстро он может идти!

Ваш алгоритм генерации простых чисел очень неэффективен. Сравните его с primegen, который генерирует 50847534 простых чисел до 1000000000 всего за 8 секунд на Pentium II-350.

Чтобы легко использовать все процессоры, вы можете решить смущающую параллельную проблему, например, вычислить Mandelbrot set или используйте генетическое программирование для рисования Mona Lisa в нескольких потоках (процессах).

Другой подход - взять существующую тестовую программу для суперкомпьютера Cray и перенести ее на современный ПК.

Ответ 4

Причина, по которой вы получаете 15% от шестисегментного процессора, заключается в том, что ваш код использует 1 ядро ​​на 100%. 100/6 = 16,67%, что с использованием скользящей средней с планированием процесса (ваш процесс будет работать под обычным приоритетом) можно легко сообщить как 15%.

Поэтому, чтобы использовать 100% -ный процессор, вам нужно будет использовать все ядра вашего процессора - запустить 6 параллельных путей кода для шестнадцатеричного ядра и получить этот масштаб вплоть до многих процессоров, на которых работает ваша машина Cray:)

Ответ 5

Также следует понимать, что как загружается ЦП. ЦП может выполнять множество различных задач, и, хотя многие из них будут объявлены как "загрузка 100% ЦП", каждый из них может использовать 100% различных частей ЦП. Другими словами, очень сложно сравнивать два разных процессора для производительности и особенно две разные архитектуры процессоров. Выполнение задачи A может помочь одному процессору над другим, при выполнении задачи B это может быть легко наоборот (поскольку два процессора могут иметь разные ресурсы внутри и могут выполнять код очень по-разному).

Именно поэтому программное обеспечение так же важно для обеспечения оптимальной работы компьютеров, как и аппаратное обеспечение. Это действительно справедливо и для "суперкомпьютеров".

Одним из показателей производительности процессора может быть инструкция в секунду, но опять же инструкции не создаются равными для разных архитектур ЦП. Другой мерой может быть производительность кэша IO, но инфраструктура кэша также не равна. Тогда мерой может быть количество инструкций на ватт, поскольку подача питания и рассеивание часто являются ограничивающим фактором при проектировании кластерного компьютера.

Итак, ваш первый вопрос должен быть: Какой параметр производительности важен для вас? Что вы хотите измерить? Если вы хотите увидеть, какая машина получает большинство FPS из Quake 4, ответ прост; ваша игровая установка будет, так как Cray не сможет запустить эту программу вообще; -)

Cheers, Стин

Ответ 6

Попробуйте распараллелить свою программу, используя, например, OpenMP. Это очень простая и эффективная структура для составления параллельных программ.

Ответ 7

Для быстрого улучшения на одном ядре удалите системные вызовы, чтобы уменьшить контекст-коммутацию. Удалите эти строки:

system("clear");
printf("%d prime numbers calculated\n",primes);

Первое особенно плохое, так как оно будет порождать новый процесс на каждой итерации.

Ответ 8

TL;DR; Принятый ответ неэффективен и несовместим. Следующий алгоритм работает 100x быстрее.

Компилятор gcc, доступный в MAC, не может работать omp. Мне пришлось установить llvm (brew install llvm ). Но я не видел, что простаивание процессора снижалось при запуске версии OMP.

Вот скриншот, когда была запущена версия OMP. введите описание изображения здесь

В качестве альтернативы я использовал основной поток POSIX, который может быть запущен с использованием любого компилятора c, а видел почти весь CPU, который использовался, когда nos of thread= no of cores= 4 (MacBook Pro, 2,3 ГГц Intel Core i5). Вот программа -

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define NUM_THREADS     10
#define THREAD_LOAD 100000
using namespace std;

struct prime_range {
    int min;
    int max;
    int total;
};

void* findPrime(void *threadarg)
{
    int i, primes = 0;
    struct prime_range *this_range;
    this_range = (struct prime_range *) threadarg;

    int minLimit =  this_range -> min ;
    int maxLimit =  this_range -> max ;
    int flag = false;
    while (minLimit <= maxLimit) {
        i = 2;
        int lim = ceil(sqrt(minLimit));
        while (i <= lim) {
            if (minLimit % i == 0){
                flag = true;
                break;
            }
            i++;
        }
        if (!flag){
            primes++;
        }
        flag = false;
        minLimit++;
    }
    this_range ->total = primes;
    pthread_exit(NULL);
}

int main (int argc, char *argv[])
{
    struct timespec start, finish;
    double elapsed;

    clock_gettime(CLOCK_MONOTONIC, &start);

    pthread_t threads[NUM_THREADS];
    struct prime_range pr[NUM_THREADS];
    int rc;
    pthread_attr_t attr;
    void *status;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    for(int t=1; t<= NUM_THREADS; t++){
        pr[t].min = (t-1) * THREAD_LOAD + 1;
        pr[t].max = t*THREAD_LOAD;
        rc = pthread_create(&threads[t], NULL, findPrime,(void *)&pr[t]);
        if (rc){
            printf("ERROR; return code from pthread_create() is %d\n", rc);
            exit(-1);
        }
    }
    int totalPrimesFound = 0;
    // free attribute and wait for the other threads
    pthread_attr_destroy(&attr);
    for(int t=1; t<= NUM_THREADS; t++){
        rc = pthread_join(threads[t], &status);
        if (rc) {
            printf("Error:unable to join, %d" ,rc);
            exit(-1);
        }
        totalPrimesFound += pr[t].total;
    }
    clock_gettime(CLOCK_MONOTONIC, &finish);
    elapsed = (finish.tv_sec - start.tv_sec);
    elapsed += (finish.tv_nsec - start.tv_nsec) / 1000000000.0;
    printf("This machine calculated all %d prime numbers under %d in %lf seconds\n",totalPrimesFound, NUM_THREADS*THREAD_LOAD, elapsed);
    pthread_exit(NULL);
}

Обратите внимание, как весь процессор исчерпан - введите описание изображения здесь

P.S. - Если вы увеличиваете количество потоков, то фактическое использование ЦП уменьшается (попробуйте сделать нити = 20.), Поскольку система использует больше времени при переключении контекста, чем фактические вычисления.

Кстати, моя машина не такая мудрая, как @mystical (Accepted answer). Но моя версия с базовой поточной POSIX работает быстрее, чем OMP. Вот результат -

введите описание изображения здесь

P.S. Увеличьте поток до 2,5 миллионов, чтобы увидеть использование ЦП, поскольку оно завершается менее чем за секунду.

Ответ 9

Просто попробуйте Zip и распакуйте большой файл, ничего, поскольку тяжелые операции ввода-вывода могут использовать cpu.