Tworzenie recordow w tablicy za wolne!

0

Cześć koledzy i koleżanki.

Mam tablice z rekordami:

fv_lv1:unit1.r_lv1; //baza w rekordach

problem w tym ze bardzo długo tworzy się ta tablica.. Chodziło Mi by móc się odwoływać do tablicy jak do ListView i to osiągnąłem, działa super. Natomiast wczytanie 82000 rekordów do tablicy zajmuję 3 minuty, te same dane do zwykłego listview wczytują się 50 sekund.

Problem zauważyłem tutaj:

SetLength(fv_lv1.item,i_idx);

Może znacie inny sposób tworzenia tablic niz poprzez SetLength by osiągnąć co mam teraz: np.

fv_lv1.item[i].Subitems[2]:='test';
function Tanc_fv1.f1_lv1_add:integer;
var
i_idx:integer;
begin

//tworzymy item w tabeli
 i_idx:=length(fv_lv1.item)+1;
 SetLength(fv_lv1.item,i_idx);

//Tworzymy Subitemy w itemie
 fv_lv1.item[i_idx-1].Subitems:=TStringList.Create;

result:=i_idx-1; //zwaracamy juz idx itema do ktorego mozna sie odwolac

end;

Dziękuje :-)

2

Czym jest fv_lv1 i weź ponazywaj to normalnie bo oczy mnie bolą od patrzenia.

0

to rekordy pisałem o tym, imitacja listview ale w recordach.

//-> lv1 baza
r_items = record
pusty:byte; //czy wolny mozna uzyc? bo np skasowano - nie kasujemy lini tylko ustwiamy stan na 0
Caption:string;
Subitems:TStringList;
end;

r_lv1 = record
item:array of unit1.r_items;
end;
//<- lv1 - baza
0

Zamień record na packed record i z TStringList zrób wskaźnik na TStringList.

0

mókłbyś rzucić jakiś przykład oraz jak się odwoływać do konkretnego rekordu?
obecnie aby odwołać się do rekordu wystarcza Mi znać jego indeks w tablicy:

fv_lv1.item[123456].Subitems[5]:='test';
0

Tak to powinno wyglądać

RItem = packed record
 Empty: Boolean; 
 Caption: String;
 Subitems: ^TStringList;
 end;
 
RBase = packed  record
 Items :Array of RItem;
 end;

Wolno ci działa bo każde SetLength kopiowało zaalokowaną pamięć. Do tego za każdym razem wyliczało potrzebną pamięć dla kolejnego rekordu.
Dodatkowo rekord trzymał dane (TStringList) a nie wskaźnik na ten obiekt. To taka różnica że zamiast kopiować tam 4 bajty (wielkość wskaźnika) to kopiował X bajtów, tyle co wsadziłeś do tego Subitems

1
hzmzp napisał(a):

Zamień record na packed record i z TStringList zrób wskaźnik na TStringList.

Pakowanie rekordów tylko pogorszy wydajność, więc odradzam. Natomiast nie ma żadnej potrzeby przerabiania referencji na wskaźnik, bo każda referencja jest de facto wskaźnikiem, tak więc summa summarum, sugerujesz zrobienie wskaźnika na wskaźnik, co nie ma absolutnie żadnego sensu. :D

Prawdziwym problemem jest to, że obecny kod dodawania elementu do macierzy wymusza relokację bloku pamięci całej tablicy, co jest wybitnie nieefektywne. A rozwiązaniem tego problemu jest wywalenie tej cholernej tablicy i zastąpienie jej listą generyczną, bo ta alokuje pamięć z odpowiednim zapasem dla kolejnych elementów.

Poza tym ten kod jest potwornie nieczytelny i wręcz wyje z bólu prosząc o sformatowanie.

0

a odpowiedź krótka i szybka to: ustal rozmiar tablicy RAZ, od razu na właściwy

0

Dziękuje bardzo hzmzp oraz furious programming.
Furious programming, czy mógłbym prosić o przykład ?

jak pisałem wcześniej chciałbym uzyskać formę odwołania:
fv_lv1.item[123456].Subitems[5]:='test';

tak kod był nieczytelny bo brakuje momentu dodawania:

idx:=anc_fv1.f1_lv1_add; //tworzy record i zwraca jego indeks w tablicy
fv_lv1.item[idx].Caption:='Test1';
fv_lv1.item[idx].Subitems.Add('test');

abrakadaber: Rekordów będzie tylko dochodzić teraz jest 82000 ale bedzie 90 i 100 itd.

0

Gdy zmieniasz wielkość tablicy za każdym razem jest tworzona w pamięci nowa tablica o innym rozmiarze i całość jest przepisywana. Jeśli będziesz zmieniał rozmiar przy każdym nowym elemencie to tablica będzie zaalokowana 82000 razy za każdym razem przepisując n-1 elementów do niej więc masz złożoność wykładniczą, stąd Twoje 3 minuty.
Rozwiązaniem jest tak jak napisał abrakadaber - ustalenie rozmiaru RAZ, zapewne przed pętlą dodającą elementy znasz już ilość tych elementów więc możesz to zrobić.

Drugim podejściem jest zaalokowanie zapasu i zapisywanie ile elementów tablicy jest w użytku, podwajając ilość elementów tablicy gdy ją zapełnimy. W ten sposób są zazwyczaj zaimplementowane listy. Swoją drogą czemu po prostu nie użyjesz listy zamiast tablicy co rozwiąże wszystkie problemy?

hzmzp napisał(a):

