【问题标题】:How to get 100% CPU usage from a C program如何从 C 程序中获得 100% 的 CPU 使用率
【发布时间】:2012-03-03 21:47:41
【问题描述】:

这是一个非常有趣的问题,所以让我来设置一下场景。我在国家计算机博物馆工作,我们刚刚设法让一台 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)的双核笔记本电脑上运行良好,获得 100% 的 CPU 使用率,大约需要 10 分钟左右。回到家后,我决定在我的六核现代游戏 PC 上尝试一下,这就是我们遇到的第一个问题。

我首先将代码修改为在 Windows 上运行,因为这是游戏 PC 所使用的,但很遗憾地发现该进程仅获得了 CPU 功率的 15% 左右。我想这一定是 Windows 就是 Windows,所以我启动到 Ubuntu 的 Live CD 中,认为 Ubuntu 将允许该进程充分发挥其潜力,就像它之前在我的笔记本电脑上所做的那样。

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

非常感谢!

附:当然,这个程序并不能真正与 Crays 8 专业处理器一起工作,那完全是另外一回事了……如果你知道任何关于优化代码以在 90 年代 Cray 超级计算机上工作的知识,也请给我们留言!

【问题讨论】:

  • 我不敢相信没有 unicos 标记。 ;)
  • 奇怪的是,这个单线程程序在双核处理器上占用了 100% 的 CPU 使用率)))
  • 只有我觉得这个问题一点都不有趣吗?来一个,在 n 核机器上运行一个单线程程序并询问它为什么使用 1/n 的 cpu 只是......没关系,我只是投反对票:-)
  • @drhirsch 好吧,这个问题显示了研究工作。我为此 +1 了 - 即使 OP 缺少关于多核计算的一些基本知识。
  • @drhirsch 网站上有很多无趣的问题。然而,有趣与否是主观的。他可能缺少基本面,这不是主观的。就像 Mystical 所说的那样,它确实显示了研究成果,但并不像看起来那么容易回答。

标签: c windows linux performance cray


【解决方案1】:

如果你想要 100% CPU,你需要使用超过 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;
}

输出:

这台机器在 29.753 秒内计算了 1000000 以下的所有 78498 个素数

这是你的 100% CPU:

【讨论】:

  • @cha0site 是的,我主要回答了游戏机的问题。肯定有更有趣的方法来固定 CPU。我做过的最臭名昭著的基准测试之一是我对this question 的回答——它使我测试的 4 台机器中的 2 台过热。
  • @Mystical Offtopic:你在运行什么硬件?我的 Hex-Core AMD @ 3.2Ghz 在 92 秒内完成了...
  • @Owen:他有一个Core i7 2600K...我很嫉妒。
  • 啊!太……太……粉红色!
  • @MohammadFadin en.wikipedia.org/wiki/Parallel_computing 基本上,您需要能够并行处理多个任务才能使用多核计算机。
【解决方案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

【讨论】:

  • 啊有点像当你需要运行 Prime95 时,你有多个它的实例......当然有一种方法可以让一个进程使用多个内核?就像哈希破解程序一样。
  • 嗯,一个进程可以使用线程进行多处理,但我不认为这就是你的意思,因为在这种情况下线程几乎是一个单独的进程。我们在这里真正谈论的是“执行头”,无论是线程还是进程。所以,不,没有办法让单线程程序在多核上运行,你必须重写它。有时它真的很难。有时这实际上是不可能的。
  • 好吧,我想这不会像让程序也为 Cray 工作那么难。考虑到我对此很陌生(是什么让我离开了:P),从哪里开始呢?
  • @Owen:嗯,UNICOS 看起来有点类似于 Unix(无论如何维基百科都这么认为),所以它可能有 fork()。我想你应该去学习如何使用它。
  • 哦!既然您有示例,请 +1。 :)
【解决方案3】:

我们真的很想看看它能跑多快!

您生成素数的算法非常低效。将其与 primegen 进行比较,后者在 Pentium II-350 上只需 8 秒即可生成 50847534 个素数,最高可达 1000000000。

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

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

【讨论】:

  • 算法效率低下并不重要,因为目标不是实际计算素数,而是执行一般困难的任务,看看它比现代桌面。一个有效的算法只会使比较变得更难,如果它非常好以至于故意利用现代 CPU 功能/怪癖,甚至可能会破坏结果。
【解决方案4】:

您在六核处理器上获得 15% 的原因是因为您的代码以 100% 使用 1 个内核。 100/6 = 16.67%,使用带有进程调度的移动平均值(您的进程将在正常优先级下运行)可以很容易地报告为 15%。

因此,为了使用 100% 的 cpu,您需要使用 CPU 的所有内核 - 为十六进制 CPU 启动 6 个并行执行代码路径,并使其扩展到您的 Cray 机器拥有的处理器数量:)

【讨论】:

  • 这样做的问题是我怎样才能清楚地了解每台机器的速度? Cray 显然也有“矢量处理器”,因此它需要比这更多的工作才能使其正常运行
  • 不知道。可能在调度过程中有所不同。
【解决方案5】:

还要非常注意如何加载 CPU。一个 CPU 可以执行许多不同的任务,虽然其中许多任务会被报告为“100% 加载 CPU”,但它们可能每个都使用了 100% 的 CPU 不同部分。换句话说,很难比较两种不同 CPU 的性能,尤其是两种不同的 CPU 架构。执行任务 A 可能有利于一个 CPU 而不是另一个,而执行任务 B 则很容易反过来(因为两个 CPU 内部可能有不同的资源,并且执行代码可能非常不同)。

这就是软件对于使计算机性能与硬件一样重要的原因。这对于“超级计算机”来说也确实如此。

衡量 CPU 性能的一个指标可能是每秒指令数,但同样,指令在不同 CPU 架构上的创建方式并不相同。另一个衡量标准可能是缓存 IO 性能,但缓存基础设施也不相同。然后衡量指标可能是每瓦使用的指令数,因为在设计集群计算机时,功率传输和耗散通常是一个限制因素。

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

干杯, 斯蒂恩

【讨论】:

    【解决方案6】:

    TLDR; 接受的答案既低效又不兼容。跟随算法的工作速度 100 倍

    MAC 上可用的 gcc 编译器无法运行omp。我必须安装 llvm (brew install llvm )。但我在运行 OMP 版本时没有看到 CPU 空闲下降

    这是 OMP 版本运行时的屏幕截图。

    或者,我使用了基本的 POSIX 线程,它可以使用任何 c 编译器运行,并且当nos of thread = no of cores = 4(MacBook Pro,2.3 GHz)时,几乎整个 CPU 都用完了英特尔酷睿 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);
    }
    

    注意整个 CPU 是如何用完的 -

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

    顺便说一句,我的机器不如@mystical 强大(已接受答案)。但是我的带有基本 POSIX 线程的版本比 OMP 更快。这是结果-

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

    【讨论】:

      【解决方案7】:

      尝试使用例如 OpenMP 来并行化您的程序。它是一个非常简单有效的构建并行程序的框架。

      【讨论】:

        【解决方案8】:

        为了快速改进一个内核,删除系统调用以减少上下文切换。删除这些行:

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

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

        【讨论】:

          【解决方案9】:

          只需尝试对大文件进行 Zip 和 Unzip 压缩,没有什么可以使用 cpu 的繁重 I/O 操作。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2023-02-11
            • 2017-11-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-08-25
            • 1970-01-01
            相关资源
            最近更新 更多