【问题标题】:Threading as "slow" as non threaded线程与非线程一样“慢”
【发布时间】:2014-06-14 15:30:48
【问题描述】:

今天我在 python 中使用线程计算素数时遇到了问题。它几乎和没有线程一样慢(参见Question)。

现在我创建了相同的代码,认为使用 pthread 在 C 中不会存在 python 问题。

#include <stdio.h>
#include <time.h>
#include <pthread.h>

int isPrime(int number) {
    int i;
    for (i=2; i<number; i++) {
        if (number % i == 0 && i != number) return 0;
    }
    return 1;
}

void calcPrimeNumbersFromNtoM(int n, int m){
    for (int i = n; i <= m; i++) {
        if (isPrime(i)) {
            //printf("%i\n",i);
        }
    }

}

void *calcFirstHalf(){
    calcPrimeNumbersFromNtoM(1,5000);
    return NULL;
}

void *calcSecondHalf(){
    calcPrimeNumbersFromNtoM(5001,10000);
    return NULL;
}

void calcThreadedPrimenumbers(){
    pthread_t t1, t2;
    pthread_create(&t1, NULL, calcFirstHalf, NULL);
    pthread_create(&t2, NULL, calcSecondHalf, NULL);

    //wait for the threads to finish
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
}

int main(int argc, const char * argv[])
{

    clock_t startNT, endNT,startT, endT;
    double cpu_time_usedNT,cpu_time_usedT;
    startNT = clock();
    calcPrimeNumbersFromNtoM(1, 10000);
    endNT = clock();
    cpu_time_usedNT = ((double) (endNT - startNT)) / CLOCKS_PER_SEC;

    startT = clock();
    calcThreadedPrimenumbers();
    endT = clock();
    cpu_time_usedT = ((double) (endT - startT)) / CLOCKS_PER_SEC;


    printf("--------Results-----------\n");
    printf("Non threaded took: %f secs\n",cpu_time_usedNT);
    printf("Threaded took: %f secs\n",cpu_time_usedT);


    return 0;
}

结果是线程再次和非线程一样慢:

--------Results-----------
Non threaded took: 0.020624 secs
Threaded took: 0.027257 secs

这让我很困惑。我的代码有问题吗?确实不需要线程比不使用线程更快吗?如果是,对此有何解释?

这是因为操作系统需要调度相同的任务只分为两部分导致相同的时间量吗?

也许这很重要:我使用的是 2.6Ghz Core i5 MacBook 和 OSX 10.9

【问题讨论】:

  • 您的问题可能不足以显示出显着差异。尝试计算更多的素数。
  • 从 1 到 1000000 的计算结果为:--------Results----------- Non threaded took: 137.332219 secs Threaded took: 140.151069 secs 所以这不是问题
  • 线程不能弥补糟糕的算法。一个不错的公共领域版本在这里:nothings.org/stb/stb_h.html
  • @technosaurus 实际上不需要增加超过 sqrt(number)
  • @technosaurus 你也可以只做for (i = 2; i*i &lt;= number; ++i) 顺便说一句,我不相信 sqrt 实际上会比这慢。我无法想象在任何给定的现代 CPU 上它需要超过几个周期,所以除非数字非常非常小(大约几十个),否则循环内昂贵的整数除法运算将主导成本

标签: c multithreading algorithm pthreads theory


【解决方案1】:

您的素数计算器是O(n^2)。注意5000^2 = 25000000,而(10,000^2)/2 = 50000000

这使第二个线程成为算法的瓶颈,并为第一个线程等待大量时间。
换句话说,与第二个线程相比,第一个线程做的工作很少,因此第一个线程在大部分工作中都处于空闲状态。

