6 votos

¿Optimizar y acelerar el código de ArcPy?

De nuevo mis líneas de escorrentía superficial. Hice un script, resolviendo el tema cuando las líneas se cruzan con otras líneas. Algo así como la generalización. Como en la imagen de abajo. El problema es que tengo que resolver 30 millones. de estas líneas. Lo he probado con 20 000 líneas y el tiempo necesario para ejecutarlo fue de 2h 30 min. Así que, aproximadamente, si lo hago con 30 millones de líneas, el tiempo de ejecución es de 5 a 6 meses.

Soy un principiante en Python.

¿Cómo puedo ajustar mi script para que se ejecute más rápido?

Estoy usando Python 2.7.3, ArcGIS 10.2 y Windows 7. Pongo mi script aquí:

enter image description here '

#IMPORT MODULES, SETTINGS    
# ##############################################################################
import arcpy, datetime
start = datetime.datetime.now()
arcpy.env.overwriteOutput = True
arcpy.env.workspace = r"C:\Users\david\Desktop\GAEC\TEST\TEST1.gdb"

# DEFINITION OF VARIABLES
# ##############################################################################
OL = "OL"                             # LAYER WITH LINES
OL_FC = "in_memory" + "\\" + "OL_FC"
OL_buff = "in_memory" + "\\" + "OL_buff"
memOne = "in_memory" + "\\" + "OL_one"
memSelect = "in_memory" + "\\" + "memSelect"
memErase = "in_memory" + "\\" + "OL_erase"
memOut = "in_memory" + "\\" + "OL_out"
memOutTemp = "in_memory" + "\\" + "OL_outTemp"
memDissolve = "in_memory" + "\\" + "memDissolve"
memSplit = "in_memory" + "\\" + "memSplit"
memUnsplit = "in_memory" + "\\" + "memUnsplit"
OL_final = "OL_final"

# TESTING AND CREATING FIELDS IN ATRB TABLE, CALCULATING LENGTH
# ##############################################################################
if len(arcpy.ListFields(OL, "LENGTH")) > 0:
    print "Table already has a field LENGTH!..."
else:
    arcpy.AddField_management(OL, "LENGTH", "LONG")
    print "Missinf field LENGTH -> creating..."
arcpy.CalculateField_management(OL, "LENGTH",  "!shape.length!", "PYTHON")
print "Calculate length for lines..."

if len(arcpy.ListFields(OL, "EDIT")) > 0:
    print "Table already has a field EDIT!..."
    arcpy.DeleteField_management(OL, "EDIT")
    arcpy.AddField_management(OL, "EDIT", "SHORT")
else:
    arcpy.AddField_management(OL, "EDIT", "SHORT")
    print "Missinf field EDIT -> creating..."

arcpy.MakeFeatureLayer_management(OL, OL_FC)
rows = arcpy.SearchCursor(OL_FC)
sql = '"LENGTH" < ' + str(10)
lengths = list()
lengths0 = list()
i = 1
a = -1

# CYCLE WHICH MAKE LIST OF LENGHTS, USED FOR NUMBER OF LINES (CYCLES)
# ##############################################################################
for row in rows:
    x = row.getValue("LENGTH")
    lengths.append(x)

