Brak pamięci - tablice dynamiczne

0

Mam taki problem. Chciałem stworzyć dość dużą tablicę dynamiczną 64(rozmiar rekordu)x5mln czyli 320MB. Ale przy alokacji 230MB wywala mi wyjątek EOutOfMemory. Może ktoś mi wytłumaczy jak działa alokacja pamięci dla tablic. Mam WIndows 7, 4GB RAM.
Po program ma dyrektywę {$M 16384,1848576000}. Nie wiem co zrobić, menadżer windows pokazuję pamięć dla aplikacji na 228MB. Może Windows 7 ma limity przydziały pamięci dla pojedynczej aplikacji, nie mam pomysłu jak sprawić by poprawnie zaalokować pamięć dla mojej tablicy.

0

Alokujesz za pomocą SetLength oczywiście?


może Windows 7 ma limity przydziały pamięci dla pojedynczej aplikacji,

O ile pamiętam, 2 lub 3 GB na aplikację max.

czyli 320MB

Wliczyłeś w to rozmiar typu?
Dla przykładu, jeżeli taka duża tablica będzie typu Integer (w systemie 32-bitowym), będzie miała rozmiar:
6450000004/1024/1024=1220 MB(!!!)
Zapewniam Cię, niech cię Boża ręka chroni przed alokacją takiej wielkiej tablicy!
Cokolwiek chcesz zrobić, da się to wykonać bez zajmowania 1.2 GB pamięci...

0

Wyliczyłem dobrze. Zmierzyłem rozmiar rekordu (uwzględnia rozmiary typów) przez SizeOf, razem daje to 64 bajty. Tablica jest bardzo duża, bo piszę aplikację, która ma przyśpieszyć te same operacje, które wykonuję na bazie danych. Na bazie danych trwa to kilka dni, dlatego chciałem to wykonać w pamięci RAM. Dziwi mnie to, że mając 4GB RAM odejmując z 1,5RAM na Windows brakuje mi pamięci przy 230MB.

0

pokaż jak powstaje ta tablica

0

Alokuję przez SetLength i przy alokacji 3.600.000 rekordu wywołuje mi wyjątek.

0

Sprawdź czy na innym memory-managerze też jest ten problem (np.na FastMM4).

0

Alokuję stopniowo co 10.000 rekordów.

0

Może coś powinienem zmienić w Options projektu ze względu na dość nietypowy rozmiar tablicy?

0

A jak wygląda ten rekord? Z fusów ciężko wróżyć. Kod svp.
Pewnie obiekty w tym rekordzie alokują pamięć po utworzeniu, a funkcja sizeof zwraca to co powinna, czyli sumuje rozmiary wskaźników do nich.

0

a nie jest to czasem lokalna zmienna (na stosie)?

0

http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/devcommon/compdirsmemoryallocsizes_xml.html
Na tej stronie piszą, że maksymalny rozmiar stosu to 2.147.483.647 a przez Options->Linking mogę zaalokować tylko 16.777.216 wie ktoś dlaczego?

0

Tablice i tak nie są tworzone przecież na stosie...

0
  1. podziel na mniejsze tablice
  2. zamiast tablicy zrób na liście jedno/dwu kierunkowej
    Jak pokazywałem wczoraj zaalokowanie ponad 1GB pamięci w Delphi 7 / XE na WinXP /W7 nie jest problemem
procedure TForm5.Button5Click(Sender: TObject);
type
  TBuf = array[1..64] of byte;
var
  Arr: array of TBuf;
begin
  SetLength(arr, 5000000);
  ShowMessage(Inttostr(Length(arr)));
end;

w szczycie program zajmuje w pamięci trochę ponad 320MB

co więcej nawet SetLength(arr, 15000000) przechodzi bez problemu i program zajmuje 860MB

0