Dodatkowo rekord trzymał dane (TStringList) a nie wskaźnik na ten obiekt. To taka różnica że zamiast kopiować tam 4 bajty (wielkość wskaźnika) to kopiował X bajtów, tyle co wsadziłeś do tego Subitems

Bzdura, TStringList to już referencja, taka zmiana jedynie komplikuje niepotrzebnie kod.
No chyba że chciałbyś podmienić instancję listy wszystkim elementom na raz. Wtedy wskaźnik na referencję miałby sens bo zamiast podmienić w pętli mógłbyś podmienić listę tylko w jednym miejscu a wszystkie elementy będą automatycznie wskazywały na nową listę. W innym przypadku nie ma sensu

1
Patryk Winner napisał(a):

Furious programming, czy mógłbym prosić o przykład ?

Przykłady są w dokumentacji. Zobacz tutaj — TList oraz TList usage example.

0

Ogólnie trudno dopasować odpowiednie rozwiązanie gdyż podajesz za mało informacji. Pomijając że te 80k rekordów to nie jest zbyt dużo, to nie wiemy jak duże to są porcje danych, jak często i w jakich ilościach ich przybywa i w jaki sposób? Jakie operacje są na nich wykonywane? jak wspomniał @abrakadaber najlepiej byłoby zainicjować odpowiednią ilość od razu, ale być może dostajesz je w strumieniu i nie wiesz ile ich będzie. Być może lepszym rozwiązaniem byłoby jak wspomniał @furious programming użyć czegoś z pakietu System.Generics.Collections TList lub np TDictionary które jest listą klucz => wartość.

0

Hej, problem w tym że operowałem od dłuższego czasu na listview, zmieniłem to na rekordy ze względu na pamięć. Tak bardzo dużo operacji i w tysiącach linijek odwołuje się w ten sposob, dlatego chciałem zostawić odwołanie w taki sposób. Nie ma Mnie teraz przy komputerze ale rozważam określić rozmiar tablicy ale zastanawiam się czy mogę wykonać na określonym rozmiarze steLength gdybym przekroczył ilość np.

X:array[0..99999] of record_y. ;

Następnie po przekroczeniu liczby 99999

If length(x) > 999999 then
SetLengt(x, length(x) +1)

Zadziala?

Sprawdzę to w domu, jak wrócę.

0

Już prędzej, ale rozważ refactoring bo takiego kodu długo nie utrzymasz, a z każdą zmianą będzie większe spaghetti z ifologią. Pamiętaj że apki 32bit będą działać do ~3GB RAM później klękają przez ograniczenia.

0
Patryk Winner napisał(a):

Hej, problem w tym że operowałem od dłuższego czasu na listview, zmieniłem to na rekordy ze względu na pamięć. Tak bardzo dużo operacji i w tysiącach linijek odwołuje się w ten sposob, dlatego chciałem zostawić odwołanie w taki sposób.

Użyj TList zamiast macierzy — nie będziesz musiał zmieniać wszystkich linijek zawierających odwołania do elementów, bo dostęp do elementów TList również uzyskuje się za pomocą nawiasów [ ], a że właściwość Items jest domyślną w tej klasie, to jej nazwy nie trzeba w ogóle pisać.

Nie ma Mnie teraz przy komputerze ale rozważam określić rozmiar tablicy ale zastanawiam się czy mogę wykonać na określonym rozmiarze steLength gdybym przekroczył ilość np. […]

Nie, nie ma takiej możliwości. Jeśli określasz rozmiar macierzy przy deklaracji, to deklarujesz macierz o stałym rozmiarze. Jeśli potrzebujesz zmieniać rozmiar to musisz skorzystać z macierzy dynamicznej.

0

tak ale potrzebuje jeszcze fv_lv1.item[0].Caption, fv_lv1.item[0].Subitems[2].. tego TList Mi nie da po to sa rekordy;
Wiec rozwiaznie rekordow jest idealne.

teraz potrzebuje jeszcze dobrego prostego sposobu na zapis i odczyt calej tablicy z fv_lv1 do pliku. (w fv_lv1 jest jeszcze item.)

myślę o czymś takim:

var
  Stream: TStream;

begin
  Stream:= TFileStream.Create(unit1.lok1+'test.dat', fmCreate);
  try
    Stream.WriteBuffer(fv_lv1, SizeOf(fv_lv1));
  finally
    Stream.Free;
  end;
end;
0

ok napisze od początku, mam rekord ktorego zawartosc chce zapisac do pliku a nastepnie odzytac z pliku:

//-> lv1 baza
r_items = record
pusty:byte; //czy wolny mozna uzyc? bo np skasowano - nie kasujemy lini tylko ustwiamy stan na 0
Caption:string;
Subitems:TStringList;
end;

r_lv1 = record
item:array of unit1.r_items;
end;
//<- lv1 - baza

tym rekordem jest:
r_lv1, w tym rekordzie znajduje sie rekord item.

0

Zamiast macierzy rekordów użyj generycznej listy rekordów.

Poza tym, zanim napiszesz kolejną linijkę kodu, najpierw kilka razy przeczytaj artykuł Object Pascal Style Guide, bo piszesz kod zupełnie niezgodnie z wszelkimi wytycznymi dotyczącymi pisania czytelnego kodu w Pascalu.

0

ok, bardzo dziękuje za pomoc, z rekordami sobie poradziłem. Teraz chce to zapisać w normalny sposób do pliku, założyłem oddzielny watek by nie mieszać.

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