随着计算技术的进步,我的工作站从 1 核变为 2 核、4 核……再到 64 个线程。 与此同时,.jsl 实际上仍然是单线程的。 我知道有些平台会利用所有线程,但我希望能够将简单的 jsl 操作随意分配给多个线程。 例如,如果我有一个对 10,000,000 行数据按行操作的脚本,如果我在 for 循环中逐行迭代并且我知道每一行的结果独立于其他行,我想将其拆分操作进入 64 个异步 for 循环,在 64 个循环并行完成时暂停其他执行,然后在所有完成后返回到单线程同步脚本,就像我在 .py 中使用 Pool() 和 poolObject.join 所做的那样。
嗨史蒂夫,
JMP 中有很多地方我们尝试利用多核:从保存/打开压缩数据表(每列发送到不同的线程)到具有多列的分布平台(每个分布分析都在不同的线程),到Mersenne-Twister 随机均匀生成器。 正如您所看到的,多线程是在很多不同的地方完成的。
所以,我们确实没有这样的清单。 是什么推动了这里的问题?
正如上面直接指出的,这个愿望超出了我们在 JSL 中要做的范围。 如果您认为某些特定操作会从多线程中受益,请提出其他愿望并提供详细信息。
虽然这个愿望得到了很多人的支持,但不幸的是,除了 Parallel Assign() 提供的功能之外的任何东西都将很难实现。 由于与用户界面的紧密集成以及 JMP 的交互性质,如果不将可能的 JSL 限制在并行分配所允许的范围内,就不可能提供线程安全环境。
利用多线程的最佳方法是尽可能使用内置平台。 这些平台是我们可以直接提供多线程解决方案的地方。在您认为多线程有用的地方提出对内置操作的其他愿望是让我们知道您希望在哪里看到更多多线程的最佳方式。
至于将简单的 JSL 操作分派给多个线程,有一个名为 Parallel Assign() 的函数。它将工作分成多个块,每个块的代码产生一个数字,整个操作的结果是数字矩阵。
脚本索引中有一个 Parallel Assign() 的玩具示例,Frame Size() 的第二个示例显示了计算 Mandelbrot 集数据的更详细的示例。 另一个小例子是@Craige_Hales的JSL Cookbook 循环帖子还有更多关于曼德尔博特在他的博客中。
我在下面提供了另一个示例,它使用多线程查找文件列表中最大的文件。 该脚本首先运行单线程版本,您可以在此任务管理器屏幕截图中看到两个活动突发。 单线程版本是长台面,并行版本是高山峰。 对于 8 核上的 17,000 个文件,时间从 13 秒缩短到 4.5 秒,或者快了 3 倍。 并不理想,但通常情况下,线程共享一些资源,例如本例中的磁盘访问。
并行执行的 JSL 确实需要简单,这样它就不会修改共享的全局状态。 这就是为什么第一步要复制一些全局变量的原因。 这也意味着并行代码无法执行创建数据表或与显示器交互等操作。
我们有兴趣了解哪些其他简单操作可能需要更丰富的界面。 据我所知,每个工人都要退回除该编号以外的物品。 本质上,这意味着使用结果列表而不是矩阵。
----
Names Default To Here( 1 );
// Example for Parallel Assign().
// get a list of a lot of files
dir = If(Host is("Mac"), "$SAMPLE_DATA", "$JMP_HOME");
all files = Files In Directory( dir, "recursive" );
naf = N Items( all files );
// convert relative paths to full paths
For( i = 1, i <= naf, i++,
all files[i] = dir || "/" || all files[i]
);
// Function that looks at a range of a given file list and returns
// the index of the largest file.
// Importantly, this function uses no global variables.
largest file = Function( {files, first, last},
{i, i large = -1, bytes large = -1, bytes},
For( i = first, i <= last, i++,
bytes = File Size( files[i] );
If( bytes > bytes large,
bytes large = bytes;
i large = i;
);
);
i large;
);
// Single-threaded approach: see all the files to the function.
t0 = Tick Seconds();
lf = largest file( all files, 1, naf );
t1 = Tick Seconds();
Show( naf, lf, all files[lf], t1 - t0 );
// about 13sec on my machine (17k files on Win)
wait(1); // so we can see a break in a CPU activity monitor
// Multi-threaded approach: break the list up into 8 ranges that
// can be handled by separate threads.
t0 = Tick Seconds();
num chunks = 8; // JMP may create this many threads, or fewer
// initialize to -1, so we can detect failure if desired
results = J( num chunks, 1, -1 );
Parallel Assign(
// copy these globals into the thread's local variables
{tf = Name Expr( largest file ), tfiles = all files, tn = num chunks},
// make this assignment for each chunk:
results[i] = Local( {n = N Items( tfiles )},
tf( tfiles, 1 + Floor( (i - 1) * n / tn ), Floor( i * n / tn ) )
)
);
// We need to call the function once more to see which of the top
// files is the overall largest.
top files = all files[results];
toplf = largest file( top files, 1, num chunks );
lf = results[toplf];
t1 = Tick Seconds();
Show( naf, lf, all files[lf], t1 - t0 );
// about 4.5sec