给定一个C字符串(以NULL字符常量终止的字符数组),我们必须找到字符串的长度。你能否提出一些方法来为N个执行线程并行化这个方法。我有问题分为子问题,因为访问不存在的阵列的位置将给出分段错误。
编辑:我并不担心并行执行此任务可能会带来更大的开销。只想知道是否可以这样做(使用类似openmp等的东西)。
答案 0 :(得分:2)
这可能不值得尝试。如果字符串很短,则开销将大于处理速度的增益。如果字符串非常长,速度可能会受到内存速度的限制,而不受CPU处理速度的限制。
答案 1 :(得分:2)
不,不能。因为每个步骤都需要知道先前的状态(我们是否在前一个char上遇到null)。您一次只能安全地检查1个字符。
想象一下,你正在翻转岩石,你必须停在下面的白色油漆(null),否则你会死(也就是段故障等)。
你不能让人们“相互提前”,因为白色的油漆岩石可能介于两者之间。
拥有多个人(线程/进程)只是轮流成为转向下一个摇滚的人。他们永远不会在彼此同时翻过岩石。
答案 2 :(得分:1)
我只想说一个标准的C字符串就无法完成。但是,如果您可以定义具有与进程一样多的字符的个人终止字符串 - 那么它是直截了当的。
答案 3 :(得分:1)
你知道那个char数组的最大大小吗?如果是这样,您可以在不同的junks中进行并行搜索,并返回具有最小索引的终结符的索引。 因此,您只能处理已分配的内存,无法获得段错误。
当然,这并不像s_nairs那样复杂,但很直接。 例如:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <omp.h>
int main(int argc, char **argv)
{
int N=1000;
char *str = calloc(N, sizeof(char));
strcpy(str, "This is a test string!");
fprintf(stdout, "%s\n", str);
int nthreads = omp_get_num_procs();
int i;
int ind[nthreads];
for( i = 0; i < nthreads; i++){
ind[i] = -1;
}
int procn;
int flag;
#pragma omp parallel private(procn, flag)
{
flag = 1;
procn = omp_get_thread_num();
#pragma omp for
for( i = 0; i < N; i++){
if (str[i] == '\0' && flag == 1){
ind[procn] = i;
flag = 0;
}
}
}
int len = 0;
for( i = 0; i < nthreads; i++){
if(ind[i]>-1){
len = ind[i];
break;
}
}
fprintf(stdout,"strlen %d\n", len);
free(str);
return 0;
}
答案 4 :(得分:1)
在Windows中包含SEH __try块中的不安全内存读取时,你可以做一些像这样丑陋的事情:
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 2
DWORD WINAPI FindZeroThread(LPVOID lpParameter)
{
const char* volatile* pp = (const char* volatile*)lpParameter;
__try
{
while (**pp)
{
(*pp) += N;
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
*pp = NULL;
}
return 0;
}
size_t pstrlen(const char* s)
{
int i;
HANDLE handles[N];
const char* volatile ptrs[N];
const char* p = (const char*)(UINT_PTR)-1;
for (i = 0; i < N; i++)
{
ptrs[i] = s + i;
handles[i] = CreateThread(NULL, 0, &FindZeroThread, (LPVOID)&ptrs[i], 0, NULL);
}
WaitForMultipleObjects(N, handles, TRUE /* bWaitAll */, INFINITE);
for (i = 0; i < N; i++)
{
CloseHandle(handles[i]);
if (ptrs[i] && p > ptrs[i]) p = ptrs[i];
}
return (size_t)(p - s);
}
#define LEN (20 * 1000 * 1000)
int main(void)
{
char* s = malloc(LEN);
memset(s, '*', LEN);
s[LEN - 1] = 0;
printf("strlen()=%zu pstrlen()=%zu\n", strlen(s), pstrlen(s));
return 0;
}
输出:
strlen()=19999999 pstrlen()=19999999
我认为使用MMX / SSE指令以更加平行的方式加速代码可能会更好。
编辑:毕竟,这在Windows上可能不是一个好主意,请参阅Raymond Chen的 IsBadXxxPtr should really be called CrashProgramRandomly
答案 5 :(得分:0)
让我承认这一点,
以下代码是使用C#而不是C编写的。您可以将我想要表达的想法联系起来。大多数内容来自并行模式(由Microsoft提供并行方法的草案文档)
为了尽可能进行最佳静态分区,您需要能够准确地预测所有迭代需要多长时间。这很少可行,导致需要更动态的分区,系统可以快速适应不断变化的工作负载。我们可以通过转移到分区权衡频谱的另一端来解决这个问题,尽可能多地实现负载均衡。
要做到这一点,我们可以让线程竞争迭代,而不是将一组给定的索引推送到每个线程。我们使用剩余迭代池来处理,最初开始填充所有迭代。在处理完所有迭代之前,每个线程都会进入迭代池,删除迭代值,处理它,然后重复。通过这种方式,我们可以以贪婪的方式实现可能的最佳负载平衡水平的近似(真正的最优化只能通过事先了解每次迭代需要多长时间才能实现)。如果线程在处理特定的长迭代时遇到困难,则其他线程将在此期间通过处理池中的工作来进行补偿。当然,即使采用这种方案,您仍然可以发现自己远远没有达到最佳分区(如果一个线程碰巧遇到几个比其余部分大得多的工作,可能会发生这种情况),但不知道处理时间多少如果要完成一项工作,就可以做更多的工作。
这是一个将负载平衡发挥到极致的示例实现。迭代值池保持为表示下一个可用迭代的单个整数,并通过原子递增此整数来处理“删除项目”中涉及的线程:
public static void MyParallelFor(
int inclusiveLowerBound, int exclusiveUpperBound, Action<int> body)
{
// Get the number of processors, initialize the number of remaining
// threads, and set the starting point for the iteration.
int numProcs = Environment.ProcessorCount;
int remainingWorkItems = numProcs;
int nextIteration = inclusiveLowerBound;
using (ManualResetEvent mre = new ManualResetEvent(false))
{
// Create each of the work items.
for (int p = 0; p < numProcs; p++)
{
ThreadPool.QueueUserWorkItem(delegate
{
int index;
while ((index = Interlocked.Increment(
ref nextIteration) - 1) < exclusiveUpperBound)
{
body(index);
}
if (Interlocked.Decrement(ref remainingWorkItems) == 0)
mre.Set();
});
}
// Wait for all threads to complete.
mre.WaitOne();
}
}