Tablica struktur + wskaźniki

0

Mam taki problem, że tworzę aplikację w C# są tam dwie struktury, tak jak poniżej:

        public unsafe struct rekord
        {
            public uint offset;
            public uint klucz;
            public string wartosc;
            public uint numer;
            public bool zmodyfikowano;
            public bool nieprzebudowano;
            public bool zmieniono_klucz;
        };

        public unsafe struct baza_danych
        {
            public string nazwa_sekcji;
            public bool zmodyfikowano;
            public uint offset;
            public uint ilosc_rekordow;
            public unsafe rekord* dane;
        };

Jak widać w strukturze baza_danych będzie powstawać tablica struktur rekord. Z całości jeszcze chciałem zrobić jedną tablicę, a całość miała być na miarę jakiejś bazy danych. Tutaj kod tworzenia tablicy ze struktury baza_danych i tablic dane ze struktury rekord:

        public unsafe baza_danych* stworz_baze(uint rozmiar)
        {
            baza_danych *baza = stackalloc baza_danych[rozmiar];   
            for(uint i = 0; i < rozmiar; i++)
            {
                baza[i].nazwa_sekcji = "";
                baza[i].zmodyfikowano = false;
                baza[i].offset = 0;
                baza[i].ilosc_rekordow = 0;
                baza[i].dane = 0;
                return baza;
            }        
        }
        public unsafe rekord* stworz_rekord(uint rozmiar)
        {
            rekord *dane = stackalloc rekord[rozmiar];   
            for(uint i = 0; i < rozmiar; i++)
            {
                dane[i].offset = 0;
                dane[i].klucz = 0;
                dane[i].wartosc = "";
                dane[i].numer = 0;   
                dane[i].zmodyfikowano = false;
                dane[i].nieprzebudowano = false;
                dane[i].zmieniono_klucz = false;                    
            }       
            return dane; 

Niestety podczas próby kompilacji otrzymuję dwa błędy:
"Error 1 Cannot take the address of, get the size of, or declare a pointer to a managed type ('OpenENgine.rekord')"
"Error 2 Cannot take the address of, get the size of, or declare a pointer to a managed type ('OpenENgine.baza_danych')"

Nie mam za bardzo pojęcia co może być nie tak i co z tym zrobić...

1

public uint ilosc_rekordow;
public unsafe rekord* dane;

Gdyby to było w moim kodzie to znalazłbym osobę która to napisała i zatłukł monitorem...

Nie mam za bardzo pojęcia co może być nie tak i co z tym zrobić...

Co jest nie tak? Wszystko... Ja bym proponował przepisać od nowa...

Ok, a teraz dla odmiany coś konstruktywnego:

Skąd jest ten kod? Domyślam się że albo go skądś skopiowałeś (źle) albo wcześniej pisałeś w C/C++ (bardzo źle) - w C# nigdy nie trzeba używać wskaźników(*).

Jeśli już upierasz się przy obecnym kodzie, albo leżą za nim jakieś głębsze powody, użyj Marshal.SizeOf(obiekt) albo Marshal.SizeOf(typ)
Czyli np. Marshal.Sizeof(typeof(int)); zamiast sizeof(int)
Żeby to działało na górze pliku musisz dopisać using System.Runtime.InteropServices;

(*) bardzo, bardzo rzadko robi się od tego wyjątek i używa ich - w końcu po to są - w sytuacjach jak np. przetwarzanie obrazów gdzie rozwiązanie bez wskaźników jest wolne. Na razie... Forget about it.

0

Co jest nie tak? Wszystko...

Ekhmmm...

Skąd jest ten kod? Domyślam się że albo go skądś skopiowałeś (źle) albo wcześniej pisałeś w C/C++ (bardzo źle) - w C# nigdy nie trzeba używać wskaźników(*).

Nie skopiowałem go, przenosiłem go z C++ właśnie. Bardzo źle, że pisałem w C++, czy że ja źle pisałem? Bo akurat w C++ radzę sobie na dosyć przyzwoitym poziomie. Czytałem, że przy korzystaniu z bardzo dużej ilości dużych tablic znacznie przyspieszają działanie aplikacji, a ten program właśnie z takich tablic korzysta, łącznie chyba do maksymalnie 100 000 elementów. Ale czytałem też, że są dosyć problemowe więc chyba jednak zrezygnuję z nich na rzecz normalnego zapisu (baza_danych[] baza = new baza_danych[rozmiar];)...

Wracając do Marshal'a, to pewnie chodziło ci o to:

public uint ilosc_rekordow;
public unsafe rekord* dane;

Żeby mierzyć nim rozmiar tablicy dane, zgadza się? Wiem o tym, tylko tamten kod w C++ był póki co pisany na szybko, a po dwóch dniach okazało się, że będę musiał ten program pisać w C#, a nie C++;

0

Komuniści chcą dobrze, a wychodzi jak zwykle :P

Nie skopiowałem go, przenosiłem go z C++ właśnie. Bardzo źle, że pisałem w C++, czy że ja źle pisałem? Bo akurat w C++ radzę sobie na dosyć przyzwoitym poziomie.

Toteż poczytaj o podstawach C#. Nie używamy wskaźników, chyba że w wyjątkowych sytuacjach. W C# jest także różnica między struct i class. Warto popisać parę programików żeby się wczuć w ten język, zanim zacznie się przenosić program z zupełnie innego języka, o zupełnie innej filozofii :)

Żeby mierzyć nim rozmiar tablicy dane, zgadza się?

W C# każda tablica dziedziczy po typie Array, który zawiera m.in. właściwość* Length. Pisząc np. public rekord[] dane, możesz odczytać długość tablicy poprzez dane.Length.
Ogólnie do pisania baz danych mógłbym Ci polecić także typ generyczny List - jako lista jednokierunkowa, o dynamicznie ustalanym rozmiarze.

O C# można by dużo pisać, ale nawet nie próbuję doścignąć ideału, którym jest bswierczynski :P

  • o właściwościach też poczytaj, ciekawa rzecz
1

Wracając do Marshal'a, to pewnie chodziło ci o to:
public uint ilosc_rekordow;
public unsafe rekord* dane;
Żeby mierzyć nim rozmiar tablicy dane, zgadza się?

Akurat nie - lekko się machnąłem:

Cannot take the address of, get the size of, or declare a pointer to a managed type ('OpenENgine.rekord')

Nie wiem czemu założyłem że chodzi o "get the size of" i podałem rozwiązanie innego problemu niż twój ;).

