Przejdź do treści
MQL5 · MetaTrader 5 · po polsku
kod odczarowany

MQL5 / Pułapki

ArrayResize i koszt rozrostu tablicy

Powiększanie tablicy o jeden element w pętli zamienia kod liniowy w kwadratowy. Trzeci argument ArrayResize, o którym prawie nikt nie wie.

1 fragment kodu z tej strony przeszedł przez kompilator MetaEditor build 6090, 2026-08-05.

Zarządzanie tablicami dynamicznymi to chleb powszedni w programowaniu EA. Pobieranie historii świec, zleceń czy pozycji wymaga użycia tablic, których rozmiar nie jest z góry znany. Wbudowana funkcja ArrayResize wydaje się prosta – podajesz nowy rozmiar. Jednak pod maską terminala kryje się skomplikowany mechanizm alokacji pamięci, którego nieznajomość potrafi zwolnić EA o kilkaset procent.

Czym jest fragmentacja i realokacja?

Kiedy zwiększasz rozmiar tablicy (np. z 1000 do 1001 elementów), system operacyjny nie może po prostu "dopisać" jednego elementu na końcu w pamięci, ponieważ obszar ten może być już zajęty przez inne zmienne. MQL5 musi wtedy zaalokować nowy, większy blok pamięci, skopiować stare dane do nowego bloku, a stary blok zwolnić. Jest to operacja o złożoności czasowej O(N).

Jeśli robisz to w pętli, dodając po jednym elemencie przy każdej iteracji, złożoność całej operacji rośnie do O(N^2). Dla 100 000 elementów oznacza to miliony niepotrzebnych operacji kopiowania pamięci.

ArrayResize i koszt rozrostu tablicyPowiększanie tablicy o jeden element w pętli zamienia kod liniowy w kwadratowy. Trzeci argument ArrayResize, o którym prawie nikt nie wie. Realokacja Pamięci bez użycia parametru 'reserve_size' Tablica [0..99] Rozmiar: 100 ArrayResize(arr, 101) Nowa Tablica [0..100] Alokacja nowej pamięci + kopiowanie 100 elementów! Stara pamięć (Zwalniana)

Rozwiązanie: Trzeci parametr ArrayResize

Wielu programistów nie wie, że funkcja ArrayResize posiada trzeci, opcjonalny parametr: reserve_size. Pozwala on na "rezerwację" pamięci na przyszłość bez zmiany logicznego rozmiaru tablicy (zwracanego przez ArraySize). Jest to odpowiednik metody .reserve() z std::vector w C++.

Terminal alokuje wtedy ciągły blok pamięci wystarczający dla reserve_size elementów. Dopóki nowy rozmiar nie przekroczy zarezerwowanej puli, żadne kopiowanie nie ma miejsca. Dodawanie elementów staje się operacją O(1) amortyzowaną.

// BŁĄD: Klasyczny antywzorzec wydajnościowy
void AppendBad(double &arr[], double value) {
    int size = ArraySize(arr);
    // Przy każdym wywołaniu terminal kopiuje całą tablicę!
    ArrayResize(arr, size + 1);
    arr[size] = value;
}

// POPRAWNIE: Użycie mechanizmu rezerwacji
void AppendGood(double &arr[], double value, int &reservedCapacity) {
    int size = ArraySize(arr);
    
    // Jeśli brakuje miejsca, zwiększamy pule rezerwy
    if(size >= reservedCapacity) {
        reservedCapacity = size + 1000; // Skok o 1000 elementów
        // Trzeci parametr fizycznie alokuje więcej pamięci, ale ArraySize się nie zmienia
        ArrayResize(arr, size + 1, reservedCapacity);
    } else {
        // Szybkie dodanie bez realokacji
        ArrayResize(arr, size + 1);
    }
    
    arr[size] = value;
}

void OnStart() {
    double myArray[];
    int capacity = 0;
    ArrayResize(myArray, 0, 0); // Inicjalizacja
    
    ulong startTime = GetTickCount64();
    for(int i = 0; i < 100000; i++) {
        AppendGood(myArray, i, capacity);
    }
    Print("Czas trwania: ", GetTickCount64() - startTime, " ms");
}