使用 Parallel Assign 进行多线程故障排除我在 Mac 系统上试图让多线程处理大型数据表 (>8MM) 的逐行操作。 它们只是简单的计算,例如除法或按原样复制值。 我无法附加我的专有数据,但在这篇文章中,我将在 JSL 中包含一个与我的数据集大小相同但包含随机数据的随机矩阵。
Names Default To Here(1);<BR /><BR />//For JMP community.
dt = J(Random Integer(6000000 , 8000000) , 5 , Random Integer(10)); <BR />dt = As Table(dt); <BR /><BR />//dt = Current Data Table(); //Table needs to be formatted such that data table has 5 columns with names X , Y , Z , Filename , Radius.
//columns must be in that order for matrix functionality to work
edgeExclusion = 0.01; //in units of radial meters
stepSize = 0.0025; //in meters; this is the resolution of the desired particle size analysis. This is the side length of the square of the defect area resolution desired.
waferSize = 0.2; //in meters, diameter
If( Floor( waferSize / stepSize ) != Ceiling( waferSize / stepSize ) , Throw( "!Defect resolution size not a common factor of wafer size"));
//Takes data table as matrix (fDT) and returns matrix of x, y, stddev, stddev points, and UID.
//Requires datatable to have X , Y , Z , Filename , Radius , and UID so that matrix fDT has X , Y , Z , Radius , and UID; in that order.
stdDevCalculator2 = Function ( { fDT , fFirstRow , fLastRow , fWS , fEE , fSS } , {Default Local} ,
fSubDT = fDT[ fFirstRow :: fLastRow , 0];
outputMat = J(0,5);
For( i = 1 , i <= N Rows(fSubDT) , i++ ,
//If( fSubDT[i,4] > (fWS/2)-fEE , Continue()); //Possibly remove to keep number of columns same for Parallel Assign
rowMat = Matrix( { Floor(fSubDT[i,1]/fSS) , Floor(fSubDT[i,2]/fSS) , fSubDT[i,3] , fSubDT[i,4] , fSubDT[i,5] } );
rowMat = Shape(rowMat,1,5);
If(N Rows(outputMat) == 0 , outputMat = rowMat , Concat To(outputMat , rowMat));
);
outputMat[1,0];
);
t1 = Tick Seconds();
numThreads = 8;
//Number of rows to delete to make Parallel Assign function operate. Trimming original data table to even multiple of thread count will allow tOutMat to be a square matrix,
//so that each thread is assigning the same number of columns.
delNum = N Rows(dt) - (Floor(N Rows(dt)/numThreads)*numThreads);
dt << Select Randomly(delNum);
dt << Delete Rows(); //Trim data set
numCols = 5 * (N Rows(dt)/numThreads); //5 for number of colums in original stacked data table.
//outMat = stdDevCalculator2(dtMat , 1 , N Rows(dtMat) , waferSize , edgeExclusion , stepSize); //For single threaded testing
dtMat = dt << GetAsMatrix;
tOutMat = J(numThreads,numCols);
Parallel Assign(
{tDT = dtMat, tWS = waferSize, tEE = edgeExclusion, tSS = stepSize, tSDC = Name Expr(stdDevCalculator2), tNum = numThreads },
tOutMat[i,0] = tSDC( tDT, 1 + Floor((i - 1) * N Rows(tDT) / tNum) , Floor( i * N Rows(tDT) / tNum) , tWS, tEE, tSS );
);
Show(tOutMat);
Write( "elapsed time=", Tick Seconds() - t1 );我的问题是,在我的 Mac 系统上,Parallel Assign 似乎可以正常工作,因为当该脚本运行时,我所有的核心利用率都跳到了 100%。 然而,在给它足够的时间结束后(在单线程版本结束后很久)我不得不强制退出。
我做了一些尝试以了解 JSL 如何更喜欢我将一组按行的值“注入”到另一个矩阵中。 举个例子:
Names Default to Here(1);
mat1 = J(3,5,0);
mat2 = J(1,5,Random Integer(5));
show(mat1);
Show(mat2);
mat1[2,0] = mat2[1,0];
Show(mat1);
mat1 现在的第二行将是 mat2 的完整副本。 我试图通过返回具有 N Rows(table)/N Threads 的单行来模拟这种相同类型的矩阵赋值。 有没有人对为什么我不能让它发挥作用有任何想法?
回复:使用并行分配进行多线程故障排除重新思考缓慢的 JSL:我认为 tDT 值是巨大的被复制的矩阵巨大的传递给 tf 函数的次数。8E6 * 8E6 个数字被复制。 线程正在等待内存访问。
tOutMat[i,j] = tf( tDT, i , j , tSS )
该副本不会在快速 JSL 中发生。
回复:使用并行分配进行多线程故障排除你好@msulzer_bsi
一些一般性陈述:当您尝试解决的问题不可并行化时,或者当您尝试计算的问题非常小时,多线程是一个坏主意,以至于生成线程将比在单线程中计算花费更多的时间。
如果您的问题可以分解成可以并行运行的部分,那么这样做可能是个好主意,但这取决于有多少部分可以并行运行以及加入子问题结果的难易程度。 如果您的算法需要多个连接(到一个线程)以在新线程上分解为新的子问题,那么它必须非常计算密集才能值得并行化,如果您的结果另一方面只是一个子问题结果列表加入一次,就有潜力。
我没有看到你的操作有多大好处,你在 2 秒内获得的速度提升不会太多,所以你为什么要做出这种努力并阻止可能/已经在其他线程上运行的其他任务。 有时,为您需要做的事情再多花 0.5 秒是不值得的。 除非它是一个高吞吐量的应用程序,在那里你得到所有 2-3s 的另一个数据块来处理,我没有看到一个很好的理由来加快这个操作,因为你说的 >8MM 的数据。
回复:使用并行分配进行多线程故障排除你好@Craige_Hales ,
我利用了您在并行分配中创建函数定义的建议。 见下文:
Names Default To Here(1);
dt = Current Data Table(); //Table needs to be formatted such that data table has 5 columns with names X , Y , Z , Filename , Radius.
//columns must be in that order for matrix functionality to work
edgeExclusion = 0.01; //in units of radial meters
stepSize = 0.0025; //in meters; this is the resolution of the desired particle size analysis. This is the side length of the square of the defect area resolution desired.
waferSize = 0.2; //in meters, diameter
If( Floor( waferSize / stepSize ) != Ceiling( waferSize / stepSize ) , Throw( "!Defect resolution size not a common factor of wafer size"));
t1 = Tick Seconds();
numThreads = 8;
//Number of rows to delete to make Parallel Assign function operate. Trimming original data table to even multiple of thread count will allow tOutMat to be a square matrix,
//so that each thread is assigning the same number of columns.
delNum = N Rows(dt) - (Floor(N Rows(dt)/numThreads)*numThreads);
If( delNum != 0 , dt << Select Randomly(delNum));
dt << Delete Rows(); //Trim data set
dtMat = dt << GetAsMatrix;
dtMat = Shape(dtMat , numThreads);
tOutMat = J(numThreads , N Cols(dtMat));
Parallel Assign(
{tDT = dtMat, tSS = stepSize, tf = Function ( { fDT , x , y , fSS } ,
If( Mod(y,5) == 1 , Floor(fDT[x,y]/fSS) ,
Mod(y,5) == 2 , Floor(fDT[x,y]/fSS) ,
fDT[x,y];
);
); },
tOutMat[i,j] = tf( tDT, i , j , tSS );
);
Show(tOutMat);
Write( "elapsed time=", Tick Seconds() - t1 );我现在正在执行脚本,不会给我任何奇怪的输出或抛出错误。 我重新评估了你解释的关于使用并行分配来评估输出矩阵的每个组合作为输出矩阵位置的函数。在我的例子中,我只对对原始长行数据表的前两列中的值执行计算感兴趣,因此 Mod() ==1 和 Mod() ==2 语句。 这不是最灵活的脚本,因为有严格的表格格式输入要求,但这是以后的问题。
但是,现在我遇到了一个奇怪的现象,当我执行脚本的多线程版本时,执行时间大约为 200 秒。 当我将并行分配语句重写为嵌套 for 循环时,如下所示作为单线程脚本:
For(i = 1 , i <= numThreads , i++ ,
For(j = 1 , j <= numCols , j++ ,
If( Mod(j,5) == 1 , tOutMat[i,j] = Floor(dtMat[i,j]/stepSize) ,
Mod(j,5) == 2 , tOutMat[i,j] = Floor(dtMat[i,j]/stepSize) ,
tOutMat[i,j] = dtMat[i,j];
);
);
);
我可以获得执行时间~2秒...... 我塑造了输入矩阵,即使它是不必要的,这样我会更均匀地比较单线程和多线程操作的过程。 我不明白是什么开销导致多线程版本变得如此慢。 从我第一次发布问题开始,我已经大大减少了矩阵计算。 你有什么见解吗?
谢谢,姆苏尔泽
回复:使用并行分配进行多线程故障排除嗨克雷奇,
非常感谢您对我的问题的回答;我希望至少您会看到该线程,因为我对 Parallel Assign 的学习主要是由于您之前发布的一些示例。 我将尝试消化您陈述的核心含义,然后将我可以应用于我的具体案例的任何结果回复给您。
回复:使用并行分配进行多线程故障排除我认为您希望 Parallel Assign 无法做到。 这段代码
tOutMat[i,0] =
在 Parallel Assign 中让我感到惊讶; Parallel Assign 唯一期望的是这些下标的 JSL 变量名称,而不是零。 零似乎被悄悄地忽略了,赋值的右侧无法知道第二个索引是什么。这里有一些例子;您可以将第二个下标更改为零,然后看到它被忽略了。
一。 使用每个线程的局部变量 i,并在每次使用后递增它。 j,k(或 j,0)索引值不用于确定为元素计算的值。
x = J( 7, 11 );
Parallel Assign( {i = 0}, x[j, k] = (i++) );
Show( x );x =
[ 0 0 0 0 1 1 1 1 2 2 2,
2 3 3 3 3 4 4 4 4 5 5,
5 5 6 6 6 6 7 7 7 7 8,
8 8 8 9 9 9 9 10 10 10 10,
11 11 11 11 12 12 12 12 13 13 13,
13 14 14 14 14 15 15 15 15 16 16,
16 16 17 17 17 17 18 18 18 18 19];
二。 相似,以另一种方式显示同一事物。 每个线程都获得一个保持不变的唯一值。 h=4 暗示有多少处理器可用。 您不应依赖 JMP 总是如图所示交错计算,但现在就是这样。
x = J( 7, 11 );
h = 0;
Parallel Assign( {i = (h++)}, x[j, k] = i );
Show( x, h );x =
[ 0 1 2 3 0 1 2 3 0 1 2,
3 0 1 2 3 0 1 2 3 0 1,
2 3 0 1 2 3 0 1 2 3 0,
1 2 3 0 1 2 3 0 1 2 3,
0 1 2 3 0 1 2 3 0 1 2,
3 0 1 2 3 0 1 2 3 0 1,
2 3 0 1 2 3 0 1 2 3 0];
h = 4;
三。 使用单个全局变量。 每次使用后增加。 线程相互锁定,可能会或可能不会以任何特定顺序运行。
x = J( 7, 11 );
global:g = 0;
Parallel Assign( {i = (h++)}, x[j, k] = (global:g++) );
Show( x, g );x =
[ 0 20 39 58 1 21 40 59 2 22 41,
60 3 23 42 61 4 24 43 62 5 25,
44 63 6 26 45 64 7 27 46 65 8,
28 47 66 9 29 48 67 10 30 49 68,
11 31 50 69 12 32 51 70 13 33 52,
71 14 34 53 72 15 35 54 73 16 36,
55 74 17 37 56 75 18 38 57 76 19];
g = 77;
在上述所有情况下,将 k 替换为 0 不会有任何改变。 在内部,Parallel Assign 仍在计算所有索引对并要求其中一个线程为每一对计算一个值。
如果您继续探索这条道路,我建议您为每个线程创建单独的函数实例,例如
x = J( 2, 3 );
Parallel Assign( {f = Function( {x, y}, x * y )}, x[i, j] = f( i, j ) );
Show( x );x = [1 2 3, 2 4 6];
这导致每个线程的本地 f 都有一个单独的 JSL 函数副本。 我认为 nameexpr 不会制作单独的副本,他们可能需要分开才能正确并行运行。
您也可以使用对全局数组元素的赋值。 使用合适长度的一维数组来驱动并行分配:(还要注意括号中的多语句右侧值)
global:results = J( 5, 7, . );
driver = J( N Rows( results ), 1, 0 );
h = 0;
Parallel Assign(
{procNum = (1 + h++), NC = N Cols( results )},
driver[j] =
(
For( k = 1, k <= NC, k += 1,
global:results[j, k] = procNum * 100 + j * 10 + k
);
procNum// value assigned to driver
)
);
Show( global:results, h, driver );全球:结果=
[ 111 112 113 114 115 116 117,
221 222 223 224 225 226 227,
331 332 333 334 335 336 337,
441 442 443 444 445 446 447,
151 152 153 154 155 156 157];
h = 4;
司机 = [1, 2, 3, 4, 1];
但是全局变量可能会使它运行得像单线程一样慢。
最后:Parallel Assign 试图阻止您编写不安全的代码,这些代码可能允许两个线程同时访问某些内部结构,从而产生不正确的结果或崩溃。 可以编写 JSL 来避开 Parallel Assign 的防御……如果您正在挑战极限,请经常保存您的工作。