Łączenie tabel, a wydajność… cz.2
Kilka dni temu w tym poście zastanawiałem się nad wydajnością łączenia k tablic (n,5) w jedną. Dziś rano, zupełnie przypadkiem wpadłem na pomysł, który skrócił rozwiązanie tego problemu prawie dziesięciokrotnie.
Każdy programista staje często przed wyborem pomiędzy szybkością wykonania, a wielkością zajmowanej przez program pamięci.
Dynamiczna zmiana rozmiaru tablicy
Dynamiczne alokowanie rozmiaru tablicy jest wyjściem bardzo kuszącym, ponieważ zwykle wydaje nam się, że dzięki temu zwiększymy elastyczność naszego kodu. W VBA w tym celu wykorzystujemy ReDim [Preserve]. Przykładowo, poniższy kod zwiększa rozmiar tablicy o 1 zachowując przy tym jej zawartość.
ReDim Preserve aTablica(UBound(aTablica) + 1)
Niestety, za elastyczność płacimy wydajnością -- ReDim, szczególnie razem z Preserve, jest poleceniem pochłaniającym dużo zasobów. W praktyce powinno wywoływać się go rzadko, tylko w odniesieniu do małych tablic. Przy większych prawie zawsze stajemy przed pytaniem: Czy lepiej częściej wywoływać zasobożerne Redim Preserveczy też jednorazowo zaalokować więcej pamięci, a następnie obsłużyć ją odpowiednimi pętlami zwiększając złożoność obliczeniową rozwiązania? Właśnie dziś rano postanowiłem sprawdzić odpowiedź na to pytanie.
Rozwiązanie problemu łączenia k tablic - podejście drugie
Wróćmy teraz do problemu połączenia razem k tablic n-wymiarowych. W poprzednim poście, wykorzystałem w tym celu instrukcję ReDim Preserve, która dołączała kolejne małe tablice do wyjściowej. Przypomnę tylko, że połączenie ponad trzystu tablic w jedną o wymiarach (8500,5) zajęło prawie 2 minuty.
Drugi sposób polega na wstępnym obliczeniu rozmiaru tablicy wynikowej, a następnie wypełnieniu jej elementami kolejnych tablic. Niech nasze testowe k tablic przechowywane jest w kolekcji.
Przekażmy teraz tą kolekcję do funkcji:
Public Function ArraysUnion(aTablice As Collection) As Variant Dim tempArray() As Variant Dim dlugoscTablicy As Long Dim c As Variant For Each c In aTablice dlugoscTablicy = dlugoscTablicy + UBound(c,2) Next ReDim tempArray(1 To 5, 1 To dlugoscTablicy) Dim Upper As Long, i As Long, j As Long Upper = 0 For Each c In aTablice For i = LBound(c, 2) To UBound(c, 2) For j = 1 To 5 tempArray(j, Upper + i) = c(j, i) Next j Next i Upper = Upper + i - 1 Next ArraysUnion = tempArray End Function
Funkcja działa w sposób następujący: Najpierw sumowane są rozmiary kmałych tablic, dzięki czemu określane są rozmiary tablicy wynikowej. Następnie tworzona jest tablica do której przy pomocy potrójnego for'a zapisywane są kolejne elementy małych tablic. Mimo, że rozwiązanie to wymaga jednej pętli for więcej niż rozwiązanie poprzednie, i tak oszczędności czasowe związane z likwidacją wywołań Redim Preservez nawiązką pokrywają ewentualne wydłużenia czasu. Okazuje się, że wykonanie funkcji dla tej tablicy trwało niecałe 12 sekund.
Jak widać Redim Preserve jest instrukcją angażującą zbyt wiele zasobów, by mogła być wywoływana często w programie. A ja chyba jednak pozostanę przy starym sprzęcie...
