问题
I've written a small C program to calculate primes and now try to optimize the code as far as I can.
In the first revision of the program I was checking if a number was even (modulo 2) and if it was I would continue to the next number.
In my second revision I tried only checking odd numbers to be possible primes by incrementing the number I would be checking by 2 (so I would start from 3, then check 5, then 7, then 9, then 11 etc etc).
I thought this would be way faster as I had cut an extra check for modulo 2 with my code and simply replaced it with an addition. However, to my surprise the code checking only odd numbers, runs a tad bit slower most of the times then the implementation checking all numbers.
Here is the code(it contains the changes I made between revisions in comments wherever it says //CHANGE)
#include <stdio.h>
#include <stdbool.h>
#include <math.h>
unsigned long i = 3; //CHANGE No 1 - it was i = 2;
bool hasDivisor(unsigned long number)
{
//https://stackoverflow.com/questions/5811151/why-do-we-check-up-to-the-square-root-of-a-prime-number-to-determine-if-it-is-pr
unsigned long squareRoot = floor(sqrt(number));
//we don't check for even divisors - we want to check only odd divisors
//CHANGE No 2 - it was (number%2 ==0)
if(!((squareRoot&1)==0)) //thought this would boost the speed
{
squareRoot += 1;
}
for(unsigned long j=3; j <= squareRoot; j+=2)
{
//printf("checking number %ld with %ld \n", number, j);
if(number%j==0)
{
return true;
}
}
return false;
}
int main(int argc, char** argv)
{
printf("Number 2 is a prime!\n");
printf("Number 3 is a prime!\n");
while(true)
{
//even numbers can't be primes as they are a multiple of 2
//so we start with 3 which is odd and contiously add 2
//that we always check an odd number for primality
i++; //thought this would boost the speed instead of i +=2;
i++; //CHANGE No 3 - it was a simple i++ so eg 3 would become 4 and we needed an extra if(i%2==0) here
//search all odd numbers between 3 and the (odd ceiling) sqrt of our number
//if there is perfect division somewhere it's not a prime
if(hasDivisor(i))
{
continue;
}
printf("Number %ld is a prime!\n", i);
}
return 0;
}
I'm using Arch Linux x64 with GCC version 8.2.1 and compiling with:
gcc main.c -lm -O3 -o primesearcher
Although I tested with O1 and O2, as well.
I'm "benchmarking" with the command below:
./primesearcher & sleep 10; kill $!
which runs the program for 10seconds and outputs primes to the terminal for that time and then kills it. I obviously tried allowing the program to run more (30, 60 and 180 seconds) but the results are about 9/10 of the time in favor of the version checking even numbers (modulo 2 version has found a bigger prime before it got killed).
Any idea why would this be happening? Maybe something wrong in terms of code in the end?
回答1:
Code is slower if(!((squareRoot&1)==0))
than with no test because it rarely has benefit.
Keep in mind that for most number
, the iteration limit is never reached due to an early return from the number%j
test. Primes tend to become rare as number
grows.
An rare extra iteration is not offset by the repetitive cost of the test.
Comparing !((squareRoot&1)==0)
to number%2 ==0
is moot.
OP's method of testing speed is error prone when differences are small: "runs a tad bit slower most of the times" shows the inconsistency.
A huge amount of time is in the printf()
. To compare prime computation performance, I/O needs to be eliminated.
The kill
is not that precise either.
Instead, form a loop to stop when i
reaches a value like 4,000,000,000 and time that.
Code has other weaknesses:
unsigned long squareRoot = floor(sqrt(number));
can creates the wrong root for large number
due to rounding when converting number
to double
and imprecision of the sqrt()
routine. OP's reference addresses the mathematical algorithm need. Yet this C code implementation can readily fail given the limitations of real computations.
Instead, suggest
// Prime test for all unsigned long number
bool isPrime(unsigned long number) {
if (number % 2 == 0) { // This can be eliminated if `number` is always odd.
return number == 2;
}
for (unsigned long j = 3; j <= number/j; j += 2) {
if (number%j == 0) {
return false;
}
}
return number > 2;
}
The cost of number/j
is often nil with modern compilers as they see number%j
and effectively compute both quotient and remainder at once. Thus the limit of j <= squareRoot
is achieved 1) without expensive sqrt()
calculation 2) is accurate for large number
, unlike sqrt()
usage.
Use matching specifiers. u
, not d
for unsigned types.
// printf("Number %ld is a prime!\n", i);
printf("Number %lu is a prime!\n", i);
Use of a global i
here is a weak coding style. Suggest re-coding and pass by function instead.
For more substantial improvements, look to Sieve of Eratosthenes and keeping a list of formerly found primes and test those rather than all odd numbers.
Prime testing is a deep subject with many more advanced ideas.
来源:https://stackoverflow.com/questions/54158929/calculating-primes-using-only-odd-divisors-is-slower