Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?

Koszt dynamicznej alokacji pamięci i kompatybilność new/malloc + funkcje z C w standardzie C++

Ostatnio zmodyfikowano 2017-07-29 16:29
Autor Wiadomość
RazzorFlame
Temat założony przez niniejszego użytkownika
Koszt dynamicznej alokacji pamięci i kompatybilność new/malloc + funkcje z C w standardzie C++
» 2017-07-29 12:06:29
Cześć, tym razem wpadłem na forum, by zapytać bardziej doświadczonych programistów jak to jest z tym new/malloc, pomijając placement new na stosie. Do niedawna myślałem, że new to taki opakowany w wywoływanie konstruktorów malloc (co w rzeczywistości jest bliskie prawdzie) a z kolei o malloc myślałem jak o jakiejś magicznej funkcji, wykorzystującej specjalnie zaprogramowane funkcje systemu operacyjnego do otrzymania adresu. Wczoraj sobie o tym poczytałem i dowiedziałem się, że malloc to tak naprawdę potężny system do zarządzania pamięcią, który sam przechowuje dane o tym o ile sobie zaciągnął nowej pamięci ze sterty, gdzie trzyma jakie dane, jakiej wielkości itp co w rzeczywistości budzi we mnie wątpliwość w wydajność takiej funkcji. Mówi się, że między korzystaniem ze stosu a korzystaniem ze sterty jest tylko niewielka różnica w czasie, prawie niezauważalna, ale patrząc na to jak działa malloc widać, że koszt tej alokacji jednak jest dość spory. Nie mówię o powiększaniu puli adresów przez sbrk() czy VirtualAlloc() ale o samym algorytmie sprawdzającym, gdzie umieścić żądane dane - tak jak mówię - wcześniej myślałem, że to system się tym sam zajmuje. Jak jest naprawdę?

I drugie pytanie - jak to możliwe, że można mieszać malloc z new, skoro obydwie mają swój, niekoniecznie kompatybilny, sposób na korzystanie ze sterty? Nie mówię o sytuacji, w której alokujemy pamięć przez malloc a zwalniamy przez delete ani na odwrót - o alokacji przez new i zwolnieniu przez free() - nie. Wiem, że to niebezpieczne i chyba tylko przypadkowo może zadziałać, ale chodzi mi o sytuację, gdy w jednym programie używa się raz malloc raz new a obydwa współgrają ze sobą, choć mają inny sposób zarządzania pamięcią - przecież sbrk() i VirtualAlloc() dotyczą całego procesu - czemu więc new nie zepsuje działalności malloc i na odwrót?

