Apr 29 2008

Łączenie tabel, a wydajność… cz.2

Posted by Bartek

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...

Powiązane artykuły:

  • brak powiązanych artykułów
Filed under : VBA, Wszystkie | No Comments »

Leave a Reply