c++ 找到40亿以下所有素数的最快方法

dpiehjr4  于 2024-01-09  发布在  其他
关注(0)|答案(7)|浏览(173)

我正在尝试打印2**32以下的所有素数。现在我正在使用bool vector构建一个筛子,然后打印出筛子后的素数。仅打印出10亿个素数就需要4分钟。有没有更快的方法来做到这一点??这是我的代码

  1. #include <iostream>
  2. #include <cstdlib>
  3. #include <vector>
  4. #include <math.h>
  5. using namespace std;
  6. int main(int argc, char **argv){
  7. long long limit = atoll(argv[1]);
  8. //cin >> limit;
  9. long long sqrtlimit = sqrt(limit);
  10. vector<bool> sieve(limit+1, false);
  11. for(long long n = 4; n <= limit; n += 2)
  12. sieve[n] = true;
  13. for(long long n=3; n <= sqrtlimit; n = n+2){
  14. if(!sieve[n]){
  15. for(long long m = n*n; m<=limit; m=m+(2*n))
  16. sieve[m] = true;
  17. }
  18. }
  19. long long last;
  20. for(long long i=limit; i >= 0; i--){
  21. if(sieve[i] == false){
  22. last = i;
  23. break;
  24. }
  25. }
  26. cout << last << endl;
  27. for(long long i=2;i<=limit;i++)
  28. {
  29. if(!sieve[i])
  30. if(i != last)
  31. cout<<i<<",";
  32. else
  33. cout<<i;
  34. }
  35. cout<<endl;

字符串

cuxqih21

cuxqih211#

我讨论了在我的blog上生成大量素数的问题,我发现第一个十亿个素数的总和是11138479445180240497。我描述了四种不同的方法:
1.蛮力,从2开始试除法。
1.使用2,3,5,7轮生成候选项,然后使用以2、7和61为底的强伪素数测试来测试素数性;这种方法只适用于2^32,这不足以让我对前10亿个素数求和,但对你来说已经足够了。

  1. Melissa奥尼尔提出的一种算法,它使用嵌入在优先级队列中的筛子,这是相当慢的。
    1.埃拉托色尼的一种分段筛,速度非常快,但需要空间来存储筛分素数和筛本身。
disho6za

disho6za2#

我已经在C++20中实现了erastosthenes的筛选,并使用多线程完成了非素数的穿孔:

  1. #include <iostream>
  2. #include <cstdlib>
  3. #include <charconv>
  4. #include <cstring>
  5. #include <vector>
  6. #include <algorithm>
  7. #include <cmath>
  8. #include <bit>
  9. #include <fstream>
  10. #include <string_view>
  11. #include <thread>
  12. #include <utility>
  13. #include <new>
  14. #include <span>
  15. #include <array>
  16. #include <cassert>
  17. #if defined(_MSC_VER)
  18. #include <intrin.h>
  19. #elif defined(__GNUC__) || defined(__clang__)
  20. #include <cpuid.h>
  21. #endif
  22. #if defined(_MSC_VER)
  23. #pragma warning(disable: 26495) // uninitialized member
  24. #endif
  25. using namespace std;
  26. #if defined(__cpp_lib_hardware_interference_size)
  27. constexpr ptrdiff_t CL_SIZE = hardware_destructive_interference_size;
  28. #else
  29. constexpr ptrdiff_t CL_SIZE = 64;
  30. #endif
  31. size_t getL2Size();
  32. bool smt();
  33. int main( int argc, char **argv )
  34. {
  35. if( argc < 2 )
  36. return EXIT_FAILURE;
  37. auto parse = []( char const *str, auto &value )
  38. {
  39. bool hex = str[0] == '0' && (str[1] == 'x' || str[1] == 'X');
  40. str += 2 * hex;
  41. auto [ptr, ec] = from_chars( str, str + strlen( str ), value, !hex ? 10 : 16 );
  42. return !(bool)ec && !*ptr;
  43. };
  44. size_t end;
  45. if( !parse( argv[1], end ) )
  46. return EXIT_FAILURE;
  47. if( end < 2 )
  48. return EXIT_FAILURE;
  49. if( (ptrdiff_t)end++ < 0 )
  50. throw bad_alloc();
  51. unsigned
  52. hc = jthread::hardware_concurrency(),
  53. nThreads;
  54. if( argc < 4 || !parse( argv[3], nThreads ) )
  55. nThreads = hc;
  56. if( !nThreads )
  57. return EXIT_FAILURE;
  58. using word_t = size_t;
  59. constexpr size_t
  60. BITS_PER_CL = CL_SIZE * 8,
  61. BITS = sizeof(word_t) * 8;
  62. size_t nBits = end / 2;
  63. union alignas(CL_SIZE) ndi_words_cl { word_t words[CL_SIZE / sizeof(word_t)]; ndi_words_cl() {} };
  64. vector<ndi_words_cl> sieveCachelines( (nBits + BITS_PER_CL - 1) / BITS_PER_CL );
  65. span<word_t> sieve( &sieveCachelines[0].words[0], (nBits + BITS - 1) / BITS );
  66. assert(to_address( sieve.end() ) <= &to_address( sieveCachelines.end() )->words[0]);
  67. fill( sieve.begin(), sieve.end(), -1 );
  68. auto punch = [&]( size_t start, size_t end, size_t prime, auto )
  69. {
  70. size_t
  71. bit = start / 2,
  72. bitEnd = end / 2;
  73. if( bit >= bitEnd )
  74. return;
  75. if( prime >= BITS ) [[likely]]
  76. do [[likely]]
  77. sieve[bit / BITS] &= rotl( (word_t)-2, bit % BITS );
  78. while( (bit += prime) < bitEnd );
  79. else
  80. {
  81. auto pSieve = sieve.begin() + bit / BITS;
  82. do [[likely]]
  83. {
  84. word_t
  85. word = *pSieve,
  86. mask = rotl( (word_t)-2, bit % BITS ),
  87. oldMask;
  88. do
  89. word &= mask,
  90. oldMask = mask,
  91. mask = rotl( mask, prime % BITS ),
  92. bit += prime;
  93. while( mask < oldMask );
  94. *pSieve++ = word;
  95. } while( bit < bitEnd );
  96. }
  97. };
  98. size_t sqrt = (ptrdiff_t)ceil( ::sqrt( (ptrdiff_t)end ) );
  99. for( size_t bSqrt = sqrt / 2, bit = 1; bit < bSqrt; ++bit ) [[likely]]
  100. {
  101. auto pSieve = sieve.begin() + bit / BITS;
  102. do [[likely]]
  103. {
  104. if( word_t w = *pSieve >> bit % BITS; w ) [[likely]]
  105. {
  106. bit += countr_zero( w );
  107. break;
  108. }
  109. ++pSieve;
  110. bit = bit + BITS & -(ptrdiff_t)BITS;
  111. } while( bit < bSqrt );
  112. if( bit >= bSqrt ) [[unlikely]]
  113. break;
  114. size_t prime = 2 * bit + 1;
  115. punch( prime * prime, sqrt, prime, false_type() );
  116. }
  117. auto scan = [&]( size_t start, size_t end, auto fn )
  118. {
  119. size_t
  120. bit = start / 2,
  121. bitEnd = end / 2;
  122. if( bit >= bitEnd )
  123. return;
  124. auto pSieve = sieve.begin() + bit / BITS;
  125. word_t word = *pSieve++ >> bit % BITS;
  126. for( ; ; )
  127. {
  128. size_t nextWordBit = bit + BITS & -(ptrdiff_t)BITS;
  129. for( unsigned gap; word; word >>= gap, word >>= 1 )
  130. {
  131. gap = countr_zero( word );
  132. bit += gap;
  133. if( bit >= bitEnd ) [[unlikely]]
  134. return;
  135. fn( bit++ * 2 + 1, true_type() );
  136. }
  137. bit = nextWordBit;
  138. if( bit >= bitEnd ) [[unlikely]]
  139. return;
  140. word = *pSieve++;
  141. }
  142. };
  143. static auto dbl = []( ptrdiff_t x ) { return (double)x; };
  144. using range_t = pair<size_t, size_t>;
  145. vector<range_t> ranges;
  146. ranges.reserve( nThreads );
  147. vector<jthread> threads;
  148. threads.reserve( nThreads );
  149. auto initiate = [&]( size_t start, auto fn )
  150. {
  151. ranges.resize( 0 );
  152. double threadRange = dbl( end - start ) / nThreads;
  153. for( size_t t = nThreads, trEnd = end, trStart; t--; trEnd = trStart ) [[likely]]
  154. {
  155. trStart = start + (ptrdiff_t)((ptrdiff_t)t * threadRange + 0.5);
  156. size_t trClStart = trStart & -(2 * CL_SIZE * 8);
  157. trStart = trClStart >= start ? trClStart : start;
  158. if( trStart < trEnd )
  159. ranges.emplace_back( trStart, trEnd );
  160. }
  161. for( range_t const &range : ranges )
  162. if( &range != &ranges.back() )
  163. threads.emplace_back( fn, range.first, range.second, true_type() );
  164. else
  165. fn( range.first, range.second, false_type() );
  166. threads.resize( 0 );
  167. };
  168. double maxCacheRange = dbl( getL2Size() * 8 * 2 ) / 3 * (smt() && nThreads > hc / 2 ? 1 : 2);
  169. initiate( sqrt,
  170. [&]( size_t rStart, size_t rEnd, auto )
  171. {
  172. double thisThreadRange = dbl( rEnd - rStart );
  173. ptrdiff_t nCachePartitions = (ptrdiff_t)ceil( thisThreadRange / maxCacheRange );
  174. double cacheRange = thisThreadRange / dbl( nCachePartitions );
  175. for( size_t p = nCachePartitions, cacheEnd = rEnd, cacheStart; p--; cacheEnd = cacheStart ) [[likely]]
  176. {
  177. cacheStart = rStart + (ptrdiff_t)((double)(ptrdiff_t)p * cacheRange + 0.5);
  178. cacheStart &= -(2 * CL_SIZE * 8);
  179. cacheStart = cacheStart >= sqrt ? cacheStart : sqrt;
  180. scan( 3, sqrt,
  181. [&]( size_t prime, auto )
  182. {
  183. size_t start = (cacheStart + prime - 1) / prime * prime;
  184. start = start % 2 ? start : start + prime;
  185. punch( start, cacheEnd, prime, true_type() );
  186. } );
  187. }
  188. } );
  189. if( bool count = false; argc >= 3 && (!*argv[2] || (count = strcmp( argv[2], "*" ) == 0)) )
  190. {
  191. if( !count )
  192. return EXIT_SUCCESS;
  193. atomic<size_t> aNPrimes( 1 );
  194. initiate( 3,
  195. [&]( size_t rStart, size_t rEnd, auto )
  196. {
  197. size_t nPrimes = 0;
  198. scan( rStart, rEnd, [&]( ... ) { ++nPrimes; } );
  199. aNPrimes.fetch_add( nPrimes, memory_order::relaxed );
  200. } );
  201. cout << aNPrimes.load( memory_order::relaxed ) << " primes" << endl;
  202. return EXIT_SUCCESS;
  203. }
  204. ofstream ofs;
  205. ofs.exceptions( ofstream::failbit | ofstream::badbit );
  206. ofs.open( argc >= 3 ? argv[2] : "primes.txt", ofstream::binary | ofstream::trunc );
  207. constexpr size_t
  208. BUF_SIZE = 0x100000,
  209. AHEAD = 32;
  210. union ndi_char { char c; ndi_char() {} };
  211. vector<ndi_char> rawPrintBuf( BUF_SIZE + AHEAD - 1 );
  212. span printBuf( &rawPrintBuf.front().c, &rawPrintBuf.back().c + 1 );
  213. auto
  214. bufBegin = printBuf.begin(),
  215. bufEnd = bufBegin,
  216. bufFlush = bufBegin + BUF_SIZE;
  217. auto print = [&]() { ofs << string_view( bufBegin, bufEnd ); };
  218. auto printPrime = [&]( size_t prime, auto )
  219. {
  220. auto [ptr, ec] = to_chars( &*bufEnd, &bufEnd[AHEAD - 1], prime );
  221. if( (bool)ec ) [[unlikely]]
  222. throw system_error( (int)ec, generic_category(), "converson failed" );
  223. bufEnd = ptr - &*bufBegin + bufBegin; // pointer to iterator - NOP
  224. *bufEnd++ = '\n';
  225. if( bufEnd >= bufFlush ) [[unlikely]]
  226. print(), bufEnd = bufBegin;
  227. };
  228. printPrime( 2, false_type() );
  229. scan( 3, end, printPrime );
  230. print();
  231. }
  232. array<unsigned, 4> cpuId( unsigned code )
  233. {
  234. array<unsigned, 4> regs;
  235. #if defined(_MSC_VER)
  236. __cpuid( (int *)&regs[0], code );
  237. #elif defined(__GNUC__) || defined(__clang__)
  238. __cpuid(code, regs[0], regs[1], regs[2], regs[3]);
  239. #endif
  240. return regs;
  241. }
  242. bool smt()
  243. {
  244. if( cpuId( 0 )[0] < 1 )
  245. return false;
  246. return cpuId( 1 )[3] >> 28 & 1;
  247. }
  248. size_t getL2Size()
  249. {
  250. if( cpuId( 0x80000000u )[0] < 0x80000006u )
  251. return 512ull * 1024;
  252. return (cpuId( 0x80000006u )[2] >> 16) * 1024;
  253. }

字符串
几乎所有的CPU时间都花在了多线程代码中,用于对非质数进行打孔。在打孔线程中,位范围被分区以适应L2缓存。如果线程数量超过SMT系统上逻辑核数量的一半,则线程子分区的大小为一半。
我只是增加了只存储奇素数的功能,即不存储2的倍数,从而提高了大约87%的性能。
在我的Ryzen 9 7950 X Zen 4 16核CPU上,产生所有素数高达2^32,这几乎是210 E6素数,在所有核心上大约需要0.15秒,抑制文件输出;在一个核心上,算法大约需要1.73秒。程序的第三个参数是线程数。在我的16个核心Zen 4上有16个线程,在一个线程上,我得到了70%的扩展。占用更多的SMT兄弟线程几乎不会带来进一步的加速,因为代码依赖于L2缓存的吞吐量。
文件输出是通过to_chars和我自己的缓冲来完成的,以加快I/O。在我的计算机(64 GB,PCIe 4.0 SSD)上,从上述范围生成2.2GB的文件大约需要额外的两秒钟。

展开查看全部
deyfvvtc

deyfvvtc3#

这可能会加快一点:

  1. #include <algorithm>
  2. #include <iostream>
  3. #include <iterator>
  4. #include <vector>
  5. int main()
  6. {
  7. std::vector<unsigned long long> numbers;
  8. unsigned long long maximum = 4294967296;
  9. for (unsigned long long i = 2; i <= maximum; ++i)
  10. {
  11. if (numbers.empty())
  12. {
  13. numbers.push_back(i);
  14. continue;
  15. }
  16. if (std::none_of(numbers.begin(), numbers.end(), [&](unsigned long long p)
  17. {
  18. return i % p == 0;
  19. }))
  20. {
  21. numbers.push_back(i);
  22. }
  23. }
  24. std::cout << "Primes: " << std::endl;
  25. std::copy(numbers.begin(), numbers.end(), std::ostream_iterator<int>(std::cout, " "));
  26. return 0;
  27. }

字符串
它有点像埃拉托塞尼筛的逆(不是从极限以下的每个数字开始并消除倍数,而是从2开始并忽略极限以下的倍数)。

展开查看全部
gtlvzcf8

gtlvzcf84#

最快的方法可能是使用预先生成的列表。
http://www.bigprimes.net/有前14亿个质数可供下载,其中应该包括300亿以下的所有质数。
我想加载二进制文件可能需要太长的时间,当它是几个千兆字节的大小。

fv2wmkja

fv2wmkja5#

你有没有衡量过什么是最耗时的?是筛子本身,还是输出的写作?
一个快速的方法来加速筛子是停止担心所有的偶数。只有一个偶数是素数,你可以硬编码它。这将把你的数组大小减少一半,这将极大地帮助你克服物理内存的限制。

  1. vector<bool> sieve((limit+1)/2, false);
  2. ...
  3. for(long long m = n*n/2; m<=limit/2; m=m+n)
  4. sieve[m] = true;

字符串
至于输出本身,cout是出了名的低效。自己调用itoa或其他等效函数,然后使用cout.write输出可能会更高效。您甚至可以采用传统方法,将fwritestdout一起使用。

w8rqjzmb

w8rqjzmb6#

我用C写了一个快速的方法,在我的Ryzen 9 3900x上使用一个线程在三分钟内输出高达40亿个素数。如果你把它输出到一个文件,它最终是2.298GB,我认为它使用了大约20GB的内存来完成。

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #define ARRSIZE 4000000000
  4. #define MAXCALC ARRSIZE/2
  5. int main() {
  6. long int z;
  7. long int *arr = (long int*) malloc((ARRSIZE) * sizeof(long int));
  8. for (long int x=3;x <= MAXCALC; x++) {
  9. if (x % 10 == 3 || x % 10 == 7 || x % 10 == 9) {
  10. for (long int y=3; y < MAXCALC; y++){
  11. z = x * y;
  12. if (z < ARRSIZE)
  13. arr[z] = 1;
  14. else
  15. break;
  16. }
  17. }
  18. }
  19. printf("2 3 5 ");
  20. for (long int x=7; x < ARRSIZE; x++) {
  21. if (x % 2 != 0 && x % 10 != 5)
  22. if (arr[x] != 1)
  23. printf("%ld ", x);
  24. }
  25. printf("\n");
  26. free(arr);
  27. return EXIT_SUCCESS;
  28. }

字符串

展开查看全部
uplii1fm

uplii1fm7#

我写了代码i python输出所有素数小于10亿在8.7秒.但我不确定如果你的第一个40亿素数或艾伦素数小于.无论如何,这是我的解决方案:

  1. import numpy as np
  2. from math import isqrt
  3. def all_primes_less_than(n):
  4. is_prime = np.full(n,True)
  5. is_prime[:2] = False
  6. is_prime[4::2] = False
  7. for p in range(3,isqrt(n),2):
  8. if is_prime[p]: is_prime[p*p::p] = False
  9. return is_prime.nonzero()[0]

字符串

相关问题