簡體   English   中英

基本的 C 基准計算 Primes 會拋出瘋狂的結果,然后在 Windows 上廢話?

[英]Rudimentary C Benchmark calculating Primes throws up crazy results and then craps out on Windows?

我編寫了一個基本的 C 程序(Prime C 基准測試的 PCB),它通過計時為 0 和用戶輸入的數字之間的所有自然數查找素數的過程來對系統速度進行基准測試 [用戶輸入一個“加載值”,它乘以10^5]

在我的 Intel i5 5350U 和 LPDDR3(MacBook Air 2017,使用 Apple Clang 11)上 1 的工作負載(即最高 100,000 次)運行 5 次,平均需要 25 秒(插入電源,充電,但在 5% 電池電量時達到 50 秒)。

在我的 Exynos 9611 和 LPDDR4x(Samsung M21,使用“Coding C”應用程序/編譯器)上完全相同的工作負載並運行 5 次,平均需要8秒!

在 Windows(i5 3340M,Win7_SP2,VS2019 最新版本,發布版本,x86)上,當針對任何“加載值”運行 5 次時,該程序絕對崩潰。 我得到的時間是 0?0000.. 什么.. 這里絕對有問題 XD ....\

Linux(Ubuntu 20.04,GCC,與 Win,i3 相同的硬件)需要 21.5 秒。 在我看來,Linux 和 MacOS(所以 Apple Clang 和 GCC)可能做得對......

編碼:

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

long count = 0;

void bench(double x) {
    register unsigned long n, i, q;
    for (q = 0; q <= x; q++) {
        for (i = 2; i <= q / 2; ++i) {
            if (q % i == 0)
                count++;
        }
    }  
}

int main() {
    double x;
    int y;
    printf( "\nPCB v0.1\nOpen-source Tool for Benchmarking System Speed.\n\nRecommended Load Value 1 - 3\n");
    printf("\nEnter Load Value : ");
    scanf("%lf", &x);
    printf("\nEnter Frequency for Repetition : ");
    scanf("%d", &y);
    x = x * 100000;
    printf("\nPress Enter to Run ");
    getchar();
    getchar();
    printf("\n(...Running...)\n");
    int z;
    for (z = 1; z <= y; z++) {
        clock_t t; 
        t = clock(); 
        bench(x); 
        t = clock() - t; 
        double time_taken = ((double)t)/CLOCKS_PER_SEC; // in seconds 
        printf("\nTime Taken #%d = %.4f seconds\n", z, time_taken);
    }
    printf("\nPress Enter to Exit ");
    getchar();
    return count;
}

您的素數枚舉代碼有缺陷:

  • 在最初發布的代碼中, benchmark function 中的for循環沒有副作用,因此高效的編譯器能夠對其進行優化並且基本上不生成任何代碼。 這解釋了從一個系統到另一個系統的巨大差異。

  • 在上次更新中,您的算法不計算素數的計數,它僅執行大量除法並計算您獲得零余數的次數。 這比實際素數測試的成本要高得多,而實際素數測試本身的效率遠低於執行Eratostenes 篩法

為了測量和比較系統性能,這種方法誇大了除法操作碼的速度,它顯示出Linux、OS/X和Windows之間的差異很大,可能是因為unsigned long類型的大小為64位在 Linux 和 OS/X 與 Windows 上的 32 位上,使 Windows 上的模運算更快,即使對於同一組數字也是如此。 此外,這種類型的基准測試使用單核,因此它不能長期測量整體系統性能。

不同系統的相對性能應使用更多樣化的操作集來衡量,重點關注 CPU、memory、存儲和通信系統。

關於素數枚舉,這里是一個帶有素數測試的修改版本:

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

unsigned long long bench(double x) {
    if (x < 0 || x >= ULLONG_MAX) {
        printf("invalid benchmark range\n");
        return 0;
    }
    unsigned long long n = (unsigned long long)x;
    unsigned long long count = 0;
    if (n >= 2)
        count++;
    for (unsigned long long p = 3; p <= n; p += 2) {
        count++;
        for (unsigned long long i = 3; i * i <= p; i += 2) {
            if (p % i == 0) {
                count--;
                break;
            }
        }
    }
    return count;
}

int main() {
    double x;
    int y;
    clock_t total = 0;
    unsigned long long count;
    double time_taken;

    printf("\nPCB v0.1\nOpen-source Tool for Benchmarking System Speed.\n\nRecommended Load Value 1 - 3\n");
    printf("\nEnter load value: ");
    if (scanf("%lf", &x) != 1)
        return 1;
    printf("\nEnter repeat count: ");
    if (scanf("%d", &y) != 1)
        return 1;
    x = x * 100000;
    printf("\nPress Enter to Run ");
    getchar();
    getchar();
    printf("\n(...Running...)\n");
    for (int z = 0; z < y; z++) {
        clock_t t;
        t = clock();
        count = bench(x);
        t = clock() - t;
        total += t;
        time_taken = ((double)t) / CLOCKS_PER_SEC; // in seconds
        printf("\n%llu primes, time taken #%d = %.4f seconds\n", count, z, time_taken);
    }
    time_taken = ((double)total) / CLOCKS_PER_SEC; // in seconds
    printf("\nAverage time taken = %.4f seconds\n", time_taken / y);
    printf("\nPress Enter to Exit ");
    getchar();
    return 0;
}

