Какие стратегии эффективны для обработки одновременных чтений на разнородных многоядерных архитектурах? - PullRequest
3 голосов
/ 15 апреля 2010

Я решаю проблему использования возможностей 8 ядра машины и высокопроизводительного GPU (Tesla 10) .

У меня есть один большой входной файл, один поток для каждого ядра и один для обработки графического процессора. Поток Gpu, чтобы быть эффективным, требует большого количества строк от ввода, в то время как потоку процессора требуется только одна строка (сохранение нескольких строк во временном буфере было медленнее). Файл не должен быть прочитан последовательно. Я использую boost .

Моя стратегия состоит в том, чтобы мьютекс во входном потоке, и каждый поток блокирует - разблокирует его. Это не оптимально, потому что поток GPU должен иметь более высокий приоритет при блокировке мьютекса, будучи самым быстрым и наиболее требовательным.

Я могу придумать разные решения, но прежде чем приступить к внедрению, я хотел бы получить некоторые рекомендации.

Какой подход вы используете / рекомендуете?

Ответы [ 3 ]

2 голосов
/ 18 апреля 2010

Вам может не потребоваться блокировка вообще, если «1 строка на поток» не является строгим требованием, и вы можете иногда доходить до 2 или трех строк. Затем вы можете разделить файл поровну, основываясь на формуле. Предположим, вы хотите прочитать файл в блоках по 1024 килобайта (это может быть и гигабайт): вы разбили его на ядра с приоритетом. Итак:

  • #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 КБ
    • Ядро 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
  • ГП получает 512 КБ, смещение = (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
}

Как вы можете видеть, это распараллеливает обработку блока чтения, а не сами чтения. Как вы распараллеливаете чтение? Лучшим, самым удивительным способом было бы отображение в памяти всего блока. Это даст лучшую производительность ввода-вывода, так как это самый низкий уровень.

1 голос
/ 18 апреля 2010

Некоторые идеи:

1) Поскольку бутылочного соединения нет в IO, файл должен храниться почти полностью в оперативной памяти для более легкого доступа.

2) Реализация не должна позволять потокам блокироваться. Лучше иметь немного неоптимальное решение, если это уменьшает блокировку.

Предполагая, что у нас большие потоки файлов данных, можно использовать тактику «выстрел в темноте». Это означает, что как только поток получает блокировку, он просто увеличивает fpos и разблокирует память. Затем он предоставляет себе право обрабатывать часть памяти, которую он только что получил. Например, поток может обработать все строки, которые имеют начало во фрагменте.

Результаты:

1) Поток практически невозможно заблокировать. Время блокировки очень короткое (в диапазоне нескольких инструкций + время очистки кешей)

2) Гибкость. Поток может принимать столько данных, сколько он хочет.

Конечно, должны быть некоторые механизмы для адаптации к длине строки в файле данных, чтобы избежать наихудшего сценария.

0 голосов
/ 17 апреля 2010

Я бы использовал буфер. Иметь один поток, заполняющий этот буфер с диска. Каждый поток блокирует буфер, считывает данные в буфер потока, затем снимает блокировку с мьютекса перед обработкой данных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...