随着计算的进步,我已经从 1 核到 2 核再到 4 核……到我的工作站上的 64 个线程。 同时 .jsl 有效地保持单线程。 我知道有些平台会利用所有线程,但我希望能够将简单的 jsl 操作随意分配给多个线程。 例如,如果我有一个对 10,000,000 行数据逐行操作的脚本,如果我在 for 循环中逐行迭代并且我知道每一行的结果独立于其他行,我想拆分它操作到 64 个异步 for 循环,在 64 个循环并行完成时暂停其他执行,然后在全部完成后移回单线程同步脚本,就像我在 .py 中使用 Pool() 和 poolObject.join 一样。
正如上面直接提到的,这超出了我们将在 JSL 中所做的范围。 如果有您认为可以从多线程中受益的特定操作,请打开其他希望的详细信息。
虽然这个愿望有很多票,但不幸的是,除了 Parallel Assign() 提供的东西之外,任何东西都很难实现。 由于与用户界面的紧密集成以及 JMP 的交互性质,不可能在不将可能的 JSL 限制在 Parallel Assign 允许的范围内的情况下提供线程安全的环境。
利用多线程的最佳方式是尽可能多地使用内置平台。 这些平台是我们可以直接提供多线程解决方案的地方。在您认为多线程有用的内置操作中打开其他愿望是让我们知道您希望在哪里看到更多多线程的最佳方式。
至于将简单的 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