Optimization
Probably the single biggest optimization on this code has nothing to do with the prime computation itself at all.
For the sake of testing, I changed it to generate primes up to 10000000, by modifying the outer loop to become:
for (int nur = 3; nur<10000000; nur += 2)
With the rest of the code as originally written, this ran in ~2300 ms. I then changed:
cout << nur << endl;
to:
cout << nur << "\n";
...and this reduced the time to ~1200 ms (approximately doubled the speed).
Another fairly minor change was to change from computing the square root, to computing the square of the trial divisor, and seeing if that was less than the number being tested. This doesn't make a huge difference in speed, but does help a bit.
The obvious step after that (at least to me) was to rewrite the code to encapsulate the logic into a class. I didn't expect this to give a huge increase in speed by itself, but did expect to get cleaner code that might be a little easier to optimize further. That gave this:
#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
#include <iterator>
class isprime {
std::vector<int> primes;
int n;
bool check(int n) {
auto div = primes.begin();
for ( ; *div * *div <= n; ++div)
if (n % *div == 0)
return false;
primes.push_back(n);
return true;
}
public:
isprime() : primes{ 2 }, n{ 3 } {}
int operator()() {
while (!check(n))
n += 2;
auto ret = n;
n += 2;
return ret;
}
};
int main() {
isprime p;
int prime;
while ((prime = p()) < 10000000)
std::cout << prime << '\n';
}
Somewhat surprisingly, this actually gave another fairly healthy improvement in speed, so it ran in about 900 ms. I decided that was enough, at least for now.
As far as reviewing the code otherwise goes:
indentation
Right now your indentation renders the code considerably less readable than it could be.
vertical whitespace
Much like your indentation, you have a fair (unfair?) amount of seemingly random vertical white space (i.e., blank lines) that don't seem to serve an purpose, and reduce visibility. I expect a blank line to be a little like a separation between paragraphs in text--a separation that signals a small change of subject, or something on that order.
Implement functions as functions
You don't need to stuff everything into main
. Some people do this in a mistaken attempt at optimization.
I wouldn't be afraid to implement the code to test whether a number is prime as a function instead of inline code. Compilers are usually pretty decent at figuring out where you'll get significant benefit from having the code generated inline, and doing so where it'll help (though MS does support __forceline
for those cases where it is possible, and for whatever reason the compiler chooses not to).
avoid std::endl
As shown above, std::end
can and does lead to significant slow-downs in some cases, especially when substantial amounts of output are being produced. If you just want a new-line, just write a new-line.
Don't be afraid of classes either
As shown here, encapsulating the code into a class didn't slow it down at all--in fact, it improved speed by 25% or so.
Multithreading
The obvious next step would be to make the code multi-threaded. One obvious way to do this would be to start by using one thread to compute the primes up to the square root of the maximum you care about. From there, split the job into N threads, each of which gets a copy of the primes found so far, and uses it to check every Nth candidate for primacy.
Perhaps less obviously (but only slightly so) the same process can be used recursively--that is, to compute the primes up to the square root of the limit, compute the primes up to the square root of that limit in a single thread, then split the job out into multiple threads from there (continue the same basic idea until you get such a small group of primes to find that splitting it among threads is no longer worthwhile).
Of course, after this you're left with a number of separate collections of primes. You'll need to merge those together to get a single collection that's all in order.
This potentially gives nearly perfect scaling, so with N cores it can run about N times faster than the previous (but even a really simple sieve is still probably going to be faster).
sqrt()
calls by computing the next number when that actually has to be computed:if (nur >= next_limit) { ilimit = sqrt(nur); next_limit = (ilimit+1)*(ilimit+1); }
(or something similar; been a while since I used C++). \$\endgroup\$sqrt(15)
= 3.87,sqrt(16)
= 4.00,sqrt(17)
= 4.12,sqrt(18)
= 4.24,sqrt(19)
= 4.36,sqrt(20)
= 4.47,sqrt(21)
= 4.58,sqrt(22)
= 4.69,sqrt(23)
= 4.80,sqrt(24)
= 4.90,sqrt(25)
= 5.00,sqrt(26)
= 5.10 ---> Instead of settingilimit = sqrt(nur)
for everynur
, you could set it only when needed (in this case, whennur
= 16 and whennur
= 25, but not for the rest of the numbers that I've listed). \$\endgroup\$