【讨论】:

  • 这是一个很好的观点,但对于非线程替代方案来说应该是同样的问题。因此,如果时间可能会增加,那么它应该在同时处理的下限
  • 准确来说,运行时间是O(n^2-nlogn)(还是很接近O(n^2)的。这是因为每次迭代的平均运行时间是1/2+2/3+...+(n-1)/n = 1-1/2+1-1/3+1-1/4+...+1-1/n = n- (1/2+1/3+..+1/n) = n-Hn,这里是@ 987654326@是谐波数,在O(logn)中。
  • @amit: 因为对于任何大小合适的nnlogn 都小于n^2,它从大O 符号中删除,所以O(n^2) 仍然正确。
  • @MooingDuck 是的,你当然是对的。不过,我喜欢这种对时间复杂度的分析,所以我将把它留在这里,并附上您的额外评论以澄清事情。
  • 你还没有看到大约 33÷ 的加速吗?我不相信这个答案
【解决方案2】:

clock() 返回 CPU 时间。如果您在 1 秒内同时使用 2 个 CPU,clock() 将增加 2。您将需要测量挂墙时间(实际经过的真实世界时间)。此外,正如其他回答者所说,您的线程负载不平衡,因此一个线程将比另一个线程运行更长的时间,尽管总挂墙时间仍应仅略高于单线程情况的 75%。 (对于足够长的工作量)

【讨论】:

【解决方案3】:

专门解决您的(一般)问题

Is it true that threads are not necessary faster than using no thread? 
If yes what is the explanation for this?

使用多个线程来完成一项任务的效率主要受 CPU 内核数量的限制(包括可用的超线程)。例如,如果您的系统有两个内核,那么两个线程可以同时运行。在您的情况下(i5),您可能有一个 2 核或 4 核处理器。使用超线程,您的系统可以同时运行 4 或 8 个线程。

如果您的应用程序似乎只有两个线程(三个,包括父 'main()' 线程),应该会有显着的改进。但是,请记住,您的线程并不是系统上唯一活跃的线程。很可能,你的机器上已经有很多执行线程了;都在争夺 CPU 资源。

当 CPU 资源变得可用时,线程调度程序从等待 CPU 的线程队列中拉出另一个线程。您的线程之一不太可能始终位于运行队列的顶部。因此,他们将继续在运行队列中等待轮到他们。

每次您的代码调用“阻塞”函数时,线程的上下文都会存储在内存中,并且线程会返回到运行队列。即使是像“printf()”这样可能阻塞的无辜函数,也会导致线程返回到运行队列。

通常,对等线程竞争 CPU 资源以外的资源;例如共享内存、共享文件访问等。通常这些资源受到信号量、锁等的保护。这也会影响多线程与单线程的效率。

这些以及许多其他因素(包括 Mark Ransom 提到的因素)可能会对计时结果产生影响。

【讨论】:

  • 这是一个关于线程的通用答案,与具体问题无关。
【解决方案4】:

我想你会发现你的 isPrime 函数是 O(n),所以大 n 的后半部分将主导整个时序。您应该为无线程测试分别计时。

【讨论】:

  • @amit 循环的结构方式,素数将一直运行到n
  • 我删除了我的评论,我说的是平均情况,我认为平均运行时间是 O(1/2+1/3+...+1/n),但它是一个错误。应该是O(1/2+2/3+...+(n-1)/n)。这是O(n-log(n)),所以我们俩一开始都非常正确。
【解决方案5】:

您可以通过对工作进行不同的分区来平衡您的线程。请注意,2 是唯一的偶数素数,因此使用这样的代码给每个线程一半的奇数

void *calcFirstHalf()
{
    int i;
    for ( i = 1; i < 1000000; i += 4 )  // 1, 5, 9, 13...
       if ( isPrime( i ) )
       {
       }
    return NULL;
}

void *calcSecondHalf()
{
    int i;
    for ( i = 3; i < 1000000; i += 4 )  // 3, 7, 11, 15...
       if ( isPrime( i ) )
       {
       }
    return NULL;
}

旁注:您还可以通过仅检查高达所提议素数平方根的因数来提高isPrime 函数的效率,因为每个非素数必须至少有一个小于或等于的因数平方根。


在 MAC 上进行性能测量

MAC 上的高精度定时器通过mach_absolute_time 函数访问,如下代码所示。

#include <mach/mach.h>
#include <mach/mach_time.h>

void testTimer( void )
{
    uint64_t start, end;
    mach_timebase_info_data_t info;

    mach_timebase_info( &info );
    printf( "numer=%u denom=%u\n", info.numer, info.denom );

    start = mach_absolute_time();
    sleep( 1 );
    end = mach_absolute_time();

    printf( "%llu\n", end - start );
}

注意定时器的精度不是固定值,必须根据mach_timebase_info函数返回的信息来计算。计算是

timer_rate = 1Ghz * numer / denom

您可以通过拨打sleep 一秒钟来确认计时器速率,以查看您每秒大约获得多少滴答声。

【讨论】:

  • 检查了 - 类似的结果
  • 刚刚检查了clock 的手册页。它说clock 测量进程使用的 CPU 时间。这对您的实验来说是有问题的,因为多线程不会减少整体 CPU 时间。如果有多个 CPU 可用,多线程将减少挂钟时间。
  • @AzzUrr1 我添加了一些关于如何在 MAC 上进行精确时间测量的信息。
猜你喜欢
  • 1970-01-01
  • 2015-08-22
  • 2015-08-21
  • 1970-01-01
  • 2011-08-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多