# WHOLE PROGRAMM CYCLE
# ##############################################################################
for q in (lengths):
    # COUNTING LENGTHS FOR LINES, NEED TO BE UPDATED CAUSE LENGHTS ARE CHANGING
    rows0 = arcpy.SearchCursor(OL_FC)
    for row0 in rows0:
        x0 = row0.getValue("LENGTH")
        lengths0.append(x0)
        lengths0.sort()
    # ##########################################################################
    leng = lengths0[a]
    # SELECTION OF ACTUAL LONGEST LINE, STARTS FROM END OF THE LIST AND DESCENDING
    sql1 = '"LENGTH" = ' + str(leng)
    arcpy.SelectLayerByAttribute_management(OL_FC, "NEW_SELECTION", sql1)
    # ##########################################################################
    curA = arcpy.SearchCursor(OL_FC)
    for rowA in curA:
        # TESTING IF LINE WAS EDITED BEFORE (IF YES, FIELD 'EDIT' = 1, THEN SKIP)
        test0 = rowA.getValue("EDIT")
        if test0 != 1:
        # ######################################################################
            arcpy.MakeFeatureLayer_management(OL_FC, memOne)
            print "Line exported into independently layer"
            arcpy.Buffer_analysis(memOne, OL_buff, "1.3 Meters")
            print "Created buffer around the line"
            arcpy.SelectLayerByLocation_management(OL_FC, "INTERSECT", OL_buff, "", "NEW_SELECTION")
            curAA = arcpy.UpdateCursor(OL_FC)
            for w2 in curAA:
                # FILLING FIELD EDIT WITH 1, SETTING FIELD LENGTH TO 0
                # ##############################################################
                w2.setValue("EDIT", 1)
                w2.setValue("LENGTH", 0)
                curAA.updateRow(w2)
            arcpy.MakeFeatureLayer_management(OL_FC, memSelect)
            print "Exporting affected lines"
            test = int(arcpy.GetCount_management(OL_FC)[0])
            # TESTING IF LINE TOUCHES AT LEAST ONE ANOTHER LINE, IF NO, SKIP
            # ##################################################################
            if test != 1:
                arcpy.Dissolve_management(memSelect, memDissolve)
                arcpy.Erase_analysis(memDissolve, OL_buff, memErase)
                arcpy.SplitLine_management(memErase, memSplit)
                arcpy.UnsplitLine_management(memSplit, memUnsplit)
                arcpy.Merge_management([memUnsplit, memJedna], memOut)
                print "Merge with original line (which buffer erases)"
                arcpy.Snap_edit(memOut, [[memOne, "EDGE", "1.4 Meters"]])
                print "LINES ARE SNAPPED TO ORIGINAL LINES"
                # DELETING LINES THAT ARE SHORTER THAN 10 M
                # ##############################################################
                arcpy.MakeFeatureLayer_management(memOut, memOutTemp)
                arcpy.CalculateField_management(memOutTemp, "LENGTH",  "!shape.length!", "PYTHON")
                arcpy.SelectLayerByAttribute_management(memOutTemp, "NEW_SELECTION", sql)
                arcpy.DeleteFeatures_management(memOutTemp)
                print "Lines shorter than 10 m deleted"
                # FIRST CYCLE CREATES OL_finan, AFTER LINES ARE APPENDING TO FINAL
                # ##############################################################
                if i == 1:
                    arcpy.CopyFeatures_management(memOutTemp, OL_final)
                else:
                    arcpy.Append_management(memOutTemp, OL_final)
                    print "New layer is append to layer before"
            # LINES WITHOUT CROSSING ANY OTHER LINE
            # ##################################################################
            else:
                for w2 in curAA:
                    w2.setValue("EDIT", 1)
                    w2.setValue("LENGTH", 0)
                    arcpy.Append_management(OL_FC, OL_final)
                    curAA.updateRow(w2)
                print "Standalone line appends to final layer"
            print "Imputed i, i = " + str(i) + "..."
    # DELETE RAM MEMORY
    # ##############################################################
    arcpy.Delete_management("in_memory")
    # IMPUTED TO NEXT CYCLE
    # ##########################################################################
    i+=1
    a-=1

# DELETE ASSISTANT FILEDS
# ##############################################################
arcpy.DeleteField_management(memOutTemp, "LENGTH")
arcpy.DeleteField_management(memOutTemp, "EDIT")

# ENDING OF TIMER
# ##############################################################################
end = datetime.datetime.now()
print "\nStart: " + start.strftime("%Y-%m-%d %H:%M:%S")
print "End of script: " + end.strftime("%Y-%m-%d %H:%M:%S")

