首页 文章

如何从C程序获得100%的CPU使用率

提问于
浏览
77

这是一个非常有趣的问题,所以让我设置场景 . 我在国家计算机博物馆工作,我们刚刚设法从1992年开始运行一台Cray Y-MP EL超级计算机,我们真的想看看它有多快!

我们认为最好的方法是编写一个简单的C程序来计算素数,并显示这需要多长时间,然后在快速的现代台式PC上运行程序并比较结果 .

我们很快想出了这个代码来计算素数:

#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),工作得很好,CPU使用率达到100%,大约需要10分钟左右 . 当我回到家时,我决定在我的六核现代游戏PC上试用它,这就是我们第一个问题 .

我首先调整了在Windows上运行的代码,因为这是游戏PC正在使用的,但很遗憾发现这个过程只获得了大约15%的CPU功率 . 我认为Windows必须是Windows,因此我启动了Ubuntu的Live CD,认为Ubuntu将允许该进程以其早期在我的笔记本电脑上完成的全部潜力运行 .

但是我只有5%的使用率!所以我的问题是,我怎样才能使程序在Windows 7或Linux上以100%的CPU利用率在我的游戏机上运行?另一件很棒但不必要的事情是,最终产品可以是一个可以在Windows机器上轻松分发和运行的.exe .

非常感谢!

附:当然这个程序并没有真正与Crays 8专业处理器配合使用,这是另外一个问题......如果你对90码Cray超级计算机的优化代码有所了解,那也给我们一个大喊大叫!

9 回答

  • 23

    如果您想要100%CPU,则需要使用1个以上的核心 . 要做到这一点,您需要多个线程 .

    Here's a parallel version using 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;
    }
    

    Output:

    该机器在29.753秒内计算出1000000以下的所有78498素数

    Here's your 100% CPU:

    enter image description here

  • 0

    您在多核计算机上运行一个进程 - 因此它只在一个核心上运行 .

    解决方案很简单,因为你只是试图固定处理器 - 如果你有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
    
  • 4

    我们真的想看看它有多快!

    生成素数的算法效率很低 . 将它与primegen进行比较,在Pentium II-350上仅需8秒即可生成50847534素数至1000000000 .

    要轻松使用所有CPU,您可以在多个线程(进程)中解决embarrassingly parallel problem,例如,计算Mandelbrot set或使用genetic programming to paint Mona Lisa .

    另一种方法是为Cray超级计算机采用现有的基准程序并将其移植到现代PC .

  • 0

    您在十六进制核心处理器上获得15%的原因是因为您的代码在100%时使用了1个核心 . 100/6 = 16.67%,使用具有流程调度的移动平均值(您的流程将在正常优先级下运行)可以轻松报告为15% .

    因此,为了使用100%的CPU,您需要使用CPU的所有内核 - 为十六进制核心CPU启动6个并行执行代码路径,并且这个规模可以达到您的Cray机器所拥有的许多处理器:)

  • 2

    另外要非常清楚 how 你很难比较两个不同的CPU的性能,特别是两种不同的CPU架构 . 执行任务A可能有利于一个CPU而不是另一个CPU,而执行任务B则可以很容易地相反(因为两个CPU内部可能具有不同的资源并且可能以非常不同的方式执行代码) .

    这就是软件与硬件一样使计算机表现最佳的原因 . 对于“超级计算机”来说,这确实也是如此 .

    CPU性能的一个衡量指标可能是每秒指令,但是在不同的CPU架构上,指令不会相同 . 另一项措施可能是缓存IO性能,但缓存基础架构也不相同 . 然后,度量可以是每瓦使用的指令数量,作为功率在设计集群计算机时,传递和耗散通常是一个限制因素 .

    所以你的第一个问题应该是:哪个性能参数对你很重要?你想测量什么?如果你想看看哪个机器从Quake 4中获得最多的FPS,答案很简单;你的游戏装备会,因为Cray根本无法运行该程序;-)

    干杯,斯蒂恩

  • 0

    尝试使用例如OpenMP并行化您的程序 . 它是组成并行程序的一个非常简单有效的框架 .

  • 81

    要在一个核心上快速改进,请删除系统调用以减少上下文切换 . 删除这些行:

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

    第一个特别糟糕,因为它会在每次迭代时产生一个新进程 .

  • 8

    只需尝试Zip和解压缩一个大文件,没有任何重型I / O操作可以使用cpu .

  • 0

    TLDR;接受的答案既低效又不兼容 . 以下算法更快地工作 100x .

    MAC上可用的gcc编译器无法运行 omp . 我不得不安装llvm (brew install llvm ) . 但是我在运行OMP版本时 didn't see CPU idle was going down .

    这是OMP版本运行时的屏幕截图 .
    enter image description here

    或者,我使用了基本的POSIX线程,当 nos of thread = no of cores = 4(MacBook Pro,2.3 GHz Intel Core i5)时,可以使用任何c编译器和 saw almost entire CPU used up 运行 . 这是程序 -

    #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);
    }
    

    注意整个CPU是如何用完的 -
    enter image description here

    附: - 如果增加线程数,则实际CPU使用率下降(尝试不使用线程= 20 . )因为系统在上下文切换中使用的时间比实际计算多 .

    顺便说一句,我的机器不像@mystical那样强壮(接受的答案) . 但我的基本POSIX线程版本比OMP版本更快 . 结果如下 -

    enter image description here

    附:将线程负载增加到250万以查看CPU使用情况,因为它在不到一秒的时间内完成 .

相关问题