Czekam na jakieś ciekawe i rozbudowane wypowiedzi, bo to raczej interesujący temat :)
P-163680
mokrowski
» 2017-07-29 14:29:12
Oto rozbudowana odpowiedź -> https://randomascii.wordpress.com/2014/12/10/hidden-costs-of-memory-allocation/
P-163682
RazzorFlame
Temat założony przez niniejszego użytkownika
» 2017-07-29 14:51:57
@mokrowski, no to mnie zaskoczyłeś idealnie artykuł, który odpowiada na moje pierwsze pytanie.
Co do reszty - tak, czekam na Twoją odpowiedź pekfos xd, tak samo jak od DejaVu, Moniki, Elaine lub jakiegoś innego mastera :)
P-163683
pekfos
» 2017-07-29 14:57:20
I drugie pytanie - jak to możliwe, że można mieszać malloc z new, skoro obydwie mają swój, niekoniecznie kompatybilny, sposób na korzystanie ze sterty?
Nie rozróżniałbym ich aż tak bardzo. new alokuje pamięć i wywołuje konstruktory i rzuca wyjątki (lub nie) jeśli alokacja się nie powiedzie. Ale skąd pamięć?
void* __CRTDECL operator new(size_t const size)
{
01532820  push        ebp 
01532821  mov         ebp,esp 
01532823  push        ecx 
    for (;;)
    {
        if (void* const block = malloc(size))
01532824  mov         eax,dword ptr [size] 
01532827  push        eax 
01532828  call        _malloc (0134BDF6h) 
0153282D  add         esp,4 
Choćby i z malloc(). A nawet jeśli nie i są różne mechanizmy zarządzania pamięcią dla malloc() i wtfalloc(), to nie zapominaj że pamięć podaje i tak system. Złożone mechanizmy dodają warstwę oddzielającą program od systemu w tej kwestii, dzięki czemu może być np jakaś pula pamięci wyciągnięta od systemu wcześniej i te funkcje sobie nimi zarządzają po swojemu, żeby zoptymalizować alokowanie małych obiektów. Mieszając te 2 funkcje ze sobą faktycznie będziesz mieć niekompatybilne między sobą alokacje i może zmarnujesz trochę pamięci na utrzymywanie dwóch systemów alokacji, ale co z tego? I tak nie wolno mieszać różnych alokacji z dealokacjami, a strata pamięci jest i tak niewielka. To nie Java, że jest tylko pamięć z prywatnej puli i start programu wciąga 500 MB. Pomimo tego, że biblioteka standardowa C++ zawiera w sobie bibliotekę std. C, standard (18.6.1.1) nie nakłada na implementację new wymagań co do użycia malloc().
Whether the attempt involves a call to the Standard C library function malloc is unspecified.

a z kolei o malloc myślałem jak o jakiejś magicznej funkcji, wykorzystującej specjalnie zaprogramowane funkcje systemu operacyjnego do otrzymania adresu. Wczoraj sobie o tym poczytałem i dowiedziałem się, że malloc to tak naprawdę potężny system do zarządzania pamięcią, który sam przechowuje dane o tym o ile sobie zaciągnął nowej pamięci ze sterty, gdzie trzyma jakie dane, jakiej wielkości itp co w rzeczywistości budzi we mnie wątpliwość w wydajność takiej funkcji.
Aha, czyli teraz to malloc to potężny system, a system operacyjny to tylko widły do przerzucania pamięci z jednej sterty na drugą? ;) W sumie dość trafne, bo system nie musi nawet dbać o to, czy zamówione n kilobajtów to ciągły obszar pamięci, czy nie.
Co do tego czegoś o wątpliwości i  wydajności: ilość i złożoność mechanizmów nie wpływa negatywnie na wydajność. Oczywiście, malloc() nigdy nie będzie tak szybki jak sub esp, a przynajmniej do czasu, gdy faktyczna robota nie zostanie oddelegowana do jakiegoś garbage collectora. Zwiększenie ilości kodu i stopnia komplikacji to nie potrzeba optymalizacji, tylko jej efekt uboczny. W końcu gdyby to wszystko było wolniejsze od zwykłego zapytania systemu, to by tam było zwykłe zapytanie systemu.
P-163684
RazzorFlame
Temat założony przez niniejszego użytkownika
» 2017-07-29 15:42:44
Mieszając te 2 funkcje ze sobą faktycznie będziesz mieć niekompatybilne między sobą alokacje i może zmarnujesz trochę pamięci na utrzymywanie dwóch systemów alokacji, ale co z tego?
Dobra, teraz hipotetycznie zakładamy, że new w naszym przypadku nie korzysta z malloc tylko ma do tego własną implementację. new teraz nie wie, że malloc też zarządza pamięcią, która w końcu jest wspólna. Powiedzmy, że mamy na starcie dostępne jakieś N bajtów do użytku. malloc, jeszcze przed new umieścił zaalokowane dane w tym dostępnym miejscu, ale teraz wchodzi new i nic o tym nie wie, dla niego jest to po prostu jakaś pula adresów z przypadkowymi danymi. Dlaczego w takim razie new nie uzna, że może przyznać używany już adres przez dane zaalokwane przez malloc, skoro dla niego tam nic teoretycznie nie ma? Jest jakiś specjalny mechanizm napisany do kooperacji new i malloc, żeby na siebie nie nachodziły?

I jeszcze jedno, bo właśnie weszliśmy na temat używania funkcji z C w C++. Za żadne skarby nie potrafiłem znaleźć jakiegoś większego artykułu z tego czy dłuższych wypowiedzi na stackoverflow, więc jeśli już tu jesteśmy to tak:
Pewni osobnicy niedawno kłócili się ze mną, że standard C++ całkowicie jest napisany od zera, że nigdzie nie używa funkcji z C przy tym mnie wyśmiewając, że "ale teraz to doje*****, hahah" ech.
To co ja znalazłem to:
- cout używa putchar (nie zawsze, ale często też ma po prostu kod kopiuj wklej z tej funkcji)
- std::stoull wywołuje funkcję z C strtoull
- teraz do tego malloc wywoływane jest przez new (nie zawsze)
Znasz/znacie inne przykłady?
Wiem że jeden temat jeden problem, w takim razie żeby było formalnie wszystko to apdejtuje nazwę tematu.
P-163685
pekfos
» 2017-07-29 15:47:50
new teraz nie wie, że malloc też zarządza pamięcią, która w końcu jest wspólna.
A system operacyjny? Też zarządza pamięcią, to już jest ich trzech! Aplikacja ma swoją przestrzeń adresową, ale to nie jest tak, że to malloc nią zarządza. Jeśli malloc chce pamięci, żeby sobie nią rządzić, to pyta o nią system - dostaje zmapowane trochę pamięci pod jakiś zakres adresów. Ta pamięć fizyczna i te adresy w przestrzeni aplikacji są wtedy zajęte. Ten drugi system też musi zapytać o pamięć, bo przecież jej sam nie wyczaruje. Dostanie inną pamięć, pod innym adresem.
Faktyczna pamięć jest podzielona na strony, często rozmiaru 4 kilobajtów. System przydziela pamięć całymi stronami. Zaawansowane zarządzanie pamięcią jest po to, żeby:
  • Nie marnować pamięci alokując jedną liczbę na 4 kilobajtach
  • Nie pytać systemu o każdą alokację
To się osiąga własnym zarządzaniem pamięcią, jak np w malloc, ale to czym się zarządza po swojemu to już jest zaalokowana pamięć, wzięta od systemu. Nikt jej nie dostanie inaczej, niż za pośrednictwem tego konkretnego API do alokowania.
P-163686
RazzorFlame
Temat założony przez niniejszego użytkownika
» 2017-07-29 16:03:01
Okej, teraz rozwiałeś już wszystkie moje wątpliwości co do zarządzania pamięcią, dzięki.
A jak z używaniem C w języku C++? To nie jest tak, że standard to taki wielki "wrapper"? < tutaj oczywiście mam na myśli, że po prostu standard C++ często używa funkcji z C, jednak też nie do przesady - to co się da lepiej zaimplementować robi od nowa. Jeśli tak to jaki jest sens w nazywaniu C++ całkowicie odrębnym językiem od C? Często widzę takie posty - "Coś ty, C++ i C to całkowicie co innego", gdy w rzeczywistości to C wzbogacony o trochę przydatnych elementów składni i większą bibliotekę standardową - czyli jednak ma dużo wspólnego z C.
P-163687
pekfos
» 2017-07-29 16:08:30
Pewni osobnicy niedawno kłócili się ze mną, że standard C++ całkowicie jest napisany od zera, że nigdzie nie używa funkcji z C przy tym mnie wyśmiewając, że "ale teraz to doje*****, hahah" ech.
Najlepszy przykład to pewnie preprocesor, to się chyba w ogóle nie zmieniło. Co do samego użycia funkcji z C, to zacznijmy od tego, że standard nie definiuje, jak cała masa rzeczy ma być zaimplementowana na poziomie, jakiej funkcji należy użyć. Za to standard mówi, że funkcje z C są dostępne w C++, zmodyfikowane dla lepszej kontroli typów, albo jakie nazwy mają być zadeklarowane w jakich nagłówkach, bo i też mają inne nazwy niż w C. Czy to już się liczy jako użycie funkcji?
The constructor is strstreambuf(s, n, s + std::strlen(s))
Tu mam jakiś wyrwany z kontekstu fragment.
Btw, jaki dokładnie argument wywołał taką reakcję? :P

"Coś ty, C++ i C to całkowicie co innego", gdy w rzeczywistości to C wzbogacony o trochę przydatnych elementów składni i większą bibliotekę standardową - czyli jednak ma dużo wspólnego z C.
To są różne języki. Nazywanie C++ rozszerzeniem C jest błędne, bo to by sugerowało, że poprawny kod C jest poprawnym kodem C++, a tak nie jest. C++ i C są jednak bardzo podobne do siebie i można bez problemu napisać kod, który zadziała jako C++ i jako C. Jest przecież makro __cplusplus specjalnie po to, żeby współdzielić kod języka C i C++.
P-163688
« 1 » 2
  Strona 1 z 2 Następna strona