2 votos

Algo pasa con el procedimiento utilizado para calcular las líneas de flujo si permite que se crucen. ¿Por qué no utilizar un procedimiento mejor desde el principio y evitar por completo este tratamiento posterior?

3 votos

Parece que está pidiendo una revisión del código, lo que hace que su pregunta sea demasiado amplia para nuestro formato de preguntas y respuestas. Creo que debería identificar los cuellos de botella utilizando más código de sincronización y luego buscar preguntar sobre las alternativas a esos como fragmentos de código individuales.

0 votos

Calculamos las líneas del MDT para toda la república. El MDT tenía una resolución de 5x5m y se utilizó el GRASS para ello. Había muchas imprecisiones en el MDT, como lugares sin salida y ruido digital. Así que se utilizó la función Fill para generalizar el MDT y rellenar las zonas sin flujo de salida. Ahora vamos a intentar calcular las líneas de nuevo con un mejor MDT, pero creo que este tipo de errores (como el cruce y así sucesivamente - este tema es sólo sobre el cruce, pero trato de resolver muchos otros problemas) todavía aparecerá.

8voto

Hameno Puntos 129

Además de utilizar el nuevo cursor arcpy.da, también sugeriría:

  • Tienes muchos cursores de búsqueda diferentes en la misma capa, mira si puedes eliminar algunos de ellos y sacar tus atributos de uno o dos
  • Aplicar un Añadir índice de atributos en cualquier columna que esté consultando
  • Vea si puede eliminar la capa de selección por lógica de atributos y aplicar esa consulta en el argumento de la cláusula where en el cursor (esto eliminará muchos registros que no necesita iterar)

0 votos

Gracias, intentaré usar los cursores de arcpy.da y añadir los índices a mi código. Espero que eso haga que el script sea un poco más rápido al menos. Pero una pregunta.. ¿Si uso AddIndex al campo por ejemplo ID, entonces uso SearchCursor en el campo original ID archivado o en IndexName?

1 votos

El campo en el que está buscando aplica el índice.

4voto

Nick Puntos 3115

Además de las respuestas sobre el cambio de cursor y la reducción del número de ellos, también puedes paralelizar esto un poco usando subprocesos. Los subprocesos en Python sortean la limitación del Bloqueo Global del Intérprete (GIL) - a diferencia del multithreading en Python (una cosa diferente al multiprocesamiento). Aunque ArcGIS 10 utiliza el multiprocesamiento ahora, creo que todavía debe decirle explícitamente a ArcPy que lo haga y crear sus subprocesos en consecuencia. El número que usted crea es un acto de equilibrio sin embargo. Inicie demasiados y usted realmente ralentizará las cosas. Para las tareas de geoprocesamiento pesadas trabajo en una regla general de número de subprocesos = núcleos -1 (dejando un núcleo para ArcGIS y el sistema operativo y el resto puede ser acaparado por su proceso).

También puede crear una granja de geoprocesamiento y poner en servicio muchas máquinas. Hay una manera de hacer esto con una sola copia de ArcGIS, pero entra en la "zona gris" del acuerdo de licencia de software. ¡Lo he hecho en el pasado con el conocimiento y el interés de ESRI (quemando aproximadamente 50 núcleos a través de unas 20 máquinas), pero hay que tener cuidado acerca de dónde se encuentra legalmente!

Su alternativa sería recrear su proceso en el software FOSS4G y poner en servicio tantas máquinas de su red como pueda, repartiendo la tarea entre ellas. Dadas las escalas de tiempo de las que hablas, puede que tengas que considerar esta opción si los cambios sugeridos más unos cuantos subprocesos locales no te dan la velocidad que necesitas.

EDITAR Para la documentación sobre el multiprocesamiento en Python, véase aquí . Sólo hay que tener cuidado en caso de que haya algún cambio entre la v2.x y la 3.x.