Twój kod odpowiada dobremu kodu C++ - ale to co jest dobre w C++ niekoniecznie jest zalecane w C#, więc uważaj na to i naprawdę radzę Ci nie używać unsafe

Ale czytałem też, że są dosyć problemowe więc chyba jednak zrezygnuję z nich na rzecz normalnego zapisu (baza_danych[] baza = new baza_danych[rozmiar];)...

Idziesz w dobrym kierunku ;). Chociaż ja bym radził pójść jeszcze dalej i użyć List<rekord> rekordy = new List<rekord>();

0

Dzięki za rady mam tylko jeszcze jedno, chyba ostatnie, pytanie.

Oprócz plików - tych dla formatek - tworzę jeszcze jeden plik *.cs dołączam go od projektu, tworzę w nim przestrzeń namespace - taka jak nazwa projektu i mam w nim klasę:

public class OpenENgine

i - jak może zauważyliście - właśnie w niej są te struktury, o których pisałem wcześniej będzie też kilka funkcji.

Później w pliku Form1.cs deklaruje obiekt za pomocą:

OpenENgine.baza_danych[] baza;

Moje pytanie brzmi jak mam uruchamiać funkcje, które znajdują się w tamtym drugim pliku??? Bo ja biorę: OpenENgine.otworz_plik(parametry);

 to niestety nie działa.
0

Dobra, trochę poczytałem i co nieco już wiem. Poradziłem sobie także z funkcją klasy :). Mam tylko jeszcze jedno pytanie. Czy tworząc tablicę w sposób:

