【问题标题】:why is this parallel function for computing the longest common subsequence slower than the serial?为什么这个用于计算最长公共子序列的并行函数比串行函数慢?
【发布时间】:2012-11-14 00:14:43
【问题描述】:

LCS 的并行计算遵循波前模式。 这是比串行实现慢的并行功能。 (我认为对角线数(平行)与行数(串行)有关系)

void parallelLCS(char * sequence_a, char * sequence_b, size_t size_a, size_t size_b) {
double start, end;

int ** dp_table = new int*[size_a + 1];

for (int i = 0; i <= size_a; i++)
    dp_table[i] = new int[size_b + 1];

for (int i = 1; i <= size_a; i++)
    dp_table[i][0] = 0;
for (int j = 0; j <= size_b; j++)
    dp_table[0][j] = 0;

int p_threads = 2;
int diagonals = size_a + size_b;

start = omp_get_wtime();
#pragma omp parallel num_threads(p_threads) default(none) firstprivate(p_threads,size_a,size_b,sequence_a,sequence_b) shared(dp_table,diagonals)
{
    for (int curr_diagonal = 1; curr_diagonal <= (diagonals - 1);) {
        int j = omp_get_thread_num() + 1;   //column index
        int i = curr_diagonal - j + 1;      //row index
        for (; j <= curr_diagonal; j += p_threads, i = i - p_threads) {
            if (i <= size_a && j <= size_b) {
                if (sequence_a[i] == sequence_b[j]) {
                    dp_table[i][j] = dp_table[i - 1][j - 1] + 1;
                } else if (dp_table[i - 1][j] >= dp_table[i][j - 1]) {
                    dp_table[i][j] = dp_table[i - 1][j];
                } else {
                    dp_table[i][j] = dp_table[i][j - 1];
                }
            }
        }
        curr_diagonal++;
#pragma omp barrier
    }
}
end = omp_get_wtime();

printf("\nParallel - Final answer: %d\n", dp_table[size_a][size_b]);
printf("Time: %f\n", end - start);

//Delete dp_table
for (int i = 0; i <= size_a; i++)
    delete [] dp_table[i];
delete [] dp_table;
}

这里是串行函数

void serialLCS(char * sequence_a, char * sequence_b, size_t size_a, size_t size_b) {
double start, end;
int ** dp_table = new int*[size_a + 1];
for (int i = 0; i <= size_a; i++)
    dp_table[i] = new int[size_b + 1];

for (int i = 1; i <= size_a; i++)
    dp_table[i][0] = 0;
for (int j = 0; j <= size_b; j++)
    dp_table[0][j] = 0;

start = omp_get_wtime();
for (int i = 1; i <= size_a; i++) {
    for (int j = 1; j <= size_b; j++) {
        if (sequence_a[i] == sequence_b[j]) {
            dp_table[i][j] = dp_table[i - 1][j - 1] + 1;
        } else if (dp_table[i - 1][j] >= dp_table[i][j - 1]) {
            dp_table[i][j] = dp_table[i - 1][j];
        } else {
            dp_table[i][j] = dp_table[i][j - 1];
        }
    }
}
end = omp_get_wtime();
printf("\nSerial - Final answer: %d\n", dp_table[size_a][size_b]);
printf("Time: %f\n", end - start);

//Delete dp_table
for (int i = 0; i <= size_a; i++)
    delete [] dp_table[i];
delete [] dp_table;
}

...我想我会添加测试功能

#include <cstdlib>
#include <stdio.h>

#include <omp.h>

void serialLCS(char * sequence_a, char * sequence_b, size_t size_a, size_t size_b);
void parallelLCS(char * sequence_a, char * sequence_b, size_t size_a, size_t size_b);

