隨著計算技術的進步,我的工作站從 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