no masz, trzyma się stosu jak pijany płota :-)
coś mnie chodzi po głowie, że pierwsze po {$M to stos, ale w 16 bajtowych ramkach, liczby podobne...

0
Xitami napisał(a):

a nie jest to czasem lokalna zmienna (na stosie)?

Pole w klasie.
Próbowałem też z pola zrobić zmienną globalną - ten sam efekt dokładnie.
Wygląda na to, jakby wskaźniki do rekordów nie mieściły się w rozmiarze stosu 8x3.600.000=288.000.000, bo max rozmiar stosu (w Options-> Linking)to 16MB (dlaczego? a nie 2GB jak piszą na stronie Embarcadero).

0

może zamiast opowiadać tak pięknie pokażesz wreszcie kod

0

Kod jest prostacki: deklaracja rekordu, deklaracja tablicy dynamicznej i SetLength() co 10.000 rekordów, obsługa wyjątku EOutOfMemory.

0

Alokuję stopniowo co 10.000 rekordów.

A jak sądzisz co zrobi subsystem? Zaalokuje nową pamięć o ileśtam bajtów większą i wszystko przeniesie.
Tablice dynamiczne nie są wysokopoziomowe, one są stosunkowo głupie. Użyj czegoś innego, np. w FPC TFPGList<integer>

Wyliczyłem dobrze. Zmierzyłem rozmiar rekordu (uwzględnia rozmiary typów) przez SizeOf, razem daje to 64 bajty.

Jakie razem, może jest coś alignowane według ABI?
Skoro wyliczyłeś dobrze to znaczy że nie masz pamięci.

może zamiast opowiadać tak pięknie pokażesz wreszcie kod

Kod powinien być na początku. eh.

Tablice i tak nie są tworzone przecież na stosie...

Toż to oczywiste, jak ktoś myśli że na stosie to może od razu wyjść... Chodzi ofc o tablice dynamiczne.

Kod jest prostacki: deklaracja rekordu, deklaracja tablicy dynamicznej i SetLength() co 10.000 rekordów, obsługa wyjątku EOutOfMemory.

Znasz takie słowo jak KOD ???

0

Nie o błąd w kodzie mi chodzi, bo kod działa prawidłowo. Ale dla tych co chcą kodu proszę:

type TTransactionsRec=record
        Id:LongInt;
        Direction:Byte;
        Currency:Byte;
        Open:Double;
        DataOpen:TDateTime;
        Close:Double;
        DataClose:TDateTime;
        Zysk:Double;
        Minimum:Double;
        Maximum:Double;
end;

type TTransactions=array of TTransactionsRec;
var Transactions:TTransactions;

procedure TMain.FormCreate(Sender: TObject);
begin
  MaxTransactions:=10000;
  AddTransactions:=10000;
  CountTransactions:=0;
  Initialize(Transactions);
  SetLength(Transactions,MaxTransactions);
end;



function TMain.CzytajHistorie(FileName:string):Boolean;
var StreamReader:TStreamReader;
    Encoding:TEncoding;
    Output : TSplit;
    FormatSettings:TFormatSettings;
begin
  Result:=true;
  FormatSettings:=TFormatSettings.Create;
  FormatSettings.TimeSeparator:=':';
  FormatSettings.DateSeparator:='-';
  FormatSettings.ShortDateFormat:='yyyy-mm-dd hh:nn:ss';
  FormatSettings.DecimalSeparator:='.';
  if FileExists(FileName) then
    begin
      StreamReader:=TStreamReader.Create(FileName,Encoding.ASCII);
      repeat
        Split(';',StreamReader.ReadLine,Output);
        if (CountTransactions>=MaxTransactions) then
          begin
            MaxTransactions:=MaxTransactions+AddTransactions;
            try
              SetLength(Transactions,MaxTransactions);
            except
              on EOutOfMemory do
                begin
                  Memo1.Lines.Add('Brak pamieci. Zaladowano: '+IntToStr(CountTransactions)+' rekordow.');
                  Result:=false;
                  Break;
                end;
            end;
          end;
        Transactions[CountTransactions].Id:=StrToInt(OutPut[0]);
        if OutPut[1]='buy' then Transactions[CountTransactions].Direction:=1
                           else Transactions[CountTransactions].Direction:=2;
        Transactions[CountTransactions].Currency:=StrToCurrency(OutPut[2]);
        Transactions[CountTransactions].Open:=StrToFloat(OutPut[4],FormatSettings);
        Transactions[CountTransactions].DataOpen:=StrToDateTime(OutPut[5],FormatSettings);
        Transactions[CountTransactions].Close:=StrToFloat(OutPut[6],FormatSettings);
        Transactions[CountTransactions].DataClose:=StrToDateTime(OutPut[7],FormatSettings);
        Transactions[CountTransactions].Zysk:=StrToFloat(OutPut[8],FormatSettings);
        Transactions[CountTransactions].Maximum:=StrToFloat(OutPut[9],FormatSettings);
        Transactions[CountTransactions].Minimum:=StrToFloat(OutPut[10],FormatSettings);
        Inc(CountTransactions);
      until StreamReader.EndOfStream;
      StreamReader.Free;
    end;
end;
0

Jeżeli SetLength nie kopiuje poprzedniej wartości do nowego miejsca a jedynie zwiększa rozmiar poprzedniego bloku to na 100% jest to wina błędu alokacji ciągłego bloku pamięci w windzie.

Zrób zamiast tej ciągłej tablicy jakąś listę jedno lub dwukierunkową z tych rekordów i na niej działaj.

0

if (CountTransactions>=MaxTransactions) then
begin
MaxTransactions:=MaxTransactions+AddTransactions;
try
SetLength(Transactions,MaxTransactions);
except
on EOutOfMemory do
begin
Memo1.Lines.Add('Brak pamieci. Zaladowano: '+IntToStr(CountTransactions)+' rekordow.');
Result:=false;
Break;
end;
end;
end;

Cóż za emulacja listy. Wymyślanie koła od nowa to twoja specjalność chyba.

Jeżeli SetLength nie kopiuje poprzedniej wartości do nowego miejsca a jedynie zwiększa rozmiar poprzedniego bloku to na 100% jest to wina błędu alokacji ciągłego bloku pamięci w windzie.

Hmhm. Jaki błąd?

0

Kolega nie słyszał, że pamięć alokowana dla tablicy (nie mylić z listą) musi znajdować się w bloku ciągłym (pusty obszar) lub rosnąć w nim inaczej po ptokach?

Wiem że musi, ale mi na mojej maszynie udało się zaalokować ~600Mb w bloku ciągłym z pomocą TFPGList<int64>. Przy integerze wywalało przekręcenie licznika listy. Więc nie wiem czy jest potrzeba tworzenia skomplikowanej listy która nie będzie ciągła i przez to wolniejsza.
Teraz już ogarniam o jaki błąd chodzi. Z początku myślałem że chodzi o jakiś bug windy.

0
-123oho napisał(a):

if (CountTransactions>=MaxTransactions) then
begin
MaxTransactions:=MaxTransactions+AddTransactions;
try
SetLength(Transactions,MaxTransactions);
except
on EOutOfMemory do
begin
Memo1.Lines.Add('Brak pamieci. Zaladowano: '+IntToStr(CountTransactions)+' rekordow.');
Result:=false;
Break;
end;
end;
end;

Cóż za emulacja listy. Wymyślanie koła od nowa to twoja specjalność chyba.

Napisałem to ten sposób, żeby znaleźć przy jakiej wielkości alokacji, program się sypie. Oczywiście mogłem zaalokować od razu 5mln. OK napiszę to na liście, ale podejrzewam, że wystąpi podobny błąd. Chodzi mi raczej o generalny problem, mam 4GB RAM, załóżmy, że dostępne mam 2GB, dlaczego z tych 2GB mam dostępne 300MB? Co z resztą.

Mogę zaalokować trochę więcej gdy na początku wpiszę alokację 4.300.000 rekordów. 4.400.400 już nie wpiszę.

procedure TMain.FormCreate(Sender: TObject);
begin
  MaxTransactions:={10000;}4300000;
  AddTransactions:=100000;
  CountTransactions:=0;
  Initialize(Transactions);
  SetLength(Transactions,MaxTransactions);
end;
0
Initialize(Transactions);

Po co wywoływać to na tablicy dynamicznej?

1

Chodzi mi raczej o generalny problem, mam 4GB RAM, załóżmy, że dostępne mam 2GB, dlaczego z tych 2GB mam dostępne 300MB? Co z resztą.

Masz dostępne 300MB jako całość w jednym bloku. Nie zależy to od fizycznej ilości ramu, po prostu Winda 32bit ma 2GB przestrzeni adresowej dla użyszkodnika. W tej przestrzeni masz różne dllki które dzielą przestrzeń na mniejsze bloki. Być może najdłuższy dostępny blok u ciebie wynosi 300Mb. U mnie udało się do 600Mb (2GB ramu fizycznie).
Lista ma to do siebie że każde dodanie nie spowoduje zwiększenia jej rozmiaru w pamięci bo ona ma "zapas".
Jeżeli mimo listy ciągle brakuje ci pamięci to możesz napisać to na paru listach które będą rezerwować oddzielne bloki ciągłe albo po prostu napisać system który nie będzie aż tyle danych czytał.

Po co wywoływać to na tablicy dynamicznej?

Też nie wiem, FPC tego nie chce.

0

Dziękuję za pomoc. Na liście działa. Rozumiem już w czym problem. Dziękuję.

0

myślę, że nie zrozumieliśmy i nie dotarliśmy
w Pascalu (którymś) alokowałem tablice po prawie 2GB, bo tyle można
i z fizyczną pamięcią RAM miało to związek luźny
a co to Tatusiu pamięć wirtualna? a to coś co wymyślił twój dziadek

0

Na poczatku napisales ze na bazie trwa to dlugo.
Jaki to silnik?
Ps Nie zrobic niczego szybszego niz baza danych (zalezy od silnika FireBird'a i tego typu domoroslych konstrukcji nie biore pod uwage).
Z tego co widze i tak czytasz plik (baza robi to samo, przyczym czyta np MSSQL po stronach 8kB).

Rozwiazanie na bazie da sie przyspieszyc - napisz tylko co robisz.
Jesli to przetwarzanie danych ala OLAP to od 2012 MSSQL masz do dyspozycji columnstore index.

1 użytkowników online, w tym zalogowanych: 0, gości: 1