【问题标题】:Improving a simple function using threading使用线程改进一个简单的函数
【发布时间】:2015-05-19 10:09:43
【问题描述】:

我用以下代码编写了一个简单的函数,用于计算一维数组中的最小值:

uint32_t get_minimum(const uint32_t* matrix) {
    int min = 0;
    min = matrix[0];


    for (ssize_t i = 0; i < g_elements; i++){
      if (min > matrix[i]){
        min = matrix[i];
      }
    }

    return min;
}    

但是,我想提高此功能的性能,并建议使用线程,因此我将其修改为以下内容:

struct minargument{
  const uint32_t* matrix;
  ssize_t tid;
  long long results;
};

static void *minworker(void *arg){
  struct minargument *argument = (struct minargument *)arg;

  const ssize_t start = argument -> tid * CHUNK;
  const ssize_t end = argument -> tid == THREADS - 1 ? g_elements :     (argument -> tid + 1) * CHUNK;

  long long result = argument -> matrix[0];

  for(ssize_t i = start; i < end; i++){
    for(ssize_t x = 0; x < g_elements; x++){
      if(result > argument->matrix[i]){
        result = argument->matrix[i];
      }    
    }
  }

  argument -> results = result;

  return NULL;


}


uint32_t get_minimum(const uint32_t* matrix) {

    struct minargument *args = malloc(sizeof(struct minargument) * THREADS);

    long long min = 0;


    for(ssize_t i = 0; i < THREADS; i++){
      args[i] = (struct minargument){
        .matrix = matrix,
        .tid = i,
        .results = min,
      };
    }

    pthread_t thread_ids[THREADS];

    for(ssize_t i =0; i < THREADS; i++){
      if(pthread_create(thread_ids + i, NULL, minworker, args + i) != 0){
        perror("pthread_create failed");
        return 1;
      }
    }

    for (ssize_t i = 0; i < THREADS; i++){
      if(pthread_join(thread_ids[i], NULL) != 0){
        perror("pthread_join failed");
        return 1;
      }
    }
    for(ssize_t i =0; i < THREADS; i++){
      min = args[i].results;
    }


    free(args);


    return min;
}

但是这似乎比第一个函数慢。 我是否正确使用线程使第一个函数运行得更快?如果是这样,我该如何修改第二个函数,使其比第一个函数更快?

【问题讨论】:

  • 这个问题几乎肯定是内存受限的。如果工作集不适合 cpu 的私有缓存,则添加更多线程只会提高性能,然后直到共享内存带宽耗尽。您的测试用例可能需要达到 MB 级别才能从多线程中受益。
  • matrix 中有多少个元素?这取决于数据大小、THREADS 的值、您拥有的内核数量等。请注意,您最后也没有正确计算 minfor(ssize_t i =0; i &lt; THREADS; i++){ min = args[i].results; }
  • @EOF 我已经针对大量元素进行了测试,所以我认为它仍然会受益。对 BlueMoon:我正在测试 20~20,000。你们能解释一下最后一部分是怎么错的吗
  • 我相信线程优先级因素在优化中也有一定的作用。
  • 20,000 long long ints 在大多数平台上是~160 kB。典型的 x86 具有 ~256 kB 的私有 L2 缓存。除非第二个线程被安排在同一物理内核上的另一个超线程上,否则您的测试用例至少要小一个数量级。

标签: c multithreading pthreads


【解决方案1】:

由于创建线程、调度线程和等待所有线程完成的开销,拥有多于可用于运行它们的内核的线程总是比单个线程慢。

您提供的示例不太可能受益于编译器将为您做的任何优化,因为它是一个简短而简单的操作。如果您在多核系统上做一些更复杂的事情,例如将两个巨大的矩阵相乘,以对高速实时数据运行相关算法,那么多线程可能是解决方案。

对您的问题的更抽象的答案是另一个问题:您真的需要对其进行优化吗?除非您知道存在 性能问题这一事实,否则您的 时间最好花在为您的程序添加更多功能上,而不是解决实际上并不存在的问题。

编辑 - 比较

我刚刚在以 40 MHz 指令时钟运行的 16 位 ARM 微控制器上运行(代表版本)OP 代码。使用 GCC 编译的代码,未经优化。

找到 20,000 个 32 位整数的最小值需要 25 毫秒多一点。

对于 40 kByte 的页面大小(容纳 20,000 个 4 字节值数组的一半),线程在时钟频率为 2.67 GHz 的双 Intel 5150 处理器的不同内核上运行,仅执行上下文切换就需要近 50 毫秒和分页操作!

一个简单的单线程微控制器实现所需的实时时间是多线程桌面实现的一半。

【讨论】:

  • 如果工作集足够大,完全可以通过多线程来提高线性搜索的性能。
  • @EOF 是的,前提是您有多个内核来运行线程。否则,线程仅用于将某些进程安排为后台任务,而其他时间更关键的线程将运行直到结果可用和需要。如果样本集足够大并且您有可用的处理资源,那么它可以提高性能。否则,它会产生相反的效果,正如 OP 所观察到的那样。
  • 您可能需要重新考虑优化,尤其是如果这是您工作的一部分。这段代码会在数亿个元素的数组上运行吗?它会在一次执行中在较小的数组上运行数千次吗?这是一个实时应用程序,如果您不“修复”它,用户将盯着屏幕 20 分钟?它会批量运行吗?它是某个更大的应用程序的一部分,有人会启动然后去吃午饭吗?考虑首先优化其他东西,比如 I/O。将运行时间缩短 10 毫秒很有趣,但并不总是需要。
  • @jimmcnamara 这是对我的评论吗?我从事嵌入式软件工作,从 ISR 中删除一两条指令可以区分好产品和无用产品。对于复杂的信号处理,将算法委托给 DSP 协处理器可能是必不可少的。在 OP 的情况下,我倾向于在填充数组时跟踪最小值。你的最后一句话是大赚一笔!
猜你喜欢
  • 1970-01-01
  • 2018-08-18
  • 2014-07-30
  • 1970-01-01
  • 2012-04-12
  • 2021-10-24
  • 1970-01-01
  • 1970-01-01
  • 2012-03-23
相关资源
最近更新 更多