【问题标题】:python c extension: multithreading and random numberspython c扩展:多线程和随机数
【发布时间】:2012-01-09 01:42:41
【问题描述】:

我已经用 C 语言(在 python 扩展中)实现了一个工作队列模式,但我对性能感到失望。

我有一个包含粒子列表(“元素”)的模拟,我对执行时间步所需的所有计算所花费的时间进行了基准测试,并将其与所涉及的粒子数量一起记录下来。我在四核超线程 i7 上运行代码,所以我期待性能上升(所需时间下降),线程数最多约为 8,但最快的实现没有工作线程(函数只是执行而不是添加到队列中,)并且对于每个工作线程,代码变得越来越慢(比每个新线程的非线程实现的时间要多一步!)我快速查看了我的处理器使用情况应用程序,而且无论有多少线程正在运行,python 似乎从未真正超过 130% 的 CPU 使用率。该机器有足够的余量,整体系统使用率约为 200%。

现在我的队列实现的一部分(如下所示)是从队列中随机选择一个项目,因为每个工作项目的执行都需要锁定两个元素,并且相似的元素将彼此靠近队列。因此,我希望线程选择随机索引并攻击队列的不同位以最小化互斥冲突。

现在,我了解到我最初使用 rand() 的尝试会很慢,因为我的随机数不是线程安全的(这句话有意义吗?不确定...)

我已经尝试使用 random()drand48_r 实现(尽管不幸的是,后者似乎在 OS X 上不可用)但对统计数据无济于事。

也许其他人可以告诉我问题的原因可能是什么?代码(worker 函数)在下面,如果您认为 queue_add 函数或构造函数中的任何一个也可能有用,请大声喊叫。

void* worker_thread_function(void* untyped_queue) {

  queue_t* queue = (queue_t*)untyped_queue;
  int success = 0;
  int rand_id;
  long int temp;
  work_item_t* work_to_do = NULL;
  int work_items_completed = 0;

  while (1) {
    if (pthread_mutex_lock(queue->mutex)) {

      // error case, try again:
      continue;
    }

    while (!success) {

      if (queue->queue->count == 0) {

        pthread_mutex_unlock(queue->mutex);
        break;
      }

      // choose a random item from the work queue, in order to avoid clashing element mutexes.
      rand_id = random() % queue->queue->count;

      if (!pthread_mutex_trylock(((work_item_t*)queue->queue->items[rand_id])->mutex)) {

        // obtain mutex locks on both elements for the work item.
        work_to_do = (work_item_t*)queue->queue->items[rand_id];

        if (!pthread_mutex_trylock(((element_t*)work_to_do->element_1)->mutex)){ 
          if (!pthread_mutex_trylock(((element_t*)work_to_do->element_2)->mutex)) {

            success = 1;
          } else {

            // only locked element_1 and work item:
            pthread_mutex_unlock(((element_t*)work_to_do->element_1)->mutex);
            pthread_mutex_unlock(work_to_do->mutex);
            work_to_do = NULL;
          }
        } else {

          // couldn't lock element_1, didn't even try 2:
          pthread_mutex_unlock(work_to_do->mutex);
          work_to_do = NULL;
        }
      }
    }

    if (work_to_do == NULL) {
       if (queue->queue->count == 0 && queue->exit_flag) {

        break;
      } else {

        continue;
      }
    }

    queue_remove_work_item(queue, rand_id, NULL, 1);
    pthread_mutex_unlock(work_to_do->mutex);

    pthread_mutex_unlock(queue->mutex);

    // At this point, we have mutex locks for the two elements in question, and a
    // work item no longer visible to any other threads. we have also unlocked the main
    // shared queue, and are free to perform the work on the elements.
    execute_function(
      work_to_do->interaction_function,
      (element_t*)work_to_do->element_1,
      (element_t*)work_to_do->element_2,
      (simulation_parameters_t*)work_to_do->params
    );

    // now finished, we should unlock both the elements:
    pthread_mutex_unlock(((element_t*)work_to_do->element_1)->mutex);
    pthread_mutex_unlock(((element_t*)work_to_do->element_2)->mutex);

    // and release the work_item RAM:
    work_item_destroy((void*)work_to_do);
    work_to_do = NULL;

    work_items_completed++;
    success = 0;
  }
  return NULL;
}

