W jaki sposób poprawia się wydajność kursora dostępu do danych w porównaniu z poprzednimi wersjami?

18

Moduł dostępu do danych został wprowadzony wraz z ArcGIS w wersji 10.1. ESRI opisuje moduł dostępu do danych w następujący sposób ( źródło ):

Moduł dostępu do danych, arcpy.da, jest modułem Pythona do pracy z danymi. Pozwala kontrolować sesję edycji, operację edycji, ulepszoną obsługę kursorów (w tym wyższą wydajność), funkcje do konwertowania tabel i klas funkcji na tablice NumPy i z nich, a także obsługę przepływu pracy wersji, replik, domen i podtypów.

Jednak niewiele jest informacji na temat tego, dlaczego wydajność kursora jest tak poprawiona w porównaniu z poprzednią generacją kursorów.

Załączony rysunek pokazuje wyniki testu porównawczego nowej dametody UpdateCursor w porównaniu do starej metody UpdateCursor. Zasadniczo skrypt wykonuje następujący przepływ pracy:

  1. Twórz losowe punkty (10, 100, 1000, 10000, 100000)
  2. Próbkuj losowo z rozkładu normalnego i dodaj kursor do nowej kolumny w tabeli atrybutów punktów losowych
  3. Uruchom 5 iteracji każdego scenariusza losowego dla obu nowych i starych metod UpdateCursor i zapisz średnią wartość do list
  4. Wykreśl wyniki

Co dzieje się za kulisami z dakursorem aktualizacji, aby poprawić wydajność kursora w stopniu pokazanym na rysunku?


wprowadź opis zdjęcia tutaj


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
źródło

Odpowiedzi:

25

Jeden z programistów arcpy.datutaj. Osiągnęliśmy wydajność tam, gdzie jest, ponieważ wydajność była naszym głównym zmartwieniem : głównym problemem ze starymi kursorami było to, że były one powolne, a nie brakowało jakiejkolwiek szczególnej funkcjonalności. Kod korzysta z tych samych podstawowych obiektów ArcObjects, które są dostępne w ArcGIS od wersji 8.x (na przykład implementacja CPython kursora wyszukiwania wygląda bardzo podobnie do przykładów kodu w tej implementacji, z wyjątkiem, wiesz, w C ++ zamiast C #).

Dwie główne rzeczy, które zrobiliśmy, aby uzyskać przyspieszenie to:

  1. Wyeliminuj warstwy abstrakcji: początkowa implementacja kursora Pythona była oparta na starym obiekcie GPDispatch opartym na technologii Dispatch / COM , co umożliwiło korzystanie z tego samego interfejsu API w dowolnym języku, który mógłby wykorzystywać obiekty COM Dispatch . Oznacza to, że posiadasz interfejs API, który nie został specjalnie zoptymalizowany pod kątem pojedynczego środowiska, ale oznaczał również, że istnieje wiele warstw abstrakcji dla obiektów COM, na przykład w celu reklamowania i rozwiązywania metod w czasie wykonywania. Jeśli pamiętasz przed ArcGIS 9.3, możliwe było pisanie skryptów geoprzetwarzania przy użyciu tego samego niezgrabnego interfejsu wielu języków, nawet Perla i Ruby . Dodatkowa papierkowa robota, którą musi wykonać obiekt, aby obsłużyćIDispatch rzeczy dodaje dużo złożoności i spowolnienia wywołań funkcji.
  2. Zrób ściśle zintegrowany, Python specyficzny biblioteki C ++ przy użyciu idiomów pythonic i struktur danych: pomysł na Rowobiekt i naprawdę dziwnym while cursor.Next():tańcu były po prostu nieefektywne w Pythonie. Pobieranie elementu z listy jest bardzo szybką operacją i upraszcza zaledwie kilka wywołań funkcji CPython (w zasadzie __getitem__wywołanie, mocno zoptymalizowane na listach). Robienie row.getValue("column")przez porównanie jest ważniejsze: __getattr__pobiera metodę (na której musi utworzyć nowy obiekt metody powiązanej), a następnie wywołuje tę metodę z podanymi argumentami ( __call__). Każda część arcpy.daimplementacji jest bardzo ściśle zintegrowana z interfejsem API CPython z dużą ilością ręcznie dostrojonego C ++, aby był szybki, przy użyciu natywnych struktur danych w języku Python (i także integracji numpy, dla jeszcze większej szybkości i wydajności pamięci).

Zauważysz również, że w prawie każdym teście porównawczym ( patrz na przykład slajdy ) obiekty arc. W .Net i C ++ są nadal ponad dwukrotnie szybsze niż arcpy.daw większości zadań. Używanie kodu w języku Python arcpy.dajest szybsze, ale nadal nie szybsze niż skompilowany język niższego poziomu.

TL; DR : dajest szybszy, ponieważ dajest implementowany w prostych, niefałszowanych obiektach Arcobjects / C ++ / CPython, które zostały zaprojektowane specjalnie w celu uzyskania szybkiego kodu w języku Python.

Jason Scheirer
źródło
4

Związane z wydajnością

  • Kursor domyślnie wykonuje iterację tylko przez ustawioną listę pól (nie całą bazę danych)

Inne niezwiązane bezpośrednio z wydajnością, ale ładne ulepszenia:

  • Możliwość korzystania z tokenów (np. SHAPE @ LENGTH, SHAPE @ XY) w celu uzyskania dostępu do geometrii elementu
  • Możliwość przeglądania baz danych (przy użyciu metody arcpy.da.Walk )
grafika 21
źródło