回复:來自 JMP JSL 的 Python 並行處理請注意,內容編輯器從 defs.py 的內容中刪除了一個回車符,應該顯示為:
def f(x):
return x*x
來自 JMP JSL 的 Python 並行處理在假期休息期間,我有時間修補 Python。 我們在 Python/JSL 接口的並行處理中遇到的問題是 Python 通常會啟動 JMP 的多個副本,而不是 Python。 原因是我們在 JMP 中嵌入了 Python,因此可執行文件是 JMP 而不是 Python。 我發現 multiprocessing 包允許將可執行文件設置為在運行並行代碼時啟動。 這是拼圖中缺失的部分。
下面的腳本我已經在 Mac 和 Windows 上使用 Python 3.9 在 JMP 15.2.1 上進行了測試。在我的 Windows 機器上,我必須使用 Python Init() 的 Path() 參數。 我有多個版本的 Python,並希望確保選擇正確的版本。 在我的 Mac 上,不帶參數的 Python Init() 適用於我的配置。 我在 Windows 上遇到了一個問題,我不得不硬編碼我的 pythonw.exe 的路徑。 您將需要更改它以匹配您的 Python 位置。 搜索修復我:
除了 Python Init() 參數和 Windows 上的 pythonw.exe 路徑之外,這應該在 JMP 15.2.1 和更新版本的 Mac 或 Windows 上運行沒有問題。 可以在具有 Python 支持但尚未經過測試的舊版 JMP 上運行。
您需要在與此腳本相同的目錄中創建一個 defs.py 文件。 多處理包要求並行運行的代碼來自可導入的代碼,而不是腳本的本地代碼。 所以這個腳本中的大部分工作是確保正確設置 sys.path 以便腳本可以簡單地導入 defs.py 文件。
defs.py 文件的全部內容都在文件註釋中,便於粘貼到新文件中。 下面重複的內容是:
def f(x):
return x*x
該腳本接受一個數字列表 [1,2,3] 並並行調用 f(x) 來平方給出結果 [1,4,9] 的值。
pyParallel.jsl
Names Default To Here( 1 );
/* Description: Python mulltiprocessing from JMP JSL
* File: pyParallel.jsl, and defs.py
* Author: Paul Nelson
* JMP Statistical Discovery, LLC
*
* Python multiprocessing from an embedded context adds additional challenges.
* Just attempting to run Python parallel processing modules, typically launches
* multiple copies of JMP, not executing the Python code in parallel.
*
* The multiprocessing module allows setting the executable to launch, since we
* are loading Python into JMP, it's an embedded case thus sys.executable => JMP, not Python.
*
*
* Note: Functions to be run in parallel must come from importable code, not local
* code definitions.
* A multiprocessing 'Feature', not bug, See: https://bugs.python.org/issue25053
*
* Known Bugs:
* On Windows within JMP, sys.exec_prefix is empty, so path to pythonw.exe has to be set explicitly
*
* Create a defs.py file with the contents below. Place in the same directory as this JSL file.
def f(x):
return x*x
*/
// On my windows machine since I have multiple versions of Python, I have to specify the
// full path to the python39.dll, on my Mac it just works.
If( Host Is("Windows"),
Python Init(Path("C:\\Users\\panels\\AppData\\Local\\Programs\\Python\\Python39\\python39.dll")),
Python Init()
);
// send the current working directory where we loaded this script, down to Python so we can find defs.py
script_path = Get Default Directory();
Python Send(script_path);
Python Submit("\[
import sys
import os
import multiprocessing
from multiprocessing import Pool
import platform
# The loaded Python does not have the current working directory initialized, so change it
# to the directory of the script, as sent in from JSL
print('script_path: ' + script_path)
hostPlatform = platform.system()
print(hostPlatform)
try:
if 'Windows' == hostPlatform:
# strip leading / off from path if it exists
if '/' == script_path[0]:
script_path = script_path[1:]
norm_path = os.path.normpath(script_path)
print(norm_path)
os.chdir(norm_path)
else:
os.chdir(script_path)
except:
print("Unable to change current working directory", sys.exc_info())
finally:
print(os.getcwd())
# if '.' is not at the beginning of sys.path, prepend so we can look for modules within CWD
if sys.path[0] != '.':
sys.path.insert(0, '.')
print( sys.path )
# here argv is [''] empty string, doesn't seem to bother us in this use case.
#print( 'argv: ', sys.argv )
# prints the path to JMP, not Python
print('sys.executable: ' + sys.executable)
# build up path prefix to python3 exectable
if 'Darwin' == hostPlatform:
pyPath = os.path.join(sys.exec_prefix, 'bin', 'python3')
elif 'Windows' == hostPlatform:
# Bug - on windows sys.exec_prefix is empty in Python within JMP!
pyPath = None
if sys.exec_prefix:
pyPath = os.path.join(sys.exec_prefix, 'pythonw.exe')
else:
# FixMe: hardcode for now
pyPath = 'C:\\Users\\panels\\AppData\\Local\\Programs\\Python\\Python39\\pythonw.exe'
print ('Python Executable: ' + pyPath)
# Finally down to running something in parallel.
#
# Import the function code that will be executed in parallel.
# JMP is not reporting ImportError or ModuleNotFound errors, the scripting just returns -1
# on an import failure, so wrap it within our own try: except: block
try:
import defs as d
# print( d.f(2) )
# Tell multiprocessing to use the Python executable, not JMP to spawn the workers.
multiprocessing.set_executable(pyPath)
# Run the tasks in parallel - (separate Python instances)
with Pool(5) as p:
print(p.map(d.f, [1,2,3])) # Results in [1,4,9] the square of the numbers
except ModuleNotFoundError as mnf:
print(mnf)
print('Check that the moudule is located on your sys.path:')
print( sys.path )
except ImportError as error:
print(error)
except Exception as ex:
print(ex)
]\");
第三方集成分析應用程序開發實用程序回覆:JMP JSL 中的 Python 並行處理您好@Paul_Nelson ,如何使用嵌入式 Python 在 JMP 18 中實現相同的結果(在背景執行 python 腳本或函數)?
謝謝!
馬泰奧
回覆:JMP JSL 中的 Python 並行處理使用與上面相同的 defs.py 的 Parallel.py 透過 JMP 18 的整合變得更加容易。 JMP 知道 Python 可執行檔的位置,並在jmp導入套件中以「常數」提供,而 JMP 現在知道將腳本的目錄新增至套件搜尋路徑,以便可以匯入本機檔案。
# test_multiprocessing.py
# Author: Paul R. Nelson
# JMP Statistical Discovery LLC
#
# Description: Demonstrate using Python's parallel processing capability.
# The file defs.py contains the code that will execute in parallel.
#
# Contents of defs.py:
#def f(x):
# return x*x
#
import jmp
import multiprocessing
from multiprocessing import Pool
# jmp.PYTHON_EXE is the file path to JMP's Python executable.
print(jmp.PYTHON_EXE)
try:
# defs.py is the code to be run in parallel, located in same directory as this file.
import defs as d
# tell multiprocessing to use Python executable, not spawn JMP for the workers
multiprocessing.set_executable(jmp.PYTHON_EXE)
# square each number in parallel - separate Python instances
with Pool(5) as p:
print(p.map(d.f, [1,2,3])) # result is the square of the numbers
except ModuleNotFoundError as mnf:
print(mnf)
print('Check that the module is located on your sys.path:')
print( sys.path )
except ImportError as error:
print(error)
except Exception as ex:
print(ex)回覆:JMP JSL 中的 Python 並行處理感謝您及時的回覆。您的程式碼按原樣工作正常,但我無法將其擴展到我感興趣的案例。我正在嘗試從 JMP 運行 cmdstanpy 範例方法(貝葉斯 MCMC)。僅以基本形式使用它,它工作得很好,但它會為每個MCMC 鏈打開一個cmd.exe 終端,並保持它們打開直到演算法完成(這可能會相當長的時間),不允許任何交互,也不允許顯示任何日誌,直到結束。
這是基本程式碼:
import cmdstanpy as cs
model = cs.CmdStanModel(stan_file=stan_file)
fit = model.sample(data=datafile, show_progress=True, show_console=True)
fit_table = fit.draws_pd(vars=genVariables+modelParams)
我想讓它在後台運行並在演算法運行時顯示日誌資訊。我根據你的建議嘗試了這個改編:
def sample_chain(chain_id):
return model.sample(data=datafile, chains=1, parallel_chains=1, show_progress=True, show_console=True)
try:
multiprocessing.set_executable(jmp.PYTHON_EXE)
with multiprocessing.Pool(4) as pool:
results = pool.map(sample_chain, range(4))它確實打開了 4 個 cmd.exe 窗口,但它永遠不會完成,而且在 4 個窗口打開時,它仍然沒有將控制權交還給主程式。還有進一步的建議嗎?
謝謝
馬泰奧
回覆:JMP JSL 中的 Python 並行處理我不熟悉這個包,但我想知道參數 show_progress=True 或 show_console=True 是否會產生問題。我還要確保在並行運行的程式碼中,確保它具有異常處理程序,如果發生故障則退出。可能是因為窗戶仍然打開,所以它沒有返回。嘗試在並行腳本的末尾添加一個退出(不是 JMP 中的退出)。
由於 JMP 將 Python 共享庫載入到其進程空間中,因此當 Python 運行時,它可以控制事件循環,而不是 JMP。可以透過使用 jmp.run_jsl('Wait(0);') (例如在循環內)定期將控制權交還給 JMP。這會將控制權短暫傳回給 JMP 以執行 JMP 事件循環。您也可以在 JMP 運行程式碼中進行試驗,但不確定特定位置,因為您不是循環而是並行啟動,並且啟動的平行程式碼無法存取匯入 jmp 功能。我玩了一下你的程式碼,看看是否有什麼突出的地方。
回覆:JMP JSL 中的 Python 並行處理我發現從小而簡單的開始是調試某些東西的最佳方法。在嘗試透過跳到多執行緒來「運行」之前,請從 JMP 中執行 cmdstanpy 來「行走」。
我嘗試了以下方法
import os
from cmdstanpy import CmdStanModel
stan_file = os.path.join('/Users/_me_/Desktop', 'bernoulli.stan')
print(stan_file)
model = CmdStanModel(stan_file=stan_file)
print(model)
print(model.exe_info())
結果
/*:
//:*/
import os
from cmdstanpy import CmdStanModel
stan_file = os.path.join('/Users/_me_/Desktop', 'bernoulli.stan')
print(stan_file)
model = CmdStanModel(stan_file=stan_file)
print(model)
print(model.exe_info())
/*:
/Users/_me_/Desktop/bernoulli.stan
19:01:04 - cmdstanpy - INFO - No CmdStan installation found.
19:01:04 - cmdstanpy - INFO - Cannot determine whether version is before 2.27.
19:01:04 - cmdstanpy - INFO - No CmdStan installation found.
19:01:04 - cmdstanpy - INFO - Cannot determine whether version is before 2.27.
Traceback (most recent call last):
File "”,第 8 行,在檔案“/Users/_me_/Library/Application Support/JMP/Python/3.11/lib/python/site-packages/cmdstanpy/model.py”,第212 行,位於__init__ model_info = self.src_info() ^^^ ^^ ^^^^^^^^^^ 檔案“/Users/_me_/Library/Application Support/JMP/Python/3.11/lib/python/site-packages/cmdstanpy/model.py”,第318 行,在src_info 回傳編譯中.src_info(str(self.stan_file), self._compiler_options) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 檔「/Users/_me_/Library/Application Support/JMP/ Python/3.11/lib/python /site-packages/cmdstanpy/compilation.py”,第354 行,在src_info [os.path.join(cmdstan_path(), 'bin', 'stanc' + EXTENSION)] ^^^ ^^^^^^^^ ^^^ 檔案“/Users/_me_/Library/Application Support/JMP/Python/3.11/lib/python/site-packages/cmdstanpy/utils/cmdstan.py”,第170 行,在cmdstan_path 中引發ValueError( ValueError: No CmdStan找到安裝,運行命令“install_cmdstan”或(重新)激活您的 conda 環境!
了解這個範例要求 JMP 做什麼。 CmdStan 是一個命令列程序,cmdstanpy 套件是一個 Python 包裝器。因此,它看起來就像 Python 子進程一樣,呼叫作業系統、啟動命令列程式並監視 shell 輸出。 JMP 和 JMP jpip 包裝器安裝的套件最終位於 JMP 的私有 site-packages 目錄中。我們沒有運行 Conda init,因為這不是 Conda 發行版,而且 install_cmdstan 軟體包可能會也可能不會配置足以在 JMP 虛擬環境中運行的環境。通常,Conda 或 venv 啟動會為套件路徑和目錄路徑設定環境變數。 install_cmdstan 可能會做同樣的事情。然而,所有這些都假設剛剛配置的相同 shell 環境將用於運行將導入 cmdstan 的 python 可執行檔。這些假設不成立,JMP 必須自行設定這些路徑,以便能夠將 JMP 套件環境與系統上的其他 Python 實例隔離。因此,雖然 JMP 可以導入並運行 cmdstanpy 包,但該包本身正在查找 cmdstan 命令行可執行文件,而不是找到它。然後嘗試添加多處理,實際上是要求 JMP 的 Python 啟動運行 Python 腳本的 jmp.PYTHON_EXE 副本,然後依次嘗試啟動子進程來運行執行任務的命令列程式...
我最初的範例介紹了創建純 Python 函數的用例,該函數將透過啟動多個 Python 實例並行運行。您嘗試執行的內容會在命令列執行檔上新增 Python 包裝器的複雜性以及嵌套 shell 環境的問題。
我認為當前形式不支援您嘗試的用例。在嘗試使用 JMP 之前,您應該在 JMP 之外嘗試相同的多處理。如果您能讓它在那裡發揮作用,您可能會在 JMP 中取得成功。 (只需將 jmp.PYTHON_EXE 替換為 python.exe 的完整路徑,並且不要導入 jmp)。
我知道基本純Python 的平行腳本可以工作。我相信即使是不依賴共享庫導入的導入Python包也應該在並行腳本中工作......(但我還沒有測試過)。它可能需要將 JMP 環境的 site.USER_BASE 傳遞到並行 jmp.PYTHON_EXE 並正確設定其環境以符合 JMP 本身設定的相同目錄環境。
如果您在探索時,請將您的發現發布回社區。
JMP 受到以下事實的限制:Python 在 JMP 的主事件循環上運行。 JMP 和多執行緒 Python 的可能解決方案之一是啟動獨立的 Python 子解釋器。如果他們解決了刪除 Python 全域解釋器鎖定 (GIL) 的所有問題,則需要 Python 3.13。我正在研究這一點,但 Python 3.13 不會出現在 JMP 18 中。JMP 18 在其整個生命週期中將保留在 Python 3.11.x 中。根據 Python 3.13 的時間表和穩定性,此類功能可能會進入 JMP 19 或 20。
回覆:JMP JSL 中的 Python 並行處理感謝您的解釋。我將進一步研究並在有新發現時回來。
馬泰奧