int main() {

size_t size_a;
size_t size_b;

printf("Enter size of sequence A: ");
scanf("%zd",&size_a);
printf("Enter size of sequence B: ");
scanf("%zd",&size_b);

//keep larger sequence in sequence_a
if (size_b > size_a) size_a ^= size_b ^= size_a ^= size_b;

char * sequence_a = new char[size_a + 1];
char * sequence_b = new char[size_b + 1];
sequence_a[0] = sequence_b[0] = '0';

const size_t alphabet_size = 12;
char A[alphabet_size] = {'A', 'T', 'G', 'C', 'Q', 'W', 'E', 'R', 'Y', 'U', 'I', 'O'};
char AA[alphabet_size] = {'T', 'C', 'A', 'G', 'R', 'E', 'W', 'Q', 'O', 'I', 'U', 'Y'};

for (size_t i = 1; i < size_a; i++) {
    sequence_a[i] = A[rand() % alphabet_size];
}
for (size_t i = 1; i < size_b; i++) {
    sequence_b[i] = AA[rand() % alphabet_size];
}

serialLCS(sequence_a, sequence_b, size_a, size_b);
parallelLCS(sequence_a, sequence_b, size_a, size_b);

delete [] sequence_a;
delete [] sequence_b;

return 0;
}

【问题讨论】:

  • 慢多少?可能是如果您的输入数据太小,则创建线程所花费的时间超过了并行计算所节省的时间。尝试运行非常大的序列,看看它是否仍然较慢。
  • 我试过用 20000 个字符的序列运行它。但运行时间差不多。
  • 并行功能似乎可以很好地扩展,..虽然我只能使用四核
  • @dreamcrash 我对实际运行时感兴趣.. 对于两个 10000char 序列,串行需要 1.8 秒,并行需要 7.3 使用 2 个内核
  • @EamonnMcEvoy 抱歉我没有在更新后标记你

标签: c++ openmp dynamic-programming lcs longest-substring


【解决方案1】:

问题不在于 OpenMP,而在于您在并行实现中访问数据的方式。即使你只用一个线程运行并行版本,它仍然慢两倍左右。

欢迎来到非缓存友好数据结构的世界。由于对角线依赖性,您可以通过对角线遍历矩阵,但仍以通常的方式存储它。数据访问模式是强非线性的,因此对缓存非常不友好。在旧的 16 核 Xeon X7350 系统上以单线程模式运行代码时,观察 L1 和 L2 缓存未命中的数量:

流程时间线的绿色部分代表代码的串行部分。橙色部分是(由于单线程执行而处于非活动状态)OpenMP parallel 区域。您可以清楚地看到串行代码对缓存非常友好 - 不仅 L2 缓存未命中的数量相对较低,而且 L1 缓存未命中的数量也相对较低。但是在代码的并行部分,由于对角线遍历矩阵时的步幅非常大,缓存不断被丢弃,未命中率很高。

如果有两个线程,情况会变得更糟。属于同一矩阵行的两条相邻对角线的元素很可能落入同一高速缓存行。但是其中一条对角线由一个线程处理,另一条由另一个线程处理。因此,您的代码会遇到大量错误共享。更不用说现代多插槽 AMD64 或(后)Nehalem 系统上的 NUMA 问题了。

解决方案不是简单地通过对角线遍历矩阵,而是将矩阵以倾斜格式存储,以便每个对角线在内存中占据连续的部分。

【讨论】:

  • 这很有意义。谢谢...我猜这是传统波前模式固有的问题,尽管我现在可以看到可以轻松改进它的方法
  • 只是出于好奇,您知道为什么将处理器分配给行而不是列并不能提高运行时间吗?前任。 P1 总是计算对角线上属于同一行的单元格...
  • 你能显示一些代码吗?我无法想象你所描述的情况。
  • int i = curr_diagonal; int j = 1; //column index for (; j &lt;= curr_diagonal; j++, i--) { d = i % p_threads; d = d == 0 ? p_threads : d; if (i &lt;= size_a &amp;&amp; j &lt;= size_b &amp;&amp; (d == tid)) { if (sequence_a[i] == sequence_b[j]) { dp_table[i][j] = dp_table[i - 1][j - 1] + 1; } else if (dp_table[i - 1][j] &gt;= dp_table[i][j - 1]) { dp_table[i][j] = dp_table[i - 1][j]; } else { dp_table[i][j] = dp_table[i][j - 1]; } } }
  • thread_ids 映射到对角线,&& (d == tid) 将线程限制在同一行中的单元格中。代码运行,只需替换并行部分中的相应段
猜你喜欢
  • 2020-11-12
  • 1970-01-01
  • 1970-01-01
  • 2018-11-28
  • 2021-09-20
  • 2014-03-06
  • 2013-09-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多