import arcpy import multiprocessing def simpleFunction(arg): return arg*3 if __name__ == "__main__": arcpy.AddMessage(" Multiprocessing test...") jobs = [] pool = multiprocessing.Pool() for i in range(10): job = pool.apply_async(simpleFunction, (i,)) jobs.append(job) for job in jobs: # collect results from the job server print job.get() del job, jobs arcpy.AddMessage(" Complete") print "Complete"
PYTHON_EXE = os.path.join(sys.exec_prefix, 'pythonw.exe') #if you use python.exe you get a command window multiprocessing.set_executable(PYTHON_EXE)
TypeError: can't pickle module objects
env = dict(os.environ) for x in list(env.iterkeys()): if x.startswith('GP_'): del env hp, ht, pid, tid = _subprocess.CreateProcess( _python_exe, cmd, None, None, 1, 0, env, None, None )
#Import some modules import os, random, subprocess, sys, time #Process: Define a function that will launch the processes and update the job dictionary accordingly def launchProcess(jobId): global jobDict #make this a global so the function can read it inputVar1 = jobDict[jobId][2][0] #Input variables are being read from the job dictionary inputVar2 = jobDict[jobId][2][1] jobDict[jobId][4] = subprocess.Popen([jobDict[jobId][0], jobDict[jobId][1], str(inputVar1), str(inputVar2)], shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) jobDict[jobId][3] = "IN_PROGRESS" #Indicate the job is 'IN_PROGRESS' #Determine how many processes to run concurrently numberOfProcessorsToUse = 3 #The number of processors you (the user) wants to use if numberOfProcessorsToUse > int(os.environ.get("NUMBER_OF_PROCESSORS")): numberOfProcessorsToUse = int(os.environ.get("NUMBER_OF_PROCESSORS")) #Populate a "job dictionary" to keep track of all the subprocess jobs and all their various inputs and outputs childScriptPath = r"C:\csny490\simple_subprocess_child.py" jobDict = {} for jobId in range (1,21): #this loop is just showing how you can populate the jobId's and their input variables - in this example theere will be 20 jobs inputVar1 = random.randrange(1,6) #this variable tells each subrocess how many seconds it will "sleep" for - between 1 and 5 seconds inputVar2 = random.randrange(2,9) #in the child proces script, if inputVar2 > 6 it will throw an exception, and the subprocess will be 'FAILED' - I simply did this to create the possibility of failed subprocesses #Format of jobDict[jobId] = [applicationExePath, childScriptPath, [list of input varibles], status {"NOT_STARTED", "IN_PROGRESS", "SUCCEEDED", "FAILED"}, subprocess.Popen object] jobDict[jobId] = [os.path.join(sys.prefix, "python.exe"), childScriptPath, [inputVar1, inputVar2], "NOT_STARTED", None] #Process: Kick off some processes, monitor the processes, and start new processes as others finish kickOffFlag = False #Indicate the process kick off has not yet occured while len([i for i in jobDict if jobDict[3] in ("SUCCEEDED","FAILED")]) < len(jobDict): if kickOffFlag == False: while len([i for i in jobDict if jobDict[3] != 'NOT_STARTED']) < numberOfProcessorsToUse: launchProcess([i for i in jobDict if jobDict[3] == 'NOT_STARTED'][0]) #Feed the appropriate jobId to the launchProcess() function kickOffFlag = True #Set the flag as True once we have done the initial kickoff for jobId in [i for i in jobDict if jobDict[3] == 'IN_PROGRESS' and jobDict[4].poll() != None]: #if an subprocess is listed as 'IN_PROGRESS' and polls as 0 (i.e. "done" but success or failure unknown) if jobDict[jobId][4].returncode == 0: #return code of 0 indicates success (no sys.exit(1) command encountered in the child process jobDict[jobId][3] = "SUCCEEDED" if jobDict[jobId][4].returncode > 0: #return code of 1 (or another integer value) indicates failure (a sys.exit(1) command was encountered in the child process) jobDict[jobId][3] = "FAILED" if len([i for i in jobDict if jobDict[3] == 'NOT_STARTED']) > 0: #if there are still jobs = 'NOT_STARTED', launch the next one in line launchProcess([i for i in jobDict if jobDict[3] == 'NOT_STARTED'][0]) print "--------------------------------------" for jobId in jobDict: print "Job ID " + str(jobId) + " = " + str(jobDict[jobId][3])
try: import sys, time var1 = int(sys.argv[1]) var2 = int(sys.argv[2]) print "Nothing to do, so sleeping for " + str(var1) + " seconds..." time.sleep(var1) if var2 > 6: test = "oh crap" + 1 #concatenating a string an int here is intended to throw an exception on purpose to demonstrate a failure print "Epic Success!" #Note: If script runs through without error, return code will be, by default, 0 - indicating success except: print "Epic Fail!" #Note: You can gather/parse all these print messages in the parent scrip using jobDict[jobId][4].communicate() sys.exit(1) #Note: If script fails, return code is 1 - indicating failure - you can set the return code to be > 0 if you want...
As soon as I apply more than one core, the processing is not stable any more
# Description # ----------- # This master script regulates the execution of the script run_union_slave.py (for each tile) # Author: Chris Snyder, WA Department of Natural Resources, chris.snyder(at)wadnr.gov import sys, string, os, shutil, time, traceback, glob try: #Defines some functions def showPyMessage(): try: print >> open(logFile, 'a'), str(time.ctime()) + " - " + str(message) print str(time.ctime()) + " - " + str(message) except: pass def launchProcess(tileNumber): global message global numberOfProcessors message = "Processing tile #" + str(tileNumber); showPyMessage() #Added this to address bug with indexTiles.shp used in the overlay tools in v9.2+ (can't run more than 1 overlay process at once) newTempDir = r"C:\temp\gptmpenvr_" + time.strftime('%Y%m%d%H%M%S') os.mkdir(newTempDir) os.environ["TEMP"] = newTempDir os.environ["TMP"] = newTempDir message = "Set TEMP and TMP variables to " + newTempDir; showPyMessage() #End bug fix... parameterList = [pythonExePath, slaveScript, root, str(tileNumber), yyyymmdd] if tileNumber <= indxPolyFeatureCount: if tileNumber == indxPolyFeatureCount or numberOfProcessors == 1: message = "Waiting for tile #" + str(indxPolyFeatureCount) + " to finish..."; showPyMessage() os.spawnv(os.P_WAIT, pythonExePath, parameterList) else: os.spawnv(os.P_NOWAIT, pythonExePath, parameterList) time.sleep(1); message = "Waiting a few seconds"; showPyMessage() #Specifies the root variable, makes the logFile variable, and does some error checking... dateTimeStamp = time.strftime('%Y%m%d%H%M%S') root = sys.argv[1] #r"E:\1junk\overlay_20060531" yyyymmdd = sys.argv[2] #this is passed from the master script since the tiles may be processed over a 2+ day period if os.path.exists(root)== False: print "Specified root directory: " + root + " does not exist... Bailing out!" sys.exit() scriptName = sys.argv[0].split("\\")[len(sys.argv[0].split("\\")) - 1][0:-3] #Gets the name of the script without the .py extension logFile = root + "\\log_files\\" + scriptName + "_" + dateTimeStamp[:8] + ".log" #Creates the logFile variable if os.path.exists(root + "\\log_files") == False: #Makes sure log_files exists os.mkdir(root + "\\log_files") if os.path.exists(logFile)== True: os.remove(logFile) message = "Deleting log file with the same name and datestamp... Recreating " + logFile; showPyMessage() workspaceDir = root + "\\index_tiles" if os.path.exists(workspaceDir)== True: message = "Overlay directory: " + workspaceDir + " already exist... Deleting and recreating " + workspaceDir; showPyMessage() shutil.rmtree(workspaceDir) else: message = "Creating index tiles directory: " + workspaceDir; showPyMessage() os.mkdir(workspaceDir) #Process: Finds python.exe pythonExePath = "" for path in sys.path: if os.path.exists(os.path.join(path, "python.exe")) == True: pythonExePath = os.path.join(path, "python.exe") if pythonExePath != "": message = "Python.exe file located at " + pythonExePath; showPyMessage() else: message = "ERROR: Python executable not found! Exiting script...."; showPyError(); sys.exit() #Determines the number of processors on the machine... numberOfProcessors = int(os.environ.get("NUMBER_OF_PROCESSORS")) maxNumberOfProcessors = 3 #The maximum number of processors you want to use if numberOfProcessors > maxNumberOfProcessors: numberOfProcessors = maxNumberOfProcessors #Determines the number of tiles in the index layer (tile numbers are assumed to be sequential) #Note: I didn't use gp.getcount so that this script wouldn't use the gp at all (and eat up more memory) indxPolyCountFile = glob.glob(root + "\\log_files\\therearethismanytiles_*.txt") if len(indxPolyCountFile) == 0: message = "Can't find index polygon count .txt file in " + root + "\\log_files" + "! Exiting script..."; showPyMessage() sys.exit() indxPolyFeatureCount = int(indxPolyCountFile[0].split("_")[-1][0:-4]) if indxPolyFeatureCount == 0: message = "Index polygon count is 0! Exiting script..."; showPyMessage() sys.exit() tileNumber = 1 numberOfProcesses = 0 slaveScript = r"\\Snarf\am\div_lm\ds\gis\ldo\current_scripts\run_union_slave_v93.py" tilesThatAreProcessingList = [] tilesThatFinishedList = [] tilesThatBombedList = [] #Process: This while loop initialy launches <numberOfProcessors> instances of the slave script while numberOfProcesses < numberOfProcessors: numberOfProcesses = numberOfProcesses + 1 tilesThatAreProcessingList.append(tileNumber) launchProcess(tileNumber) tileNumber = tileNumber + 1 #Process: This while loop checks for tiles that finished or bombed, if there are, new slave scripts are launched while len(tilesThatBombedList) + len(tilesThatFinishedList) < indxPolyFeatureCount: isDoneList = glob.glob(root + "\\log_files\\" + slaveScript.split("\\")[len(slaveScript.split("\\")) - 1][0:-3] + "_isalldone_*") #makes a list of the .txt file created by the slave script doneBombedList = glob.glob(root + "\\log_files\\" + slaveScript.split("\\")[len(slaveScript.split("\\")) - 1][0:-3] + "_bombed_*") if len(isDoneList) == 0 and len(doneBombedList) == 0: #if there are no .txt files, wait for x number of seconds time.sleep(5) else: #else if there are .txt files... if len(isDoneList) > 0: #if there are tiles that are "_isalldone_" for isDoneItem in isDoneList: #for each .txt file indicating completion... tileThatIsDone = int(isDoneItem[isDoneItem.rfind("_") + 1:isDoneItem.rfind(".")]) os.remove(root + "\\log_files\\" + slaveScript.split("\\")[len(slaveScript.split("\\")) - 1][0:-3] + "_isalldone_" + str(tileThatIsDone) + ".txt") message = "Tile #" + str(tileThatIsDone) + " is done!!!"; showPyMessage() tilesThatFinishedList.append(tileThatIsDone) tilesThatAreProcessingList.remove(tileThatIsDone) tilesThatAreProcessingList.append(tileNumber) launchProcess(tileNumber) tileNumber = tileNumber + 1 time.sleep(5) if len(doneBombedList) > 0: #if there are tiles that "_bombed_" for doneBombedItem in doneBombedList: #for each .txt file indicating failure... tileThatBombed = int(doneBombedItem[doneBombedItem.rfind("_") + 1:doneBombedItem.rfind(".")]) os.remove(root + "\\log_files\\" + slaveScript.split("\\")[len(slaveScript.split("\\")) - 1][0:-3] + "_bombed_" + str(tileThatBombed) + ".txt") message = "Tile #" + str(tileThatBombed) + " bombed!!!"; showPyMessage() tilesThatBombedList.append(tileThatBombed) tilesThatAreProcessingList.remove(tileThatBombed) tilesThatAreProcessingList.append(tileNumber) launchProcess(tileNumber) tileNumber = tileNumber + 1 time.sleep(5) message = "Tiles that are currently processing: " + str(tilesThatAreProcessingList); showPyMessage() message = "Tiles that are done: " + str(tilesThatFinishedList); showPyMessage() message = "Tiles that bombed: " + str(tilesThatBombedList); showPyMessage() time.sleep(5) if len(tilesThatBombedList) > 0: message = "ERROR - these tiles failed to process: " + str(tilesThatBombedList); showPyMessage() sys.exit() message = "ALL DONE!"; showPyMessage() print >> open(root + "\\log_files\\" + scriptName + "_isalldone.txt", 'a'), scriptName + "_isalldone.txt" except: message = "\n*** LAST GEOPROCESSOR MESSAGE (may not be source of the error)***"; showPyMessage() message = "\n*** PYTHON ERRORS *** "; showPyMessage() message = "Python Traceback Info: " + traceback.format_tb(sys.exc_info()[2])[0]; showPyMessage() message = "Python Error Info: " + str(sys.exc_type)+ ": " + str(sys.exc_value) + "\n"; showPyMessage()
''' Step 4 of 4: Example of using Multiprocessing/Parallel Python with Arcpy... Can be run either: 1. from the command line/a Python IDE (adjust paths to feature classes, as necessary) 2. as a Script tool within ArcGIS (ensure 'Run Ptyhon script in Process' is NOT checked when importing) The Parallel Python library must be installed before it can be used. ''' import arcpy import multiprocessing import time try: import pp forceMP = False except ImportError: forceMP = True def performCalculation(points_fC, polygons_fC, searchDist, typeList, calcPlatform_input=None): '''performCalculation(pointsFeatureClass, polygonsFeatureClass, searchDistance, typeList, calcPlatform) All inputs are specific. calcPlatform is either 'mp', to use the inbuilt Multiprocessing library, or 'pp', to use Parallel Python (if it exists, otherwise defaults to mp) ''' # --------------------------------------------------------------------------- ## Pre-calculation checks # --------------------------------------------------------------------------- # Check calculation platform is valid, or resort to default... defaultCalcTpye = 'mp' calcTypeExplainDict = {'pp':'Parallel Python', 'mp':'Multiprocessing'} if forceMP: # unable to import Parallel Python library (it has to be installed seperately) arcpy.AddMessage(" WARNING: Cannot load Parallel Python library, forcing Multiprocessing library...") calcPlatform = 'mp' elif (calcPlatform_input not in ['mp', 'pp']) or (calcPlatform_input == None): # Invalid/no input, resort to default arcpy.AddMessage(" WARNING: Input calculation platform '%s' invalid; should be either 'mp' or 'pp'. Defaulting to %s..." % (calcPlatform_input, calcTypeExplainDict[defaultCalcTpye])) calcPlatform = defaultCalcTpye else: calcPlatform = calcPlatform_input # --------------------------------------------------------------------------- ## Data extraction (parallel execution) # --------------------------------------------------------------------------- searchDist = int(searchDist) # convert search distance to integer... # check all datasets are OK; if not, provide some useful output and terminate valid_points = arcpy.Exists(points_fC) arcpy.AddMessage("Points Feature Class: "+points_fC) valid_polygons = arcpy.Exists(polygons_fC) arcpy.AddMessage("Polygons Feature Class: "+polygons_fC) dataCheck = valid_points & valid_polygons if not dataCheck: if not valid_points: arcpy.AddError("Points database or feature class, %s, is invalid..." % (points_fC)) if not valid_polygons: arcpy.AddError("Polygons database or feature class, %s, is invalid..." % (polygons_fC)) else: # Inputs are OK, start calculation... for type in typeList: # add fields to Points file arcpy.AddField_management(points_fC, "polygon_type%s_Sum" % type, "DOUBLE") arcpy.CalculateField_management(points_fC, "polygon_type%s_Sum" % type, 0, "PYTHON") arcpy.AddField_management(points_fC, "polygon_type%s_Count" % type, "DOUBLE") arcpy.CalculateField_management(points_fC, "polygon_type%s_Count" % type, 0, "PYTHON") arcpy.AddMessage(" Added polygon_type%s_Sum and polygon_type%s_Count fields to Points." % (type,type)) pointsDataDict = {} # dictionary: pointsDataDict[pointID][Type]=[sum of Polygon type weightings, count of Ploygons of type] jobs = [] # jobs are added to the list, then processed in parallel by the available workers (CPUs) if calcPlatform == 'mp': arcpy.AddMessage(" Utilising Python Multiprocessing library:") pool = multiprocessing.Pool() # initate the processing pool - use all available resources # pool = multiprocessing.Pool(2) # Example: limit the processing pool to 2 CPUs... for type in typeList: # For this specific example arcpy.AddMessage(" Passing %s to processing pool..." % type) jobs.append(pool.apply_async(findPolygons, (points_fC, polygons_fC, type, searchDist))) # send jobs to be processed for job in jobs: # collect results from the job server (waits until all the processing is complete) if len(pointsDataDict.keys()) == 0: # first output becomes the new dictionary pointsDataDict.update(job.get()) else: for key in job.get(): # later outputs should be added per key... pointsDataDict[key].update(job.get()[key]) del jobs elif calcPlatform == 'pp': ppservers=() job_server = pp.Server(ppservers=ppservers) # initate the job server - use all available resources # job_server = pp.Server(2, ppservers=ppservers) # Example: limit the processing pool to 2 CPUs... arcpy.AddMessage(" Utilising Parallel Python library:") for type in typeList: # For this specific example, it would only initate three processes anyway... arcpy.AddMessage(" Passing %s to processing pool..." % type) jobs.append(job_server.submit(findPolygons, (points_fC, polygons_fC, type, searchDist), (), ("arcpy",))) # send jobs to be processed for job in jobs: # collect results from the job server (waits until all the processing is complete) if len(pointsDataDict.keys()) == 0: # first output becomes the new dictioanry pointsDataDict.update(job()) else: for key in job(): pointsDataDict[key].update(job()[key]) # later outputs should be added per key... del jobs # --------------------------------------------------------------------------- ## Writing data back to points file # --------------------------------------------------------------------------- arcpy.AddMessage(" Values extracted; writing results to Points...") pointsRowList = arcpy.UpdateCursor(points_fC) for pointsRow in pointsRowList: # write the values for every point pointID = pointsRow.getValue("PointID") for type in typeList: pointsRow.setValue("polygon_type%s_Sum" % type, pointsRow.getValue("polygon_type%s_Sum" % type) + pointsDataDict[pointID][type][0]) pointsRow.setValue("polygon_type%s_Count" % type, pointsRow.getValue("polygon_type%s_Count" % type) + pointsDataDict[pointID][type][1]) pointsRowList.updateRow(pointsRow) del pointsRow del pointsRowList # just make sure any locks are cleared... del calcPlatform, calcPlatform_input, calcTypeExplainDict, dataCheck, defaultCalcTpye, job, key, pointID, pointsDataDict, points_fC, polygons_fC, type, valid_points, valid_polygons, searchDist, typeList def findPolygons(points_FC, polygons_FC, Type, search_dist): funcTempDict = {} arcpy.MakeFeatureLayer_management(polygons_FC, '%s_%s' % (polygons_FC, Type)) arcpy.MakeFeatureLayer_management(points_FC, '%s_%s' % (points_FC, Type)) pointsRowList = arcpy.SearchCursor('%s_%s' % (points_FC, Type)) for pointRow in pointsRowList: # for every origin pointID = pointRow.getValue("PointID") try: funcTempDict[pointID][Type] = [0,0] except KeyError: # first time within row funcTempDict[pointID] = {} funcTempDict[pointID][Type] = [0,0] arcpy.SelectLayerByAttribute_management('%s_%s' % (points_FC, Type), 'NEW_SELECTION', '"PointID" = \'%s\'' % pointID) arcpy.SelectLayerByLocation_management('%s_%s' % (polygons_FC, Type), 'INTERSECT', '%s_%s' % (points_FC, Type), search_dist, 'NEW_SELECTION') arcpy.SelectLayerByAttribute_management('%s_%s' % (polygons_FC, Type), 'SUBSET_SELECTION', '"polyType" = %s' % Type) polygonsRowList = arcpy.SearchCursor('%s_%s' % (polygons_FC, Type)) for polygonsRow in polygonsRowList: funcTempDict[pointID][Type][0] += polygonsRow.getValue("polyWeighting") funcTempDict[pointID][Type][1] += 1 return funcTempDict if __name__ == '__main__': # Read the parameter values: # 1: points feature class (string to location, e.g. c:\GIS\Data\points.gdb\points01) # 2: polygons feature class (string to location) # 3: search distance for polygons, integer, e.g 500 # 4: calculation type ('mp' to use Multiprocessing library, 'pp' to use Parallel Python library (if available, otherwise defaults to mp)) pointsFC = arcpy.GetParameterAsText(0) # required polygonsFC = arcpy.GetParameterAsText(1) # required search_Distance = arcpy.GetParameterAsText(2) # required calcType = arcpy.GetParameterAsText(3) # optional (will default to MP) t_start = time.clock() type_list = [1,2,3,4,5,6] # polygon types to search for... # run calculation if '' in [pointsFC, polygonsFC, search_Distance]:# if not running from Arc, the input parameters all come out as '' pointsFC = "c:\\Work\\GIS\\Data\\Points.gdb\\points01" polygonsFC = "c:\\Work\\GIS\\Data\\Polygons.gdb\\polygons01" search_Distance = 1000 performCalculation(pointsFC, polygonsFC, search_Distance, type_list) else: performCalculation(pointsFC, polygonsFC, search_Distance, type_list, calcType) arcpy.AddMessage(" ... complete in %s seconds." % (time.clock() - t_start))
Stacy, I'd love to see some samples. Ocassionally, I have the need to write large number-crunching stuff in Python (traversing large arrays or dictionary-type structures), and I'd love to explore ways to speed it up the processing. Youre' right, it's pretty hard to get practical examples of using some of this stuff!
In this case I am able to split the data beforehand, then define a python module that does the processing on the section of data that is passed to it; it's these things that run in parallel. Let me know if you want more info - I can prepare a sample code for you - there's not much around on the internet for this kind of thing!
Seeing that Arcpy isn't a native Python object, would there be any benifit (or would it even work) to send an arcpy.Whatever commands to Parallel Python, Pickle, etc.?The only way I've ever gotten a "parallel" process to actually work with gp/arcpy objects is using os.spawnv and/or subprocess.
Přihlášení členové mohou přispívat, sledovat aktualizace a další. Jste tu noví? Zaregistrujte si bezplatný účet.
Find useful guides, FAQs, and documents to help you navigate and make the most of Esri Community.