はじめに
multiprocessing モジュール が含まれる Python 2.6 がArcGIS 製品に入ってきたのは、ArcGIS 10.0 の登場がはじめでした(ArcGIS 10.0 国内リリース:2010年10月22日)。それ以降、米国 Esri の配信している Blog でも Python Multiprocessing – Approaches and Considerations(2011年8月)、Multiprocessing with ArcGIS – Approaches and Considerations (Part 1):2012年9月としてマルチプロセス処理について記事で取り上げられたのをはじめ、近年でも世界中から多くの開発者が集まるEsri Developer Summit で Parallel Python: Multiprocessing with ArcPy:2017年3月 の動画、Vector and Raster Multiprocessing with ArcPy:2018年3月 の動画 としてテクニカル セッションとして取り上げられています。
マルチプロセス処理
ArcGIS Pro 動作環境 に見られるような高スペックのハードウェア(64-bit OSが標準、CPU 4コアを推奨、メモリも最適 16GB以上)が普及してきた一方で、分析や処理に使われるデータ量も飛躍的に増加しており、効率的な分析や処理が必要な状況は今も続いています。
そのような中で、今回は、ArcGIS プラットフォームの拡張手段として定着している ArcPy とPython でのマルチプロセス処理をとりあげ、実装の要点となる箇所とコーディングパターンをサンプルを交えつつ紹介したいと思います。
さまざまな処理での実際のコーディングは、関連情報に示したものを確認いただきつつ、実際にコードを書いて試してみることをお勧めしますが、本記事を参照して並列処理実装のポイントを掴んでいただき、現在のワークフローに上手く取り入れる一助となればと思います。
ベストプラクティス
関連情報に示した各種資料では、ArcPy とPython を使用したマルチプロセス処理のベストプラクティスとして、次のようなことが言われています。
- テンポラリの結果を保持するためには、高速に動作するため、in_memory ワークスペースを使うのを推奨。
- ファイル ジオデータベース(参考:ジオデータベースの概要)や、GRID ラスターへの書き込みは避ける。
これらのデータ形式はスキーマロックや同期の問題を引き起こすことがあり、これはファイル ジオデータベースとGRIDラスターが同時書き込みをサポートしていないためで、つまり、一度に1つのプロセスだけが書き込むことができる仕様であるということです。ArcGIS で開いていたフィーチャ クラスを、ArcPy で変更しようとすると、この問題が発生していたのは目にしたことがあるかと思います。さらに、ファイル ジオデータベースを所持していて、多くのフィーチャクラスに対して書き込みを行おうとすると、その問題は拡大されます。すべてのフィーチャ クラスが独立して存在していたとしても、ファイル ジオデータベース内のフィーチャクラスに書き込むことができるのは一度に1つのフィーチャ クラスです。- スクリプトの実行には 64-bit のPython 実行環境を使用する。
ArcGIS Pro 1.4 以上、もしくはArcGIS Server 10.5 以上、またはBackground Geoprocessing (64-bit) をインストールした ArcGIS Desktop 10.5 以上の 64-bit Python 実行環境を使用。64-bit の実行環境は32-bitのメモリ制限(4GB)で失敗するような、大きなデータに対する問題を解決できる可能性があるからです。 特に実装にあたっては、2つ目に挙げているファイル ジオデータベースの使用には注意する必要があります。少なくとも各プロセスでの処理を記載するワーカー関数での書込み先として同じファイル ジオデータベースを使用しないようにすべきですが、マスター関数でそれらを使用して、多数のシェープ ファイルや、個々のファイル ジオデータベースを単一ソースにマージするのに使用することは可能です。
*複数のプロセスからの書込み先として、ワーカー関数でのエンタープライズ ジオデータベースを書込み先として使用するのも、一つの方法です。
マルチプロセス処理のサンプル
今回のサンプルは、47都道県別のフォルダ下に複数のシェープファイルが入っており、それらを47都道府県別のファイル ジオデータベースを作成後、それぞれフィーチャクラスに変換する実装サンプルコードです。 実装する際のポイントは、上記のベストプラクティスで書いているように、ファイル ジオデータベースへの書込み時にロックされるので、「1都道府県フォルダのシェープファイルは1都道府県ファイル ジオデータベースでのフィーチャクラスへの変換となるようにコードを書くこと」です。
実際にコーディングパターンとして見ていきましょう。
1) ワーカー関数:batch_convert
コーディングパターンとしては、まずはワーカー関数を通常の ArcPyでの処理と同様に書きます。このときに注意するべきなのは、ベストプラクティスで書いたように、このワーカー関数での処理がプロセスでの処理に相当するので、複数プロセス(複数のワーカー関数)から書込み先としてアクセスする必要がある場合はエンタープライズ ジオデータベースを利用するか、同じファイル ジオデータベースを使用しないようにすることです。
def batch_convert(inws, outws):
'''
1プロセスで実行する処理(ワーカー関数):
ファイル ジオデータベースへの書込みは複数プロセスでできないため、
・1都道府県のフォルダ下の複数シェープ ファイルを
・1都道府県のファイル ジオデータベース下の複数フィーチャ クラスに変換
するように記載
'''
if not arcpy.Exists(outws):
outfolder=os.path.dirname(outws)
foldername=os.path.basename(outws)
arcpy.CreateFileGDB_management(outfolder,foldername,"CURRENT")
arcpy.env.workspace = inws
fcs = arcpy.ListFeatureClasses()
for fc in fcs:
l=len(fc)
infc=os.path.splitext(fc)[0]
newfc = fc[:1]+fc[1:l-4].zfill(4)
if arcpy.Exists(os.path.join(outws,newfc)):
outfc=os.path.join(outws,newfc)
arcpy.Append_management(fc,outfc)
else:
arcpy.FeatureClassToFeatureClass_conversion(fc,outws,newfc)
del fcs
return " 変換済:{0}".format(outws)
2) ワーカー関数のラッパー:multi_run_batch_convert
マルチプロセスからワーカー関数を指定する際、ワーカー関数の引数が単独の場合は不要ですが、サンプルのように複数の引数を渡す必要がある場合は、ラッパー関数を用意すると便利です。
def multi_run_batch_convert(args):
'''
batch_convert の wrapper:
複数の引数を実行処理に渡すためのラッパー
'''
return batch_convert(*args)
3) マルチプロセスの処理:exec_batch_convert
ワーカー関数へ渡す2つの引数(inws, outws)を params にリスト化していれます。
その後、
pool = multiprocessing.Pool(cpu_cnt) で確保した pool に
results = pool.map(multi_run_batch_convert , params) を指定します。
第1引数:処理するワーカー関数名(今回の場合はラッパー関数) multi_run_batch_convert
第2引数:リストかしたパラメータ params
def exec_batch_convert(infolder,outfolder):
'''
マルチプロセスでの処理(マスター関数):
'''
try:
start=datetime.datetime.now()
print "-- Strat: Multiprocess_ShapefileToFeatureClassWithRename --:",start
cpu_cnt=multiprocessing.cpu_count()
arcpy.env.workspace = infolder
inwss = arcpy.ListWorkspaces("*","Folder")
params=[]
for inws in inwss:
param1=inws
gdbname="{0}.gdb".format(os.path.basename(inws))
param2=os.path.join(outfolder,gdbname)
params.append((param1,param2))
if len(inwss) < cpu_cnt:
cpu_cnt=len(inwss)
pool = multiprocessing.Pool(cpu_cnt)
results=pool.map(multi_run_batch_convert,params)
pool.close()
pool.join()
for r in results:
print(r)
fin=datetime.datetime.now()
print "-- Finish: Multiprocess_ShapefileToFeatureClassWithRename --:",fin
print " Elapsed time:", fin-start
except:
print traceback.format_exc(sys.exc_info()[2])
完成版のサンプルコード
上記までのコーディングパターンに従って実装したPythonスクリプト(Multiprocess_Sample.py)の infolder, outfolder をご自分の環境のものに変更してPython の実行環境から実行すれば、マルチプロセスでデータ変換が行われます。
import arcpy,os
import sys
import multiprocessing
import datetime
import traceback
reload(sys)
sys.setdefaultencoding('cp932')
def multi_run_batch_convert(args):
'''
batch_convert の wrapper:
複数の引数を実行処理に渡すためのラッパー
'''
return batch_convert(*args)
def batch_convert(inws, outws):
'''
1プロセスで実行する処理(ワーカー関数):
ファイル ジオデータベースへの書込みは複数プロセスでできないため、
・1都道府県のフォルダ下の複数シェープ ファイルを
・1都道府県のファイル ジオデータベース下の複数フィーチャ クラスに変換
するように記載
'''
if not arcpy.Exists(outws):
outfolder=os.path.dirname(outws)
foldername=os.path.basename(outws)
arcpy.CreateFileGDB_management(outfolder,foldername,"CURRENT")
arcpy.env.workspace = inws
fcs = arcpy.ListFeatureClasses()
for fc in fcs:
l=len(fc)
infc=os.path.splitext(fc)[0]
newfc = fc[:1]+fc[1:l-4].zfill(4)
if arcpy.Exists(os.path.join(outws,newfc)):
outfc=os.path.join(outws,newfc)
arcpy.Append_management(fc,outfc)
else:
arcpy.FeatureClassToFeatureClass_conversion(fc,outws,newfc)
del fcs
return " 変換済:{0}".format(outws)
def exec_batch_convert(infolder,outfolder):
'''
マルチプロセスでの処理(マスター関数):
'''
try:
start=datetime.datetime.now()
print "-- Strat: Multiprocess_ShapefileToFeatureClassWithRename --:",start
cpu_cnt=multiprocessing.cpu_count()
arcpy.env.workspace = infolder
inwss = arcpy.ListWorkspaces("*","Folder")
params=[]
for inws in inwss:
param1=inws
gdbname="{0}.gdb".format(os.path.basename(inws))
param2=os.path.join(outfolder,gdbname)
params.append((param1,param2))
if len(inwss) < cpu_cnt:
cpu_cnt=len(inwss)
pool = multiprocessing.Pool(cpu_cnt)
results=pool.map(multi_run_batch_convert,params)
pool.close()
pool.join()
for r in results:
print(r)
fin=datetime.datetime.now()
print "-- Finish: Multiprocess_ShapefileToFeatureClassWithRename --:",fin
print " Elapsed time:", fin-start
except:
print traceback.format_exc(sys.exc_info()[2])
def setup_batch_convert():
'''
コマンドプロンプトからの実行パラメータを設定:
都道府県別のシェープ ファイルが入った元フォルダ :infolder
例)
|-Shapefiles
|- 01_北海道
|-P101_CITY.shp
|-L102_RIVER.shp
・・・・・
|- 02_青森県
|- 03_岩手県
・・・・・
都道府県別のファイル ジオデータベースの作成先フォルダ :outfolder
例)
|-Filegdbs
|- 01_北海道.gdb
|-P0101_CITY
|-L0102_RIVER
・・・・・
|- 02_青森県.gdb
|- 03_岩手県.gdb
・・・・・
'''
infolder=r"E:\DATA\Shapefiles"
outfolder=r"E:\DATA\Filegdbs"
exec_batch_convert(infolder,outfolder)
if __name__ == '__main__':
setup_batch_convert()
64-bit 環境でのPython スクリプトの実行例
なお、ベストプラクティスで書いたように、64-bitの Python環境から実行してください。Pythonスクリプトの実行例としてArcGIS Desktop、ArcGIS Proでの例を載せておきます。
実行例)ArcGIS Desktop 10.6.1
>C:\Python27\ArcGISx6410.6\python.exe Multiprocess_Sample.py
実行例)ArcGIS Pro 2.2
>"c:\Program Files\ArcGIS\Pro\bin\Python\scripts\propy.bat" Multiprocess_Sample.py
参考となるブログやWebサイト
・ペンシルバニア州立大学の John A. Dutton eラーニング研究所のマテリアル
参考となる動画