C线程应用程序比非线程运行得慢

前端之家收集整理的这篇文章主要介绍了C线程应用程序比非线程运行得慢前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我目前正在用C编写素数生成器.我先制作了一个单线程版本,后来又制作了一个多线程版本.

我发现如果我的程序生成的值小于100’000,则单线程版本比多线程版本更快.显然我做错了什么.

我的代码如下:

  1. #include <iostream>
  2. #include <fstream>
  3. #include <set>
  4. #include <string>
  5. #include <thread>
  6. #include <mutex>
  7. #include <shared_mutex>
  8.  
  9. using namespace std;
  10.  
  11. set<unsigned long long> primeContainer;
  12. shared_mutex m;
  13.  
  14. void checkPrime(const unsigned long long p)
  15. {
  16. if (p % 3 == 0)
  17. return;
  18.  
  19. bool isPrime = true;
  20. for (set<unsigned long long>::const_iterator it = primeContainer.cbegin(); it != primeContainer.cend(); ++it)
  21. {
  22. if (p % *it == 0)
  23. {
  24. isPrime = false;
  25. break;
  26. }
  27. if (*it * *it > p) // check only up to square root
  28. break;
  29. }
  30.  
  31. if (isPrime)
  32. primeContainer.insert(p);
  33. }
  34.  
  35. void checkPrimeLock(const unsigned long long p)
  36. {
  37. if (p % 3 == 0)
  38. return;
  39.  
  40. bool isPrime = true;
  41. try
  42. {
  43. shared_lock<shared_mutex> l(m);
  44. for (set<unsigned long long>::const_iterator it = primeContainer.cbegin(); it != primeContainer.cend(); ++it)
  45. {
  46. if (p % *it == 0)
  47. {
  48. isPrime = false;
  49. break;
  50. }
  51. if (*it * *it > p)
  52. break;
  53. }
  54. }
  55. catch (exception& e)
  56. {
  57. cout << e.what() << endl;
  58. system("pause");
  59. }
  60.  
  61. if (isPrime)
  62. {
  63. try
  64. {
  65. unique_lock<shared_mutex> l(m);
  66. primeContainer.insert(p);
  67. }
  68. catch (exception& e)
  69. {
  70. cout << e.what() << endl;
  71. system("pause");
  72. }
  73. }
  74. }
  75.  
  76. void runLoopThread(const unsigned long long& l)
  77. {
  78. for (unsigned long long i = 10; i < l; i += 10)
  79. {
  80. thread t1(checkPrimeLock,i + 1);
  81. thread t2(checkPrimeLock,i + 3);
  82. thread t3(checkPrimeLock,i + 7);
  83. thread t4(checkPrimeLock,i + 9);
  84. t1.join();
  85. t2.join();
  86. t3.join();
  87. t4.join();
  88. }
  89. }
  90.  
  91. void runLoop(const unsigned long long& l)
  92. {
  93. for (unsigned long long i = 10; i < l; i += 10)
  94. {
  95. checkPrime(i + 1);
  96. checkPrime(i + 3);
  97. checkPrime(i + 7);
  98. checkPrime(i + 9);
  99. }
  100. }
  101.  
  102. void printPrimes(const unsigned long long& l)
  103. {
  104. if (1U <= l)
  105. cout << "1 ";
  106. if (2U <= l)
  107. cout << "2 ";
  108. if (3U <= l)
  109. cout << "3 ";
  110. if (5U <= l)
  111. cout << "5 ";
  112.  
  113. for (auto it = primeContainer.cbegin(); it != primeContainer.cend(); ++it)
  114. {
  115. if (*it <= l)
  116. cout << *it << " ";
  117. }
  118. cout << endl;
  119. }
  120.  
  121. void writeToFile(const unsigned long long& l)
  122. {
  123. string name = "primes_" + to_string(l) + ".txt";
  124. ofstream f(name);
  125.  
  126. if (f.is_open())
  127. {
  128. if (1U <= l)
  129. f << "1 ";
  130. if (2U <= l)
  131. f << "2 ";
  132. if (3U <= l)
  133. f << "3 ";
  134. if (5U <= l)
  135. f << "5 ";
  136.  
  137. for (auto it = primeContainer.cbegin(); it != primeContainer.cend(); ++it)
  138. {
  139. if (*it <= l)
  140. f << *it << " ";
  141. }
  142. }
  143. else
  144. {
  145. cout << "Error opening file." << endl;
  146. system("pause");
  147. }
  148. }
  149.  
  150. int main()
  151. {
  152. unsigned int n = thread::hardware_concurrency();
  153. std::cout << n << " concurrent threads are supported." << endl;
  154.  
  155. unsigned long long limit;
  156. cout << "Please enter the limit of prime generation: ";
  157. cin >> limit;
  158.  
  159. primeContainer.insert(7);
  160.  
  161. if (10 < limit)
  162. {
  163. //runLoop(limit); //single-threaded
  164. runLoopThread(limit); //multi-threaded
  165. }
  166.  
  167. printPrimes(limit);
  168. //writeToFile(limit);
  169. system("pause");
  170. return 0;
  171. }