baza_danych[] baza = new baza_danych[rozmiar];

Program zwalnia pamięć dopiero po jego zamknięciu czy także po wywołaniu:

baza = null;

Bo wychodzi dokładnie to co mówiłem, że otwierając i zamykając po kolei 3 pliki tym moim edytorem użycie pamięci sięga 60-70 MB. Więc dosyć sporo...

No i chyba zrezygnuję jednak z tablic i zrobię to na listach struktur tak jak pisał MSM. ;)

0

Witaj,

No właśnie ze zwalnianiem pamięci w C# jest różnie. Nikt nie powie Ci, ze pamięć zostanie zwolniona na 100%
Do tego masz Garbage Collector. Teoretycznie jeśli do swojego obiektu przypiszesz null i nie ma do niego żadnej referencji, To zostanie on zwolniony... teoretycznie.

Taka jeszcze moja dobra rada, jeśli przesiadasz się z C++ na C# to poczytaj dokładnie co i jak jest przekazywane przez referencję, a co przez wartość.

Zauwaź, ze coś co nie jest typem prostym, przekazywane jest do metody przez referencję, struktury... przez wartość.
http://msdn.microsoft.com/en-us/library/ah19swz4(v=vs.71).aspx

Ach i jeśli korzystałeś z STL'a i typów generycznych to polecam w C#

System.Collections;
System.Collections.Generic;
0

W pierwszym poście pokazałem na strukturach jak chciałbym, żeby wyglądał zbiór danych. Możecie mi zasugerować jaki sposób realizacji byłby najlepszy/najbardziej wydajny? Tablice, listy, typy generyczne? Bo tak jak już pisałem program będzie przetwarzał spore ilości danych więc chciałbym żeby to robił w miarę szybko, przykładowo otwarcie pliku w C++ na wskaźnikach zajmuje ok 1500 ms, natomiast w C# na tablicach już około 4000 ms - biorąc pod uwagę ten sam algorytm. No i chodzi mi też o zużycie pamięci.

1

Bo tak jak już pisałem program będzie przetwarzał spore ilości danych więc chciałbym żeby to robił w miarę szybko, przykładowo otwarcie pliku w C++ na wskaźnikach zajmuje ok 1500 ms, natomiast w C# na tablicach już około 4000 ms - biorąc pod uwagę ten sam algorytm. No i chodzi mi też o zużycie pamięci.

Pokaż to otwarcie pliku w C++ i najlepiej twoją wersję w C# bo skoro jest taka różnica to IMO znaczy że robisz coś źle/niedoskonale ;).
edit: Albo nie, prawdopodobnie po prostu testujesz wersję debug - różnica w szybkości wersji debug i release może sięgać skrajnie nawet kilkudziesięciu razy...

Program zwalnia pamięć dopiero po jego zamknięciu czy także po wywołaniu: baza = null;

Co do zwalniania pamięci - popatrz na ten kod:

int[] tab;
for(int i = 0; i < 100000; i++)
{ tab = new int[100000]; }

Jako że pisałeś w C++ i nie masz doświadczenia z GC prawdopodobnie padłeś ;) - tak naprawdę nic się tutaj złego nie dzieje (ofc poza tym że zupełnie bezsensownie marnujemy czas w nicnierobiącej pętli).

W 99% przypadków nie musisz (a nawet nie powinieneś) się przejmować zwalnianiem pamięci, bo pamięć zostanie zwolniona automatycznie, po straceniu wszystkich referencji do obiektu.

Ach i jeśli korzystałeś z STL'a i typów generycznych to polecam w C#

System.Collections;
System.Collections.Generic;

Wyrzuć z tej listy System.Collections - praktycznie co do jednej stały się przestarzałe razem z wejściem 2.0.

No i na sam koniec...

W pierwszym poście pokazałem na strukturach jak chciałbym, żeby wyglądał zbiór danych. Możecie mi zasugerować jaki sposób realizacji byłby najlepszy/najbardziej wydajny?

Użyj listy. Niech Cię nazwa nie zmyli, List<T> (z System.Collections.Generics) jest bliskim odpowiednikiem vector<T> z C++

0

Listy genryczne to dobry pomysł.

Możesz w nich trzymać dowolny typ (nie tylko typy proste)

Przemyśl użycie struktur C#. Pamiętaj, że te mogą mieć konstruktor i nawet metody. Jeśli mają nieskomplikowaną logikę, to ważą mniej niż obiekty klasy.
Pamiętaj, że po strukturze nie możesz dziedziczyć, ale zawsze łatwo w razie potrzeby możesz zmienić je na klasę.

Tutaj znajdziesz więcej info. http://msdn.microsoft.com/en-us/library/ah19swz4%28v=vs.71%29.aspx

Jak będziesz miał wyniki wydajności to chętnie je obejrzymy. Szczerze to sam jeszcze nie robiłem takiego porównania.

Pozdrawiam.

0

MSM miał rację, po uruchomieniu aplikacji z wersji Release funkcje działają z porównywalną szybkością:

Operacja C++ C# - Debug C# - Release
Odczyt pliku binarnego ~1600 [ms] ~4500 [ms] ~1700 [ms]
Odczyt pliku tekstowego 1 ~150 [ms] ~200 [ms] ~140 [ms]
Odczyt pliku tekstowego 2 ~80 [ms] ~120 [ms] ~80 [ms]
Zapis pliku binarnego ~600 [ms] ~4500 [ms] ~700 [ms]
Zapis pliku tekstowego 1 ~250 [ms] ~250 [ms] ~220 [ms]
Zapis pliku tekstowego 2 ~150 [ms] ~200 [ms] ~160 [ms]
Byłoby ok i bym już nie ruszał tych funkcji, tylko strasznie irytuje mnie zużycie pamięci. Wykonuje taki ciąg operacji: odczyt binarny, zapis binarny, zapis tekstowy 1, zapis tekstowy 2, odczyt tekstowy 1, zapis tekstowy 1, odczyt tekstowy 2 i zapis tekstowy 2. Wynik - zużycie pamięci na poziomie 60-70 MB. Przy każdym odczycie pliku robię:
baza_danych[] baza = new baza_danych[rozmiar];

bo ponoć pamięć jest sama zwalniana... Ale mimo wszystko trochę lipa...

Więc biorę się za te listy generyczne no i ok. Tak jakby pierwszy poziom z listy - baza_danych - wypełniam:

            baza = new List<baza_danych>();
            baza_danych baza_t = new baza_danych();
            baza_t.zmodyfikowano = false;
            baza_t.dane = new List<rekord>();
            for (int i = 0; i < info.ilosc_sekcji; i++)
            {
                baza_t.nazwa_sekcji = null;
                for (uint j = 0; j < 8; j++)
                {
                    if (data[offset] != 0)
                        baza_t.nazwa_sekcji += System.Text.Encoding.ASCII.GetString(data, offset, 1);
                    offset++;
                }
                baza_t.offset = BitConverter.ToInt32(data, offset);
                offset += 4;
                baza.Add(baza_t); 
            }

I później jadę pętle dla wszystkich elementów listy baza, czyli wypełniam drugi poziom - rekord.

            for (int index = 0; index < baza.Count; index++)
            {
                rekord rekord_t = new rekord();
                for (int i = 0; i <= ilosc_rekordow; i++) 
                {
                    rekord_t.offset = jakas_liczba;
                    rekord_t.klucz = jakas_liczba;
                    baza[index].dane.Add(rekord_t);              
// niestety plik jest tak skonstruowany, że najpierw są wszystkie wartości offset i klucze, a później na podstawie tego odczytuje się resztę;
                }
                for (int i = 0; i < baza[index].dane.Count; i++)
                {
                    rekord_t.klucz = baza[index].dane[i].klucz;
                    rekord_t.offset = baza[index].dane[i].offset;
                    rekord_t.numer = jakas_liczba;
                    rekord_t.wartosc = ciag_znakow;
                    rekord_t.zmodyfikowano = false;
                    rekord_t.nieprzebudowano = false;
                    rekord_t.zmieniono_klucz = false;
                    baza[index].dane[i] = rekord_t;
                }
            }