Output:

PCB v0.1
Open-source Tool for Benchmarking System Speed.
Recommended Load Value 1 - 3

Enter load value: 1
Enter repeat count: 5
Press Enter to Run
(...Running...)

9592 primes, time taken #0 = 0.0126 seconds
9592 primes, time taken #1 = 0.0117 seconds
9592 primes, time taken #2 = 0.0133 seconds
9592 primes, time taken #3 = 0.0136 seconds
9592 primes, time taken #4 = 0.0137 seconds

Average time taken = 0.0130 seconds
Press Enter to Exit

這比我筆記本電腦上的初始代碼快了近 2000 倍。

運行 100 的負載給出了這個 output:

PCB v0.1
Open-source Tool for Benchmarking System Speed.
Recommended Load Value 1 - 3

Enter load value: 100
Enter repeat count: 5
Press Enter to Run
(...Running...)

664579 primes, time taken #0 = 7.4249 seconds
664579 primes, time taken #1 = 7.3742 seconds
664579 primes, time taken #2 = 7.4119 seconds
664579 primes, time taken #3 = 7.3887 seconds
664579 primes, time taken #4 = 7.6725 seconds

Average time taken = 7.4544 seconds
Press Enter to Exit

這仍然比篩子慢得多:

$ chqrlie > time prime -c 1..10000000
664579

real    0m0.009s
user    0m0.006s
sys     0m0.001s

這是一個使用 Sieve 方法的簡單實現,它的速度不如我的primes實用程序中使用的優化方法快,但對於 100 的負載仍然實現了0,0773 seconds的平均時間,比 Prime 測試循環提高了100 倍

unsigned long long bench(double x) {
    /* simplistic Sieve of Eratostenes version */
    if (x < 0 || x >= SIZE_MAX) {
        printf("invalid benchmark range\n");
        return 0;
    }
    size_t count = 0;
    size_t n = (size_t)x + 1;   // array size
    if (n > 1) {
        unsigned char *a = calloc(n, 1);
        if (a == NULL) {
            printf("cannot allocate memory\n");
            return 0;
        }
        // 0 and 1 are considered composite
        a[0] = a[1] = 1;
        // flag all multiples of 2 as composite
        for (size_t i = 4; i < n; i += 2) {
            a[i] = 1;
        }
        for (size_t p = 3; p * p < n; p += 2) {
            // for all potential prime numbers
            if (a[p] == 0) {
                // if p is prime, flag all odd multiples of p as composite
                for (size_t i = p * p; i < n; i += 2 * p) {
                    a[i] = 1;
                }
            }
        }
        count = n;
        // count the number of composite numbers
        for (size_t i = 0; i < n; i++) {
            count -= a[i];
        }
        free(a);
    }
    return count;
}

我建議將bench function 更改為

unsigned  long bench (double x) {
    register unsigned long n;
    register unsigned long i, q;
    unsigned long count = 0;
    for(q = 0;q <= x; q++){
        int prime = 0;
        for (i = 2; i <= q/2 ; ++i) {
            if (q % i == 0) {
                prime = 1;
        break;
            }
        }
        if (prime)
            count++;
    }
    return count;
}

main的 function 到:

int main() {
    ...
    unsigned  long count = 0;
    for(z=1;z <=y; z++)
    {
        clock_t t; 
        t = clock(); 
        unsigned  long c = bench(x); 
        t = clock() - t; 
        count += c;
        double time_taken = ((double)t)/CLOCKS_PER_SEC; // in seconds 
        printf("\nTime Taken #%d = %.4f seconds\n", z, time_taken);
    }
    printf("\nPress Enter to Exit ");
    getchar();
    return count > 0 ? 0 : 1; // ensures the use of the return values of bench
}

此外,為了准確測量,您應該確保所有編譯器都以最大優化進行編譯。 這很重要,因為這可能使編譯器有機會使用SIMD指令來加速工作台操作。

但也請注意,僅通過一個操作對系統/CPU 進行標記並不是系統/CPU 之間一般比較的良好基礎。 例如,您的bench function 僅用於測試 CPU 划分大量連續數字的速度。

@chqrlie 走在正確的道路上......

他是對的:

  • 任何進行良好優化的編譯器都會使 bench() 中的 for 循環變得無用(所以基本上是“編碼 C”應用程序,VS2019 但不是 Apple Clang 和 GCC)
  • 這僅測試整數計算,因此在所有情況下可能都不是很准確(這是一個可以接受的限制,但稍后會更多)
  • unsigned long 在 VS 中變成了 32 位,也許在“編碼 C”中也是如此,這使得它們比 VS 和“編碼 C”中成倍地快。
  • 他的代碼有正確的 bench() function,我用過這個,還有他的智能加法平均時間 output。

此外,我可能會補充:

除了 UI 改進,比如取消每個循環的輸出,並且只在此處和那里堅持平均值以及一些大寫字母,我還有:

  • 將 x=x 10^5 變為 x=x 10^7 以減少加載值膨脹(在我的設備上加載值 1 平均需要 8 秒,您仍然可以運行十進制值)。
  • 可能還有其他小改動,idk,代碼貼在下面。

對於任何更感興趣的人,我將使用文件中的源代碼構建 PCB 一段時間,請隨時查看或建議更改/批評代碼至 apjo@tuta.io

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM