Zobrazit předchozí téma :: Zobrazit následující téma |
Autor |
Zpráva |
Vilem Otte

Založen: 18. 09. 2007 Příspěvky: 462 Bydliště: Znojmo - Sedlesovice, Kravi Hora
|
Zaslal: 20. duben 2012, 21:23:21 Předmět: Aligned structures a Windows |
|
|
Tento post je především myšlen pro další potenciální "oběti" dobrého "vtipu" MS Windows.
Představte si velmi jednoduchou třídu standardního float4:
kód: |
class float4
{
union
{
struct { float x, y, z, w; };
__m128 xmm;
};
/* Operatory, metody, ... */
};
|
Což je standardní implementace optimalizované float4 třídy. Co by však člověka rozhodně nenapadlo je, že jednoduchá alokace takové třídy jako:
kód: |
float4 data = (float4*)malloc(sizeof(float4) * data_size); |
případně
kód: |
float4 data = new float4[data_size]; |
opravdu není na 32-bit Windows dobrá. Samozřejmě lze použít _aligned_malloc a _aligned_free podobně jako posix_memalign na systémech s posix_standardem, nicméně tyto funkce nejsou přenosné mezi různými systémy.
Proč program při pokusu o zápis tedy padá?
Jednoduše řečeno, máme li paměť s adresami:
kód: |
|| | | | | | | | || | | | | | | | || | | | | | | | || | | | | | | | |
00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f
|
A alokujeme pamět na adrese 08 o velikosti 16-byte, nelze do ní zapsat tuto třidu (jelikož __m128 datový typ je alignován na 16-byte boundaries, a tedy adresa paměti do které se zapisuje musí být dělitelná 16 beze zbytku).
To znamená, že v tomto připadě lze danou třídu zapsat pouze na adresy 00 - 0f, nebo 10 - 1f. Nikoliv od 08 do 17.
Každý by čekal, že systém se postará o to, že paměť se bude alokovat tak, aby do ní bylo možné alignovaně zapisovat všechny základní data typy - jenže omyl, Microsoft nepovažuje __m128 (ostatně také velikost registru SSE jednotky, která je dnes na každém procesoru x86 architektury, už nějakých těch 10 let) za základní, a alokuje paměť na 8-byte hranicích.
Jak toto vyřešit?
Řešení je velmi triviální, buď tedy použít OS specifický alokátor (a doufat, že nikdy nebudete chtít přenášet kód). Případně minimalistické (a velmi robustní) řešení ála _mm_malloc, které je pod GPL licencí, nebo případně si napsat vlastní alokátor (což má obrovskou výhodu ve chvíli, kdy si chcete sledovat vlastní paměť).
Jednoduchý příklad jak vypadá takový alokátor v mé knihovně pro real-time ray tracing (non debug verze - implementace je bez dalších informací k paměti prakticky totožná s _mm_malloc) - http://www.pasteall.org/31086/c _________________ Should array indices start at 0 or 1? My compromise of 0.5 was rejected without, I thought, proper consideration. |
|
Návrat nahoru |
|
 |
Crypton

Založen: 14. 05. 2009 Příspěvky: 306 Bydliště: The Void
|
Zaslal: 20. duben 2012, 22:38:46 Předmět: Re: Aligned structures a Windows |
|
|
Tohle rozhodně nebude chyba na straně MS Windows, či MSVC. Za prvé ti tam chybí explicitně nastavené zarovnání u deklarace té třídy (__declspec(align(16)) popř. __attribute__ ((aligned (16))) ), takže instance alokované na zásobníku budou špatně zarovnané, a za druhé, alokace takových tříd se standardně provádí přes přetížený operator new (tj. přímo v té třídě), kde si můžeš tu paměť alokovat pomocí standardního globálního operatoru new, a zarovnat si tam tu paměť podle sebe (takže to bude přenosné).
A za třetí, to zarovnání u té proměnné xmm sice bude 16 bytů, ale relativně k adrese paměti alokované pro instanci té třídy float4.
Taky záleží jaké operace nad těmi daty provádíš, a jaké instrukce k tomu použiješ, některé totiž vyžadují zarovnání 16 bytů, a některé zase ne (movaps vs. movups).
Není pak divu že ti to hází segfaulty..  _________________
 |