Tak wygląda "mniej więcej" kod odczytu do wewnętrznej listy rekord, niestety program mi się zawiesza i nie wiem czy to z przyczyn złego wpisywania do list czy z powodu niedopracowanego algorytmu... Poza tym mam takie pytanie, jeśli wykonuję tą linijkę "baza[index].dane[i] = rekord_t;", to do pola "baza[index].dane[i]" zostaną zapisane ostatnie wartości rekord_t.klucz oraz rekord_t.offset wiec musiałem je wyciągnąć z bazy i przypisać jeszcze raz, tak mi się wydaje... To jest raz, a dwa to pytanie odnośnie tego:

System.Text.Encoding.ASCII.GetString(data, offset, 1);

Wiem, że jest funkcja:BitConverter.ToChar(byte[]);

Ale ona konwertuje dokładnie dwa bajty na Unicode char, a mi chodzi o konwertowanie tylko jedego bajta na char. Czy znacie jakiś inny sposób, bo na nic innego nie mogę wpaść...
0

Słuchajcie, poczytałem jeszcze przeanalizowałem wszystko i działa. Z tym, że struktury zamieniłem na klasy :). Przekonałem się, że tak będzie lepiej. Powiem wam jak teraz zmieniła się wydajność:

Operacja C++ - wskaźniki C# - tablica struktur C# - lista klas
Odczyt binarny ~1600 [ms] ~1700 [ms] ~2200 [ms]
Zapis binarny ~600 [ms] ~700 [ms] ~900 [ms]
Czas otwierania i zapisywania plików binarnych jest dłuższy do około 30% w porównaniu z wersją tablica struktur i do 35% w porównaniu ze wskaźnikami z C++. Wersji tekstowej nie brałem pod uwagę, bo tam wszystko się dzieje na poziomie do 300-400 [ms]. Max zużycie pamięci sięga 35-45 MB a nie 60-70 MB jak w poprzedniej wersji więc tu z kolei mam zysk rzędu do 35% :).

Wiem jak skrócić czas zapisu z tych 900 ms do mniej więcej 300-400, czyli poziomu plików tekstowych oraz czas odczytu do poziomu tego z C++ albo jeszcze niżej.

Tylko teraz tak. Może mi ktoś wyjaśnić i/albo pomóc/powiedzieć jak to rozwiązać, że taki kod:

        ... 
        Encoding utf7 = Encoding.UTF7;
        utf7.GetBytes(tekst).CopyTo(data, start);
        start += tekst.Length;
        ...

kopiuje do tablicy data typu byte przykładowo 26 bajtów, a długość tekstu w zmiennej tekst wynosi 22? Dodam, że wpisując pojedynczo do tablicy data wszystko jest ok... Bo właśnie o to chodzi, że przenosząc od razu cały tekst do tablicy da się skrócić właśnie o tyle czas zapisu pliku. Podczas osczytu sprawa ma się podobnie, ale tam sprawa raczej jest rozwiązana...

0

Byłoby ok i bym już nie ruszał tych funkcji, tylko strasznie irytuje mnie zużycie pamięci.
Taka cena.

kopiuje do tablicy data typu byte przykładowo 26 bajtów, a długość tekstu w zmiennej tekst wynosi 22?
kodowanie UTF-7 po pierwsze jest „przestarzałe” (czyt. niezalecane), chyba że musisz. jeśli nie, użyj UTF-8.
druga sprawa, to w kodowaniach tego typu (UTF-8 i UTF-16 też) jeden znak zajmuje zmienną ilość bajtów. dlatego możesz otrzymywać takie wyniki.

0

Struktur używa się wyłącznie w specyficznych przypadkach. W szczególności, struktury większe niż 16 bajtów mogą być wolniejsze od klas, trzeba o tym pamiętać.

