指定范围之间的C ++多线程主计数器

时间:2015-10-21 05:04:59

标签: c++ multithreading

#include <math.h>
#include <sstream>
#include <iostream>
#include <mutex>
#include <stdlib.h>
#include <chrono>
#include <thread>

bool isPrime(int number) {
    int i;

    for (i = 2; i < number; i++) {
        if (number % i == 0) {
            return false;
        }
    }

    return true;
}

std::mutex myMutex;

int pCnt = 0;

int icounter = 0;

int limit = 0;


int getNext() {
    std::lock_guard<std::mutex> guard(myMutex);
    icounter++;
    return icounter;
}

void primeCnt() {
    std::lock_guard<std::mutex> guard(myMutex);
    pCnt++;
}

void primes() {
    while (getNext() <= limit)
        if (isPrime(icounter))
            primeCnt();
}

int main(int argc, char *argv[]) {
    std::stringstream ss(argv[2]);
    int tCount;
    ss >> tCount;

    std::stringstream ss1(argv[4]);
    int lim;
    ss1 >> lim;

    limit = lim;

    auto t1 = std::chrono::high_resolution_clock::now();

    std::thread *arr;
    arr = new std::thread[tCount];

    for (int i = 0; i < tCount; i++)
        arr[i] = std::thread(primes);

    for (int i = 0; i < tCount; i++)
        arr[i].join();

    auto t2 = std::chrono::high_resolution_clock::now();

    std::cout << "Primes: " << pCnt << std::endl;
    std::cout << "Program took: " << std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count() <<
    " milliseconds" << std::endl;
    return 0;
}

您好,我试图找到用户指定范围之间的素数量,即1-1000000与用户指定的线程数量来加快进程,但是,似乎花费相同的时间量与一个线程相比的任何数量的线程。我不确定它应该是那样的,还是我的代码中的错误。提前谢谢你!

4 个答案:

答案 0 :(得分:2)

您没有看到性能提升,因为在isPrime()中花费的时间远远小于线程在使用互斥锁时所花费的时间。

一种可能的解决方案是使用原子操作,如Docker Netshare所述。另一种方法是将您的任务划分为较小的任务,并将它们分配到您的线程池中。

例如,如果您有n个线程,那么每个线程都应该测试从i*(limit/n)(i+1)*(limit/n)的数字,其中i是线程编号。这样你根本不需要进行任何同步,你的程序将(理论上)线性扩展。

答案 1 :(得分:2)

当线程可以自己完成大量工作时,多线程算法效果最佳。

想象一下在现实生活中这样做:你有一组20个人会为你工作,你希望他们测试每个数字是否达到1000。你会怎么做?

您是否会一次向每个人提供一个号码,然后让他们回到您的面前告诉您是否有其他号码?

当然不是;你会给每个人一堆数字,然后让他们回来告诉你有多少人是素数并接收另一堆数字。

甚至你甚至可以将整组数字分成20组,并告诉每个人在一个小组上工作。 (但是当你等待那个人完成时,你冒着一个人变慢而让其他人都闲着的风险......虽然有所谓的&#34;工作偷窃&#34;算法,但那&# 39; s复杂)

这同样适用于此;你希望每个线程自己做很多工作并保持自己的计数,并且只需要偶尔检查一次集中信息。

答案 2 :(得分:0)

更好的解决方案是使用Sieve of Atkin来查找素数(即使更容易理解的Sieve of Eratosthenes更好),您的基本算法开始时效果很差。对于您的时间间隔中的每个数字n,我们会n进行检查,以确定它是否为素数并执行此limit次。这意味着您正在进行limit*limit/2次检查 - 这就是我们所说的O(n^2)复杂性。 Atkins OTOH的Sieve只需要进行O(n)操作即可找到所有素数。如果n很大,则通过更快地执行步骤很难打败步数较少的算法。试图通过向其投入更多资源来修复一个糟糕的算法是一个糟糕的策略。

您的实施的另一个问题是它具有竞争条件,因此一开始就被打破。除非您首先确保它正常工作,否则它通常很少用于优化。问题出在primes函数:

void primes() {
    while (getNext() <= limit)
        if( isPrime(icounter) )
            primeCnt();
}

getNext()isPrime之间,另一个线程可能增加了icounter并导致程序跳过候选者。这导致程序每次都给出不同的结果。此外,icounterpCnt都未声明为volatile,因此实际上无法保证该值作为互斥锁的一部分进入全局存储位置。

由于问题是CPU密集型的,几乎所有的时间都花在执行CPU指令上,除非你有多个CPU(或核心)操作系统正在调度相同进程的线程,否则多线程将无济于事。这意味着线程数量的限制(可以低至1 - 我fx只看到两个线程的改进,超过没有),你可以期望提高性能。如果你有比核心更多的线程会发生什么,操作系统只让一个线程在核心上运行一段时间然后切换线程让下一个线程执行一段时间。

在不同核心上调度线程时可能出现的问题是,每个核心可能具有单独的缓存(比共享缓存更快)。实际上,如果两个线程要访问同一个内存,则必须刷新分离的缓存作为所涉及数据同步的一部分 - 这可能非常耗时。

那就是你必须努力保持不同线程正在分离的数据,并尽量减少常见变量数据的频繁使用。在您的示例中,这意味着您应该尽可能地避免使用全局数据。例如,只有在计数完成时才需要访问计数器(将线程贡献添加到计数中)。你也可以通过不为每个候选人阅读它来最小化icounter的使用,但是一次性获得一堆候选人。类似的东西:

void primes() {
     int next;
     int count=0;

     while( (next = getNext(1000)) <= limit ) {
          for( int j = next; j < next+1000 && j <= limit ; j++ ) {
              if( isPrime(j) )
                  count++;
          }
     }

     primeCnt(count);
 }

其中getNext相同,但会保留一些候选人(按提供的数量增加icounter),primeCntcount添加到pCnt }。

因此,您最终可能会遇到核心运行一个线程,然后在一段时间后切换到另一个线程等等。结果是您必须运行问题的所有代码 plus 代码才能在线程之间切换。补充一点,你可能会有更多的缓存命中,然后这可能会更慢。

答案 3 :(得分:-1)

也许代替互斥锁尝试使用原子整数作为计数器。它可能加速一点,不确定多少。

SELECT name, 
SUBSTRING_INDEX(GROUP_CONCAT(price ORDER BY price DESC),',',1) AS min_price,
SUBSTRING_INDEX(GROUP_CONCAT(notes ORDER BY price DESC),',',1) AS note_value
FROM test
GROUP BY name;

在基准测试中,大多数时候处理器需要预热才能获得最佳性能,因此花费一次时间并不总是很好地代表了实际性能。尝试多次运行代码并获得平均值。在进行计算之前,你还可以尝试做一些繁重的工作(一个长循环计算一些计数器的功率?)

获得准确的基准测试结果也是我感兴趣的话题,因为我还不知道如何去做。