|
Návrat nahoru |
|
 |
Vilem Otte

Založen: 18. 09. 2007 Příspěvky: 462 Bydliště: Znojmo - Sedlesovice, Kravi Hora
|
Zaslal: 21. duben 2012, 02:02:19 Předmět: |
|
|
citace: |
Tohle rozhodně nebude chyba na straně MS Windows, či MSVC. Za prvé ti tam chybí explicitně nastavené zarovnání u deklarace té třídy (__declspec(align(16)) popř. __attribute__ ((aligned (16))) ) |
Promiň, jen jsem ho tam zapomněl napsat (do zkrácené verze - celá třída je poměrně dlouhá, samozřejmě ten tam je). Ostatně on tam vlastně ani být nemusí, jelikož xmm bude zarovnaný na 16-byte boundaries... a tím pádem i ta třída bude takto zarovnaná (resp. jejích prvních 16-byte je třeba zarovnat na 16-byte boundaries - ona má však pouze 16-byte velikost).
citace: |
alokace takových tříd se standardně provádí přes přetížený operator new (tj. přímo v té třídě) |
Toto samozřejmě je možné (záleží na kódu, na stylu programování, atd.) - nicméně ten alokátor bude vypadat stejně jako kód, který jsem linkoval (ten _rt_malloc a rt_free). Bude třeba také vše zarovnat na 16-byte boundaries.
citace: |
A za třetí, to zarovnání u té proměnné xmm sice bude 16 bytů, ale relativně k adrese paměťi alokované pro instanci té třídy float4. |
Dle MSVC:
kód: |
// data_types__m128.cpp
typedef struct __declspec(intrin_type) __declspec(align(16)) __m128 {
float m128_f32[4];
} __m128; |
Je __m128 datový typ zarovnaný na 16-byte boundaries.
Doporučuji ti zkusit ve Visual Studiu jeden malý test - opravdu to nefunguje http://pasteall.org/31093/c (za předpokladu že doběhne ten cyklus a systém ti nealokuje VŠECHNY prostředky na 16-byte boundaries, což mě ani Windows 7 Ultimate a ani Windows XP Professional nedělají (program je 32-bit) Debian linux je 64-bit a samozřejmě pak má systém defaultně 16-byte boundaries v paměti (nikoliv 8-byte) a zacyklí se, pokud však aplikaci zkompiluji jako 32-bit, tak také vyhodí chybu.
Dále, k movaps a movups - měl bys vědět, že __m128 je striktně zarovnaný na 16-byte boundary (proto ten __declspec(align(16))), jelikož do XMM0 - XMM7 registrů se načitá vždy pomocí instrukce MOVAPS, jelikož unaligned-loading je příliš pomalý.
Takže pokud bych to nechal jako samotné __m128, musí být paměť striktně zarovnaná na 16-byte boundaries.
Zabalím li to do třídy - nejjednoduší (takřka stejný) příklad - http://www.pasteall.org/31095/c/ - dopadne úplně stejně (a to ať tam nechám atribut pro alignment, či ne).
Zkusil jsem to projet i valgrindem a ten chybu nejen že nenajde, on dokonce zabrání tomu, aby došlo k segfaultu a vesele to bez problému projde
citace: |
instance alokované na zásobníku budou špatně zarovnané |
Nope, nebudou. Jelikož právě __attribute__((aligned(16))) či __declspec((align(16))) způsobí, že na stacku se budou zarovnávat do 16-byte (a ten si třída převezme od __m128 data typu, příp. se může zadat ručně). To však neprovedou pro heap - tam se to musí provést ručně. _________________ Should array indices start at 0 or 1? My compromise of 0.5 was rejected without, I thought, proper consideration. |
|
Návrat nahoru |
|
 |
Crypton

Založen: 14. 05. 2009 Příspěvky: 306 Bydliště: The Void
|
Zaslal: 21. duben 2012, 02:59:34 Předmět: |
|
|
Ano, ten typ __m128 je zarovnaný na 16 bytů, ale když ho máš jako proměnnou ve třídě, tak to zarovnání té proměnné v paměti je relativní k začátku té alokované paměti pro tu instanci té třídy, takže pokud ti alokátor vyplivne kus paměti který je zarovnaný jen na 8 bytů, tak ta proměnná xmm nebude zarovnaná na 16 bytech.
V tom příkladě alokuješ nezarovnaný blok paměti (který je i tak malý, protože ti tam chybí rezerva na zarovnání toho ukazatele), který pak přetypuješ na instanci té třídy, takže proto ti to padá.
 _________________
 |
|
Návrat nahoru |
|
 |
Vilem Otte

Založen: 18. 09. 2007 Příspěvky: 462 Bydliště: Znojmo - Sedlesovice, Kravi Hora
|
Zaslal: 21. duben 2012, 03:26:21 Předmět: |
|
|
citace: |
V tom příkladě alokuješ nezarovnaný blok paměti (který je i tak malý, protože ti tam chybí rezerva na zarovnání toho ukazatele), který pak přetypuješ na instanci té třídy, takže proto ti to padá. |
Ano, a to je ta pointa, na kterou jsem psal řešení (které funguje).
Implicitně to téma bylo myšleno, že na standardní typ __m128 o velikosti simd sse registru (který je v procesoru už od dob Pentium 3, a je v architektuře standardním typem) nemohu zavolat standardní malloc (což mi samo o sobě přijde divné).
EDIT: Dle specifikace, pokud tedy budu chtít do takového (nealignovaného) __m128 načítat, což se implicitně provádí instrukcí MOVAPS, vyhodí mi instrukce #GP - General Protection Exception - "If memory operand is not aligned on a 16-byte boundary, regardless of segment." _________________ Should array indices start at 0 or 1? My compromise of 0.5 was rejected without, I thought, proper consideration. |
|
Návrat nahoru |
|
 |
Al
Založen: 23. 10. 2007 Příspěvky: 196
|
Zaslal: 21. duben 2012, 15:17:37 Předmět: |
|
|
Toto je zajímavé téma a rád bych k němu něco napsal, ale v podstatě se 100% shoduju s tím, co psal Crypton, takže jen Crypton: palec nahoru.
Mít ve svém programu chybu a křičet "Windows toto a Windows tamto"... hm, klasika. A testovat to dokonce na různých verzích Windows, jaký to má jako mít smysl? Paměť pro objekty přece alokuje C Runtime Library na své haldě, to nedělá přímo operační systém. Nebo se pletu? Jasně, tazatel vůbec nenapsal, jakou verzi překladače C/C++ používá, což je asi úplně nejzásadnější věc v tomto problému. Teoreticky může být implementace celé haldy přes funkce operačního systému, protože Windows haldy umí přímo systémově, ale to by přece zbytečně zpomalovalo program, ne?
Doporučuji se na to zeptat na stackoverflow, tam jsou lidi, co tyhle zdánlivé "chyby" vždycky rychle vyvrátí citací nějaké části ze standardu jazyka ISO C++, kde je přesně popsáno to zdánlivě špatné chování jako správné. |
|
Návrat nahoru |
|
 |
TeaTime
Založen: 17. 06. 2011 Příspěvky: 264
|
Zaslal: 21. duben 2012, 19:05:29 Předmět: |
|
|
Tohle se tedy projevuje jen na Windows? Moc jsem nepochopil to co jsi tam psal o Debianu. A nevíš, jak je na tom Solaris? |
|
Návrat nahoru |
|
 |
Vilem Otte

Založen: 18. 09. 2007 Příspěvky: 462 Bydliště: Znojmo - Sedlesovice, Kravi Hora
|
Zaslal: 21. duben 2012, 19:12:49 Předmět: |
|
|
citace: |
Mít ve svém programu chybu a křičet "Windows toto a Windows tamto"... hm, klasika. A testovat to dokonce na různých verzích Windows, jaký to má jako mít smysl? |
Ano, a proto jsem napsal i řešení jak na to, místo pouhého křičení "Windows toto a Windows tamto...". Díky tobě a podobným uživatelům ztrácím ochotu chodit na toto fórum a dávám přednost jiným, takovým, kde lidé různé informace, které nemusí znát, uvítají.
citace: |
Paměť pro objekty přece alokuje C Runtime Library na své haldě, to nedělá přímo operační systém. Nebo se pletu? |
Částečně, memory management je jedna z věcí o které se stará přímo operační systém. Samotný alokátor pak může být oddělen (např. glibc má svůj), nebo v jádře - v obou případech však voláš systém, kdy jej žádáš o paměť. Doporučuji pročíst zdroják nějakého existujícího alokátoru - http://www.pasteall.org/31113/c, kde žádáš o paměť nakonec systém (line 4011), o zarovnání se poté většinou stará alokátor (tedy ano, je to "chyba" alokátoru - ať je v jádře, či ne).
Problém bude i u tohoto alokátoru - (line 612):
kód: |
#ifndef MALLOC_ALIGNMENT
#define MALLOC_ALIGNMENT ((size_t)8U)
#endif /* MALLOC_ALIGNMENT */ |
Což provede alignment na 8-byte boundaries. Tedy je to chyba alokátoru jako takového - který by se měl starat o toto. Třeba glibc standard přímo říká, že defaultní alignment je (2 * sizeof(void)) - což tento problém způsobí na všech 32-bit systémech, kde použiješ glibc alokaci.
citace: |
Jasně, tazatel vůbec nenapsal, jakou verzi překladače C/C++ používá |
Překladač zde nehraje roli, vůbec - můžu to přeložit v GCC, ICC, Clang, nebo MSVC - vždy stejný výsledek.
citace: |
Doporučuji se na to zeptat na stackoverflow, tam jsou lidi, co tyhle zdánlivé "chyby" vždycky rychle vyvrátí citací nějaké části ze standardu jazyka ISO C++, kde je přesně popsáno to zdánlivě špatné chování jako správné. |
Takže ne standard jazyka, ale pouze daná implementace alokátoru je to na čem záleží
Upřímně veškeré tvé námitky jsou absolutně mimo - např. vliv překladače, když voláš alokátor uvnitř nějaké knihovny či systému - je absolutně nulový, to bys však měl vědět, pokud na toto téma reaguješ.
Navíc mi přijde, že sis můj post ani nepřečetl celý, pokud nečteš celý post, nereaguj. Sám jsem řekl problém na jaký jsem narazil a napsal řešení - pro kohokoliv kdo se s podobným problémem třeba setká. Druhá věc je, že jsem se dotázal, jestli někdo neví něco hlubšího o tomto problému - Crypton reagoval rozumně, ty nikoliv. Nakonec jsem si však sám sehnal informace o tomto problému, ty sis nedokázal sehnat ani informace obecně se týkající vůbec problematiky, natož samotného problému.
citace: |
Tohle se tedy projevuje jen na Windows? Moc jsem nepochopil to co jsi tam psal o Debianu. A nevíš, jak je na tom Solaris? |
Projevuje se to vždy, pokud je paměť alokátorem alokována na 8-byte hranicích - což je u Windows (když je samotný program 32-bitový), u Debian linuxu, který dodržuje glibc standard se toto bude projevovat pokud bude program používat 32-bitový glibc (kde je alignment 2 * sizeof(void*) = 8 byte), v 64-bitovém glibc se alignuje na hranicích (2 * sizeof(void*) = 16 byte) - což bude v pořádku. _________________ Should array indices start at 0 or 1? My compromise of 0.5 was rejected without, I thought, proper consideration. |
|
Návrat nahoru |
|
 |
Al
Založen: 23. 10. 2007 Příspěvky: 196
|
Zaslal: 21. duben 2012, 19:19:35 Předmět: |
|
|
TeaTime: Prosím přečti si, co jsem psal. Stručně řečeno: Ta věc nesouvisí s Windows.
A přesně, jak psal Crypton, to chování je v pořádku dle specifikace programovacího jazyka. Zeptal jsem se na to pana Google a ten mi řekl přesně to, co zde psal Crypton: Prvky struktur jsuo zarovnány vzhledem k začátku struktury. Situaci můžete řešit přetížením operátoru new. |
|
Návrat nahoru |
|
 |
Al
Založen: 23. 10. 2007 Příspěvky: 196
|
Zaslal: 21. duben 2012, 19:24:38 Předmět: |
|
|
Vilem Otte: Nechci tady vést akademické debaty o tom, co je nebo není součástí překladače. Podle mě sysétmové knihovny (C a C++ runtime library, standard template library, c0 nebo případně i další věci) jsou přece součástí toho překladače. Aspoň moje VC++ a GCC/G++ vždycky tyhle knihovny má a já je používám. Jak říkám, může to být i samostatně nebo třeba i mnou definované, ale přece neřešme akademické debaty, bavíme se o běžné situaci a jestil něco způsobuje "alokátor", když tomu tak chceš říkat, je to podle mě věcí toho překladače. Nemyslel jsem "překladač=CL.EXE", myslel jsem to ve smyslu "překladač=MSVC se vším všudy". |
|
Návrat nahoru |
|
 |
Vilem Otte

Založen: 18. 09. 2007 Příspěvky: 462 Bydliště: Znojmo - Sedlesovice, Kravi Hora
|
Zaslal: 21. duben 2012, 19:37:54 Předmět: |
|
|
V tom případě beru zpět co jsem psal , sám jako překladač beru pouze samotný program překladače (tedy přímo programy gcc, icc, etc... či jak píšeš cl.exe).
Samozřejmě v případě že uvažujeme o "překladač=MSVC se vším kolem", tak potom je to věcí překladače.  _________________ Should array indices start at 0 or 1? My compromise of 0.5 was rejected without, I thought, proper consideration. |
|
Návrat nahoru |
|
 |
pcmaster

Založen: 28. 07. 2007 Příspěvky: 1827
|
Zaslal: 23. duben 2012, 08:09:41 Předmět: |
|
|
Ja som na toto podivuhodne a uprimne smutne a zbytocne spravanie MSVC++ tiez narazil pri pisani diplomky a zdielam Vilemov smutok. Vtedy som sa len nasral a vygooglil riesenie.
Je velmi dobre, ze si to sem napisal. Prosim ta, v zaujme vsetkych co este sem chodia, ser na provokatorov a postuj dalej podobne zavazne veci, cesko-slovenska C++ i 3D API komunita bude aj nadalej vdacna. _________________ Off-topic flame-war addict since the very beginning. Registered since Oct. 2003!
Interproductum fimi omne est. |
|
Návrat nahoru |
|
 |
Tringi

Založen: 28. 07. 2007 Příspěvky: 290
|
Zaslal: 23. duben 2012, 13:19:01 Předmět: |
|
|
Tak či onak bych se nikdy neodvážil spoléhat na kompilér, že dostanu dynamickou alokaci více zarovnanou než na 8 bajtů (x86) resp. 16 bajtů (x86-64). Jak ze zkušeností, tak i protože trochu tuším jak jsou real-world kompilátory stavěné.
Redux následujícího je: Vilém musí zarovnávat sám, není to chyba MS, maximálně otázka QOI, protože __m128 ani __attribute__ není definováno žádným standardem C ani C++ takže se může dít prakticky cokoliv, a abychom věděli co, musíme si nastudovat příslušnou dokumentaci.
Podrobněji:
1) Umisťování zarovnávaných proměnných do struktur se bát nemusíte, nejvyšší zarovnání se propaguje do agregujícího typu, ISO C++ kapitola [3.11]. Tzn. je-li __m128 zarovnané na 16 bajtů, bude takto zarovnaná i jakákoliv struktura, která jej ponese, a před danou proměnnou se vloží dostatek padding bajtů aby se zarovnání dodrželo.
2) V tomto ohledu nemůžeme říct že by alokátor nebyl součástí kompilátoru, i když to tak reálně není nikde. Standardy C i C++ (viz dále) nařizují výchozímu alokátoru nějaké parametry, a pokud kompilátor vybere takový, který těmto neodpovídá, je to chyba kompilátoru.
3) ISO C99 pro funkci malloc specifikuje pouze, že vrácený pointer má být zarovnaný tak, aby mohl být platně přetypován na libovolný objekt. Přesněji to standard nespecifikuje. Za předpokladu, že je tím "objekt" myšlena libovolná struktura složená z fundamentálních typů, pak __m128 není ve standardu a ergo toto pro něj neplatí (bez zvláštní benevolence kompilátoru/alokátoru).
4) Současný C++ standard (C++11) v [3.7.4.1.2] specifikuje, že pointer vrácený new má mít zarovnání vyhovující typu v parametru. Má-li tento alignas(16), pak vrácený pointer musí být takto zarovnaný, a není-li, jde o defekt kompilátoru.
5) Nicméně C++98, o které zde zřejmě jde, alignas ani nic podobného nemá a tak nemůže nařizovat nakolik má standardní new respektovat nestandardní __attribute__((aligned(16)), to je otázkou implementace, která by to měla zdokumentovat. Na x86, kde nezarovnaný přístup způsobí přinejhorším prodloužení času přístupu do paměti, by všechno mohlo být zarovnané na 1 bajt a stále by to byla korektní standardní implementace, proto typických 8 bajtů berme jako velkorysost, de facto samozřejmou, ale stále velkorysost.
Jak píšu výše, korektní zarovnání dynamicky alokované paměti by významně zvedlo kvalitu implementace, opak ale není prohřešek proti standardu C++98 protože ten nic takového nespecifikuje. Ale můžu se mýlit, nemám finální dokument po ruce, tak vycházím z preliminárek a google.
6) V typické implementaci (C++98 na 32-bit Windows) new volá malloc, malloc je v msvcrt a volá HeapAlloc, pro které MS nikdy nikde zarovnání nespecifikoval. Yikes. Empiricky a neoficiálně z blogů developerů se zarovnání, na které se dá spolehnout, zjistit dají, a že by se budoucnu změnily (dolů) se čekat nedá, rozbilo by to asi pěknou řádku programů.
Dvě citace standardu C++11 pro Ala:
ISO C++ 3.7.4.1 : 2
[...] The pointer returned shall be suitably aligned so that it can be converted to a pointer of any complete object type with a fundamental alignment requirement (3.11) and then used to access the object or array in the storage allocated [...]
ISO C++ 3.11.2
A fundamental alignment is represented by an alignment less than or equal to the greatest alignment supported by the implementation in all contexts, which is equal to alignof(std::max_align_t) (18.2). The alignment required for a type might be different when it is used as the type of a complete object and when it is used as the type of a subobject.
Pro další referenci diskuze na stackoverflow:
http://stackoverflow.com/questions/506518/is-there-any-guarantee-of-alignment-of-address-return-by-cs-new-operation
Bug GCC 15795: Resolved WONTFIX:
http://gcc.gnu.org/bugzilla/show_bug.cgi?id=15795
(hádám že kvůli C++11 se to dříve nebo později změní) _________________ WWW | GitHub | TW |
|
Návrat nahoru |
|
 |
tom.drin

Založen: 28. 07. 2007 Příspěvky: 65
|
Zaslal: 27. duben 2012, 14:48:32 Předmět: |
|
|
Taky trochu experimentuju s SSE a narazil jsem na obdobný problém.
Používám POSIX vlákna a gcc (mingw32) a problém je, že stack pro vlákno není zarovnaný na 16 bytů (tak jako je zarovnaný stack pro main). Poté kdykoliv použiju SSE datový typ tak to padne.
Nevíte někdo co s tím, lze nějak zarovnat vlákna? |
|
Návrat nahoru |
|
 |
Tringi

Založen: 28. 07. 2007 Příspěvky: 290
|
Zaslal: 27. duben 2012, 15:51:59 Předmět: |
|
|
To je zvláštní ...jaké verze GCC a mingwrt používáš?
Vzpomínám si na extra okomentovaný kód na několika místech mingwrt, kde se právě kvůli SSE stack zarovnával.
Tak či onak zkus svému typu přidat zmíněné __attribute__((aligned (16))) a uvidíš. _________________ WWW | GitHub | TW |
|
Návrat nahoru |
|
 |
|