1 votos

+1 Iba a mencionar el módulo de multiprocesamiento. No sería difícil dividir la clase de características en particiones (más o menos) de igual tamaño que alimentan cada una a un grupo de trabajadores. Esto proporcionaría la mayor velocidad. Sólo requiere más trabajo porque tendrías que fusionar los resultados al final.

0 votos

Gracias por los consejos, debería aprender más sobre subprocesos y multiprocesos. Es bastante nuevo para mí, pero creo que me ayudaría. Tengo un montón de potencial de computación aquí (16 GB de RAM, 8 núcleos Intel i7 3,4 GHz), pero cuando ejecuto el script, hay un montón de potencial todavía sin usar. Entonces, ¿es posible con subprocesos o multiprocesos "dividir" las tareas y utilizar todo el potencial de computación? También puedo usar WM en servidores..

1 votos

Exacto. El multiprocesamiento tiene dos ventajas. En primer lugar, puedes hacer más uso de los recursos de tu máquina (8 núcleos es genial, por lo que recomiendo tener un grupo de siete trabajadores - más podría estar bien, pero dependerá de la intensidad de tu aplicación). El segundo beneficio para los procesos de larga duración es que a veces hay fugas de memoria en el código compilado que ArcPy llama. Algunas eran notorias pero parecen haber sido arregladas, pero creo que hay otras que todavía existen. Cada vez que se cierra un subproceso, la fuga de memoria (si existe) se borra. Para los procesos de larga duración como el suyo, esto es bueno.

2voto

cupakob Puntos 305

Cambia a arcpy.da para todos tus cursores. Los antiguos objetos de cursor arcpy se rompieron en 10.1 sp2 y la única forma en que ESRI pudo arreglarlo fue creando los nuevos objetos de cursor arcpy.da.

Tenía un proceso que se ejecutaba en 10.1 sp1 que tardaba 18 horas utilizando los antiguos objetos cursor. Después de sp2 tardó más de 12 días antes de que lo matara. Los nuevos cursores arcpy.da tardan 4 horas.

5 votos

Los antiguos objetos de cursor de arcpy se rompieron en 10.1 sp2 y la única forma en que ESRI pudo arreglarlo fue creando los nuevos objetos de cursor arcpy.da. ja ja ja ¿Qué? ¿Dónde has oído eso? Los antiguos cursores nunca estuvieron "rotos", sólo eran "lentos" y "las APIs no le parecían bien a un desarrollador profesional de Python". No podíamos romper la compatibilidad con los scripts existentes, así que dejamos los antiguos cursores sin tocar y ofreció la arcpy.da unos además de lo que ya existía.

2 votos

se rompieron en el hecho de que después de aplicar el service pack 2 mi script pasó de tardar 18 horas en procesarse a más de 12 días. Puede que no haya generado un error pero en mi libro eso cuenta como roto.

1 votos

¿Se ha puesto en contacto con el servicio de asistencia técnica? ¿Fue en un conjunto de datos específico? De nuevo, no se han modificado de una versión a otra. Debe haber algo más en tu caso.

2voto

dchanson Puntos 29

Además de las sugerencias anteriores, también sugeriría utilizar comprensiones de listas para bucles simples. Por ejemplo:

rows = arcpy.SearchCursor(OL_FC)
for row in rows:
    x = row.getValue("LENGTH")
    lengths.append(x)

Cambia a:

lengths = [row.getValue("LENGTH") for row in arcpy.SearchCursor(OL_FC)]

1 votos

Dependiendo del tamaño del cursor de búsqueda, es posible que quiera utilizar generadores en lugar de comprensiones de listas, de esa manera no se tiene que cargar todo en la memoria a la vez.

0 votos

Por curiosidad, ¿sería realmente más rápido?

0 votos

He comprobado que son más rápidos.

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X