为什么这个 Sieve of Sundaram 实现比这个 Sieve of Eratosthenes 实现快得多?

Why is this Sieve of Sundaram implementation much faster than this Sieve of Eratosthenes implementation?

提问人:trietng 提问时间:11/15/2022 更新时间:11/27/2022 访问量:143

问:

我目前正在尝试比较两种不同质数生成算法的平均运行时速度。我对埃拉托色尼的筛子有这个幼稚的实现:

std::vector<int32_t> sieve_of_eratosthenes(int32_t n) {
    std::vector<int32_t> primes;
    std::vector<bool> sieve(n + 1, true);
    for (int32_t i = 2; i * i <= n; i++) {
        if (sieve[i]) {
            for (int j = i * i; j <= n; j += i) {
                sieve[j] = false;
            }
        }
    }
    for (int32_t i = 2; i < n; ++i) {
        if (sieve[i]) primes.push_back(i);
    }
    return primes;
}

以及 Sundaram 筛子的实现:

std::vector<int32_t> sieve_of_sundaram(int32_t n) {
    std::vector<int32_t> primes;
    int32_t k = (n - 1) / 2;
    std::vector<bool> sieve(k + 1, true);
    for (int32_t i = 1; i * i <= k; ++i) {
        if (sieve[i]) {
            for (int32_t j = i; i + j + 2 * i * j <= k; ++j) {
                sieve[i + j + 2 * i * j] = false;
            }
        }
    }
    if (n > 2) primes.push_back(2);
    for (int32_t i = 1; i <= k; ++i) {
        if(sieve[i]) primes.push_back(2 * i + 1);
    }
    return primes;
}

这就是我尝试测量算法速度的方式。 是从命令行输入的参数。主程序将手动运行 2 次,第一次将打印出 的平均速度,第二次将是 的平均速度。testsieve_of_eratosthenessieve_of_sundaram

std::vector<int32_t> test_input(1000, 100000);
std::vector<int32_t> result;
switch (test) {
    case 1:
        for (auto n : test_input) {
            auto start = std::chrono::high_resolution_clock::now();
            auto tmp = sieve_of_eratosthenes(n);
            auto end = std::chrono::high_resolution_clock::now();
            int32_t runtime = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
            result.push_back(runtime);
        }
        break;
    case 2:
        for (auto n : test_input) {
            auto start = std::chrono::high_resolution_clock::now();
            auto tmp = sieve_of_sundaram(n);
            auto end = std::chrono::high_resolution_clock::now();
            int32_t runtime = std::chrono::duration_cast<std::chrono::nanoseconds>(endd - start).count();
            result.push_back(runtime);
        }
        break;
    default:
        break;
}
std::cout << get_avg(result); // sum of all test results / result.size()

根据理论时间复杂度,埃拉托色尼筛子应该给出更快的结果(比 快,这是孙达拉姆筛子的时间复杂度)。然而,Sundaram 的筛子实际上要快得多(几乎是埃拉托色尼筛子的 3 倍)。O(nlog(logn))O(nlogn)

结果(以纳秒为单位):

434379
179904
C++ Primes 实现 SIEVE-OF-ERATOSTHENES SIEVE

评论

0赞 273K 11/15/2022
你如何编译?
0赞 trietng 11/15/2022
@273K我使用带有 -O2 标志的 mingw g++ 进行编译。
0赞 Captain Giraffe 11/15/2022
loglog(n) 和 log(n) 之间的差异非常小,以至于常数因子很快就会发挥作用。
0赞 trietng 11/15/2022
@CaptainGiraffe我用 1000000 再次尝试,它仍然是乘以 10 的差异。
1赞 Bob__ 11/15/2022
好吧,看看内部循环执行了多少次:godbolt.org/z/nK1r4caM4

答:

2赞 oluckyman 11/25/2022 #1

您没有编写经典的 Sundaram 筛子(按几率进行筛选,确实需要 O(N log N) 操作),而是使用了一个小的修改 (),使其等同于埃拉托色尼筛子的版本,避免考虑偶数。if (sieve[i]) ...

由于您的其他代码使用经典的 Eratosthenes 筛子(按数进行筛选)并且确实考虑了偶数,因此您所谓的 Sundaram 筛子代码确实按常数因子更快。

这在维基百科上关于Sundaram筛子的文章中进行了解释。