【问题标题】:What strategies are efficient to handle concurrent reads on heterogeneous multi-core architectures?在异构多核架构上处理并发读取的有效策略是什么?
【发布时间】:2010-04-15 08:46:46
【问题描述】:

我正在应对同时使用 8 核 机器和高端 GPU (Tesla 10) 功能的挑战。

我有一个大输入文件,每个内核一个线程,一个用于 GPU 处理。 Gpu 线程为了高效,需要输入大量的行,而 Cpu 线程只需要一行即可继续(在临时缓冲区中存储多行较慢)。该文件不需要按顺序读取。我正在使用 boost

我的策略是在输入流上有一个互斥体,每个线程锁定 - 解锁它。 这不是最优的,因为 gpu 线程在锁定互斥体时应该具有更高的优先级,这是最快且要求最高的线程。

我可以想出不同的解决方案,但在急于实施之前,我想有一些指导方针。

您使用/推荐什么方法?

【问题讨论】:

  • 您有多少个并行磁盘驱动器?读取大文件时,磁盘 I/O 始终是真正的瓶颈。
  • Raid 0 中的 2。问题与处理数据划分的线程逻辑有关。
  • 同步在哪里发挥作用?您能否详细说明每个线程正在执行的处理类型?比如哪些线程正在读取和写入以及何时。
  • 诀窍是,如果您在磁盘 I/O 上遇到瓶颈,那么无论您的线程逻辑是什么,都不会影响运行时间。您的计算量是否足够大,以至于处理一行数据比从磁盘读取数据需要更长的时间?
  • @Mark B 是的,线程对每一行执行大量处理,只产生是或否。 @SpacegosthAli 他们不会在任何地方写任何东西。诀窍是弄清楚如何最好地处理线程的馈送,以便每个线程都有工作要做,尤其是 GPU 的

标签: c++ multithreading boost concurrency


【解决方案1】:

如果“每个线程 1 行”不是严格要求,您可能根本不需要锁定,有时您最多可以上 2 行或 3 行。然后,您可以根据公式平均拆分文件。假设您要读取总共 1024 KB 块中的文件(这也可能是千兆字节):您将其分成具有优先级的内核。所以:

  • #define BLOCK_SIZE (1024 * 1024)
  • #define REGULAR_THREAD_BLOCK_SIZE (BLOCK_SIZE/(2 * NUM_CORES)) // 64kb
  • #define GPU_THREAD_BLOCK_SIZE (BLOCK_SIZE/2)
  • 每个核心获得 64 KB 块
    • 核心 1:偏移量 0,大小 = REGULAR_THREAD_BLOCK_SIZE
    • 核心 2:偏移量 65536,大小 = REGULAR_THREAD_BLOCK_SIZE
    • 核心 3:偏移 131072,大小 = REGULAR_THREAD_BLOCK_SIZE
    • 核心n:偏移量(n * REGULAR_THREAD_BLOCK_SIZE),大小=REGULAR_THREAD_BLOCK_SIZE
  • GPU 获得 512 KB,偏移量 = (NUM_CORES * REGULAR_THREAD_BLOCK_SIZE),大小 = GPU_THREAD_BLOCK_SIZE

所以理想情况下它们不会重叠。但在某些情况下它们可以重叠。由于您正在阅读文本文件,因此一行可能会落入下一个核心的块中。为了避免重叠,你总是跳过其他核心的第一行,并且总是完成最后一行,假设下一个线程无论如何都会跳过它,这里是伪代码:

void threadProcess(buf, startOFfset, blockSize)
{
    int offset = startOffset;
    int endOffset = startOffset + blockSize;
    if(coreNum > 0) {
        // skip to the next line
        while(buf[offset] != '\n' && offset < endOffset) offset++;
    }
    if(offset >= endOffset) return; // nothing left to process
    // read number of lines provided in buffer
    char *currentLine = allocLineBuffer(); // opening door to security exploits :)
    int strPos = 0;
    while(offset < endOffset) {
        if(buf[offset] == '\n') {
            currentLine[strPos] = 0;
            processLine(currentLine); // do line processing here
            strPos = 0; // fresh start
            offset++;
            continue;
        }
        currentLine[strPos] = buf[offset];
        offset++;
        strPos++;
    }
    // read the remaineder past the buf
    strPos = 0;
    while(buf[offset] != '\n') {
        currentLine[strPos++] = buf[offset++];
    }
    currentLine[strPos] = 0;
    processLine(currentLine); // process the carryover line
}

如您所见,这将读取块的处理并行化,而不是读取本身。你如何并行读取?最好的最棒的方法是将整个块内存映射到内存中。这将获得最佳的 I/O 性能,因为它是最低级别。

【讨论】:

  • +1 是的,我认为这是最好的解决方案。唯一的担忧是 GPU 在线程之前结束并且可能从其他线程中“窃取”剩余的段。不错的宏。
  • 在这种情况下,您可以做两件事:1) 您可以更改 GPU 切片的大小(而不是一半,使其变为四分之一等)2) 您可以更改映射的总块大小一次。两者都会一次性改变 GPU 处理的行数。
  • +1 写得很好的答案,我同意这种方法,在这种情况下完全不需要锁定
【解决方案2】:

一些想法:

1) 因为瓶颈不在 IO 中,所以文件应该几乎完全保存在 RAM 中以便于访问。

2) 实现不应允许线程阻塞。如果这样可以减少阻塞,最好有稍微非最优的解决方案。

假设我们有大数据文件线程可以采用暗中射击的策略。这意味着一旦线程获得锁,它只会增加 fpos 并解锁内存。然后它授予自己处理刚刚获得的部分内存的特权。例如,线程可以处理所有以片段开头的行。

结果:

1) 线程阻塞几乎是不可能的。锁定时间非常短(在几条指令的范围内+刷新缓存的时间)

2) 灵活性。线程可以根据需要获取尽可能多的数据。

当然,应该有一些机制来适应数据文件中的行长以避免最坏的情况。

【讨论】:

  • 您建议将整个文件映射到内存中,然后根据需要让每个线程占用一段内存。这很有趣,因为就像@Menox 缓冲区的想法,但您在处理数据时不会锁定。唯一的黑暗部分是当一行不完全在一个单独的段内时,我猜一个线程可以读取下一段以完成最后一行。
  • 是的。实际上这不是一个大问题,因为线程可以安全地从文件中的任何位置读取,因为它是只读的。唯一的问题是所有权。我能想到的机制之一是,如果线程在其段中找到前面的换行符,则它拥有一行。当然,文件的第一行将是一个例外。
【解决方案3】:

我会使用缓冲区。有一个线程从磁盘填充该缓冲区。每个线程都会锁定缓冲区,将数据读入线程的缓冲区,然后在处理数据之前释放互斥锁上的锁。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-21
    • 1970-01-01
    • 1970-01-01
    • 2010-09-18
    相关资源
    最近更新 更多