在main函数中,您将找到关于哪个函数是单线程和多线程的注释.

它们之间的主要区别在于锁的使用,容器迭代的共享以及插入的唯一性.如果重要,我的cpu有4个核心.

为什么单线程版本更快?

解决方法

对我来说,似乎你正在为每个单一的素数检查开始一个新线程.那是不好的恕我直言,因为线程启动/关闭加上同步增加了每个素数的计算.启动一个线程可能会很慢.

我建议在main for循环之外启动那4个线程,并在每个线程中处理范围的1/4.但这可能需要一些额外的同步,因为要检查素数,上面的代码显然需要首先具有可用的sqrt N的素数.

从我的角度来看,使用Sieve of Erastothenes算法可能更容易,这可能更容易并行化而没有任何锁定(但是仍然可能遇到称为“false sharing”的问题).

编辑

在这里,我使用Siera of Erastothenes快速创建了一个版本:

  1. void processSieve(const unsigned long long& l,const unsigned long long& start,const unsigned long long& end,const unsigned long long& step,vector<char> &is_prime)
  2. {
  3. for (unsigned long long i = start; i <= end; i += step)
  4. if (is_prime[i])
  5. for (unsigned long long j = i + i; j <= l; j += i)
  6. is_prime[j] = 0;
  7. }
  8.  
  9. void runSieve(const unsigned long long& l)
  10. {
  11. vector<char> is_prime(l + 1,1);
  12. unsigned long long end = sqrt(l);
  13. processSieve(l,2,end,1,is_prime);
  14. primeContainer.clear();
  15. for (unsigned long long i = 1; i <= l; ++i)
  16. if (is_prime[i])
  17. primeContainer.insert(i);
  18. }
  19.  
  20. void runSieveThreads(const unsigned long long& l)
  21. {
  22. vector<char> is_prime(l + 1,1);
  23. unsigned long long end = sqrt(l);
  24. vector<thread> threads;
  25. threads.reserve(cpuCount);
  26. for (unsigned long long i = 0; i < cpuCount; ++i)
  27. threads.emplace_back(processSieve,l,2 + i,cpuCount,ref(is_prime));
  28. for (unsigned long long i = 0; i < cpuCount; ++i)
  29. threads[i].join();
  30. primeContainer.clear();
  31. for (unsigned long long i = 1; i <= l; ++i)
  32. if (is_prime[i])
  33. primeContainer.insert(i);
  34. }

测量结果,最高可达1 000 000(MSVC 2013,Release):

  1. runLoop: 204.02 ms
  2. runLoopThread: 43947.4 ms
  3. runSieve: 30.003 ms
  4. runSieveThreads (8 cores): 24.0024 ms

高达10 0000 000:

  1. runLoop: 4387.44 ms
  2. // runLoopThread disabled,taking too long
  3. runSieve: 350.035 ms
  4. runSieveThreads (8 cores): 285.029 ms

时间包括向量的最终处理并将结果推送到素数集.

正如您所看到的,即使在单线程版本中,Sieve版本也比您的版本快得多(对于您的互斥锁版本,我必须将锁定更改为常规互斥锁,因为MSVC 2013没有shared_lock,因此结果可能更糟糕比你的).

但是你可以看到筛网的多线程版本运行速度仍然没有预期的那么快(8个核心,即8个线程,线性加速比单线程快8倍),虽然没有锁定(折衷一些数字可能如果它们没有被其他线程标记为“无素数”,则不必要地运行,但通常结果应该是稳定的,因为每次只设置为0,如果多个线程同时设置则无关紧要).加速不是线性的原因很可能是因为我之前提到的“false sharing”问题 – 写入零的线程使每个其他高速缓存行无效.

猜你在找的C&C++相关文章