Como o desempenho do cursor de acesso a dados é tão aprimorado em comparação às versões anteriores?

18

O módulo de acesso a dados foi introduzido com o ArcGIS versão 10.1. A ESRI descreve o módulo de acesso a dados da seguinte forma ( fonte ):

O módulo de acesso a dados, arcpy.da, é um módulo Python para trabalhar com dados. Ele permite o controle da sessão de edição, operação de edição, suporte aprimorado ao cursor (incluindo desempenho mais rápido), funções para converter tabelas e classes de recursos de e para matrizes NumPy e suporte para fluxos de trabalho de versionamento, réplicas, domínios e subtipos.

No entanto, há muito pouca informação sobre por que o desempenho do cursor é tão aprimorado em relação à geração anterior de cursores.

A figura em anexo mostra os resultados de um teste de benchmark no novo damétodo UpdateCursor versus o antigo método UpdateCursor. Essencialmente, o script executa o seguinte fluxo de trabalho:

  1. Crie pontos aleatórios (10, 100, 1000, 10000, 100000)
  2. Amostra aleatória de uma distribuição normal e agrega valor a uma nova coluna na tabela de atributos de pontos aleatórios com um cursor
  3. Execute 5 iterações de cada cenário de ponto aleatório para os métodos UpdateCursor novos e antigos e grave o valor médio nas listas
  4. Traçar os resultados

O que está acontecendo nos bastidores com o dacursor de atualização para melhorar o desempenho do cursor no grau mostrado na figura?


insira a descrição da imagem aqui


import arcpy, os, numpy, time
arcpy.env.overwriteOutput = True

outws = r'C:\temp'
fc = os.path.join(outws, 'randomPoints.shp')

iterations = [10, 100, 1000, 10000, 100000]
old = []
new = []

meanOld = []
meanNew = []

for x in iterations:
    arcpy.CreateRandomPoints_management(outws, 'randomPoints', '', '', x)
    arcpy.AddField_management(fc, 'randFloat', 'FLOAT')

    for y in range(5):

        # Old method ArcGIS 10.0 and earlier
        start = time.clock()

        rows = arcpy.UpdateCursor(fc)

        for row in rows:
            # generate random float from normal distribution
            s = float(numpy.random.normal(100, 10, 1))
            row.randFloat = s
            rows.updateRow(row)

        del row, rows

        end = time.clock()
        total = end - start
        old.append(total)

        del start, end, total

        # New method 10.1 and later
        start = time.clock()

        with arcpy.da.UpdateCursor(fc, ['randFloat']) as cursor:
            for row in cursor:
                # generate random float from normal distribution
                s = float(numpy.random.normal(100, 10, 1))
                row[0] = s
                cursor.updateRow(row)

        end = time.clock()
        total = end - start
        new.append(total)
        del start, end, total
    meanOld.append(round(numpy.mean(old),4))
    meanNew.append(round(numpy.mean(new),4))

#######################
# plot the results

import matplotlib.pyplot as plt
plt.plot(iterations, meanNew, label = 'New (da)')
plt.plot(iterations, meanOld, label = 'Old')
plt.title('arcpy.da.UpdateCursor -vs- arcpy.UpdateCursor')
plt.xlabel('Random Points')
plt.ylabel('Time (minutes)')
plt.legend(loc = 2)
plt.show()
Aaron
fonte

Respostas:

25

Um dos desenvolvedores arcpy.dadaqui. Temos o desempenho onde está, porque o desempenho era a nossa principal preocupação : a principal preocupação com os antigos cursores era que eles eram lentos, não que eles não tivessem nenhuma funcionalidade específica. O código usa os mesmos ArcObjects subjacentes disponíveis no ArcGIS desde 8.x (a implementação CPython do cursor de pesquisa, por exemplo, se parece muito com exemplos de código como este em sua implementação , exceto, você sabe, em C ++ em vez de C #).

As duas principais coisas que fizemos para obter a aceleração são assim:

  1. Eliminar camadas de abstração: a implementação inicial do cursor Python foi baseada no antigo objeto GPDispatch baseado em Dispatch / COM , que permitiu usar a mesma API em qualquer idioma que pudesse consumir objetos de Despacho COM . Isso significa que você tinha uma API que não estava particularmente otimizada para um único ambiente, mas também significava que havia muitas camadas de abstração para os objetos COM anunciarem e resolverem métodos em tempo de execução, por exemplo. Se você se lembra do ArcGIS 9.3, era possível escrever scripts de geoprocessamento usando a mesma interface desajeitada de muitas linguagens, inclusive Perl e Ruby . A papelada extra que um objeto precisa fazer para lidar com aIDispatch O material adiciona muita complexidade e lentidão às chamadas de função.
  2. Crie uma biblioteca C ++ específica e especificamente integrada ao Python, usando expressões idiomáticas e estruturas de dados Python: a idéia de um Rowobjeto e a while cursor.Next():dança realmente estranha eram simplesmente ineficientes no Python. Buscar um item de uma lista é uma operação muito rápida e simplifica apenas algumas chamadas de função CPython (basicamente uma __getitem__chamada, altamente otimizada em listas). Fazer row.getValue("column")comparações é mais pesado: ele __getattr__busca o método (no qual precisa criar um novo objeto de método vinculado) e, em seguida, chama esse método com os argumentos fornecidos ( __call__). Cada parte da arcpy.daimplementação é muito estreitamente integrado com a API CPython com muito C ++ ajustado manualmente para torná-lo rápido, usando estruturas de dados Python nativas (e integração numpy, também, para ainda mais velocidade e eficiência de memória).

Você também notará que em praticamente qualquer benchmark ( veja esses slides, por exemplo ), os objetos de arco em .Net e C ++ ainda são duas vezes mais rápidos do que arcpy.dana maioria das tarefas. O uso de código Python arcpy.daé mais rápido, mas ainda não mais rápido do que uma linguagem compilada de nível inferior.

TL; DR : daé mais rápido porque daé implementado em Arcobjects / C ++ / CPython diretos e não adulterados, que foi projetado especificamente para resultar em código Python rápido.

Jason Scheirer
fonte
4

Desempenho relacionado

  • O cursor apenas repete a lista de campos definida por padrão (não o banco de dados inteiro)

Outros não diretamente relacionados ao desempenho, mas ótimos aprimoramentos:

  • Capacidade de usar tokens (por exemplo, SHAPE @ LENGTH, SHAPE @ XY) para acessar a geometria do recurso
  • Capacidade de percorrer bancos de dados (usando o método arcpy.da.Walk )
artwork21
fonte