http://msdn.microsoft.com/en-us/library/ah19swz4%28v=vs.71%29.aspx
http://msdn.microsoft.com/en-us/library/0taef578.aspx
http://stackoverflow.com/questions/521298/when-to-use-struct-in-c

Co z tego, że program zajmuje 70MB pamięci? Głupie GG zajmuje pewno więcej. A dzisiejsze komputery mają jeszcze więcej. Brakuje Ci jej?
Zwolnienie pamięci można próbować wymusić przez GC.Collect().

W jaki sposób odczytujesz pliki? Bo jeśli w pętli po bajcie czy linijce, to można to nieźle przyspieszyć. :)

0

@Azarien - niestety muszę się męczyć z UTF-7, bo w takim kodowaniu jest plik... No ale Unicode mi dobrze działa, a w C# Unicode to przecież UTF-16... A tak w ogóle to da się temu zaradzić czy muszę pojedynczo wałkować tekst?

@somekind - wiem, ale staram się, aby program był jak najbardziej optymalny. O GC.Collect(); też wiem :). Plik odczytuję tak:

        void otwieranie_pliku_bin(ref FileStream input, ref byte[] data, int rozmiar)
        {
            input.Read(data, 0, rozmiar);
        }

Bez żadnych pętli, ani po linijce. I później odpowiednio segreguję tablicę data do Listy klas baza_danych.

0
komunista napisał(a)

No ale Unicode mi dobrze działa, a w C# Unicode to przecież UTF-16...

Jesteś pewien, że UTF-16, a nie UTF-8?

0

UTF-16 jest ustawione jako Unicode.

0

@ massther - przecież metoda FileStream.Read() przyjmuje 3 argumenty - tablice typu byte, offset i rozmiar tabicy byte więc co najwyżej mogę zamienić rozmiar, który był rozmiarem tablicy defacto podawanym w parametrze funkcji - tu widzisz moje przeoczenie, bo w C++ rozmiaru tablicy nie dało się tak odczytać - na data.Length. To wszystko.

0

Ogólnie do pisania baz danych mógłbym Ci polecić także typ generyczny List - jako lista jednokierunkowa, o dynamicznie ustalanym rozmiarze.

Myślę, że lista jednokierunkowa jest zaimplementowana jako klasa LinkedList<T>

Do tego masz Garbage Collector. Teoretycznie jeśli do swojego obiektu przypiszesz null i nie ma do niego żadnej referencji, To zostanie on zwolniony... teoretycznie.

..ale tylko przy następnej okazji dla GC (niekoniecznie zaraz po linijce cos=null)

baza = new List<baza_danych>();
baza_danych baza_t = new baza_danych();
baza_t.zmodyfikowano = false;
baza_t.dane = new List<rekord>();
for (int i = 0; i < info.ilosc_sekcji; i++)
{
baza_t.nazwa_sekcji = null;
for (uint j = 0; j < 8; j++)
{
if (data[offset] != 0)
baza_t.nazwa_sekcji += System.Text.Encoding.ASCII.GetString(data, offset, 1);
offset++;
}
baza_t.offset = BitConverter.ToInt32(data, offset);
offset += 4;
baza.Add(baza_t);
}

Niewielkim przyspieszeniem tutaj może być podanie dla listy wstępnej liczby elementów, jaka ma być docelowo dla niej dodana.

Czyli:

aza_t.dane = new List<rekord>(info.ilosc_sekcji);

Lista tak naprawdę jest tylko klasą, która udaje łatwo rozszerzalny kontener, a tak naprawdę operuje na tablicy. Raz na jakiś czas, gdy już wewnętrzna tablica jest zapełniona, lista tworzy nową tablicę o rozmiarze dwukrotnie większym niż aktualna. Jeśli podasz od razu liczbę elementów, nie będzie trzeba "dynamicznie" kopiować wewnętrznej tablicy.

void otwieranie_pliku_bin(ref FileStream input, ref byte[] data, int rozmiar)
{
input.Read(data, 0, rozmiar);
}

Po co ref???

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