【问题讨论】:

    标签: python c multithreading random


    【解决方案1】:

    要知道这是否是您程序的瓶颈,您必须进行基准测试和检查,但这很有可能。

    random() 和具有隐藏状态变量的朋友可能是并行编程的严重瓶颈。 如果它们是线程安全的,这通常是通过互斥访问来完成的,所以一切都会变慢。

    POSIX 系统上线程安全随机生成器的可移植选择是erand48。与drand48 相比,它接收状态变量作为参数。你只需要在每个线程的堆栈上保留一个状态变量(它是一个unsigned short[3])并用它调用erand48

    还要记住,这些是随机生成器。如果您在不同线程之间使用相同的状态变量,则您的随机数不是独立的。

    【讨论】:

      【解决方案2】:

      random() 似乎不是您的问题,因为无论线程数如何,它都是相同的代码。由于性能会随着线程数的增加而下降,因此您很可能会被锁定开销杀死。你真的需要多线程吗?工作函数需要多长时间,您的平均队列深度是多少?随机选择项目似乎是个坏主意。绝对如果队列数

      【讨论】:

      • 粒子数是从 N = 100 到 100,000 的任意值,您至少需要进行该数量的计算,可能更像 10N。对于大数字,时间步长可能需要一秒钟,当您需要运行 50,000 个以达到平衡时... :) 谢谢,不过,循环法可能只是在不进行大量随机计算的情况下最大限度地减少锁定。
      • 你可以做一些分析,但一般来说,像 rand() 这样的伪随机数生成器(这已经足够好了)实际上计算量不是很大。它们可以通过几次乘法和加法来实现,结果溢出,或者使用反馈移位寄存器。加密随机生成器可以是重量级的,但你绝对不需要。
      • 确实如此,但循环法也减少了对其他锁定行为的需求,并且在任何时候都让一个线程不受阻碍地处理工作队列。实现它的一半,但是当多个线程时会出现令人沮丧的段错误:/所以很快就会知道! :)
      • 谢谢。循环赛策略似乎是最好的。我还没有让任何线程代码实际上比单线程运行得更快(大概是因为单线程时缓存工作得非常好)但我至少有时会使用超过 600% 的 CPU——这意味着它是我的代码很慢而不是互斥锁!非常感谢...
      【解决方案3】:

      Python 线程不是真正的线程。所有 python 线程都在同一个操作系统级别的线程中运行,并且由于 GIL(全局解释器锁)而一次执行一个。如果工人在上下文中的寿命相对较长,那么用进程重写代码可能会起到作用。

      Wikipedia's page on GIL

      ----编辑----

      是的,这是在 c 中。但 GIL 仍然很重要。 Info on threads in c extensions

      【讨论】:

      • 等等,Python 搞乱了我使用 pthread 等系统 C 库的方式?!我认为 C 扩展和纯 C 程序一样快,在用 C 编写的位期间,不是吗?
      • 我不完全确定它是否能够立即阻止您的 pthread,但这种行为听起来与我使用常规 python 线程所期望的完全一样。只要您没有在线程中触摸 python 对象,我在文档中没有看到任何具体的内容。 docs.python.org/c-api/init.html#non-python-created-threads
      • 有趣。在这种情况下,我没有这样做,但我的代码中可能有一部分。谢谢!
      • (抱歉,当我消除了我似乎造成的可怕竞争条件后,我会重新提出这个问题。)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-05-15
      • 2016-05-13
      • 1970-01-01
      • 2013-02-02
      • 1970-01-01
      • 2010-09-05
      • 1970-01-01
      相关资源
      最近更新 更多