14197
zobrazení
V tomto článku je popsána nadstavba C++ pro práci s .NET prostředím zvaná C++/CLI umožňující vytvářed mixed assembly obsahující jak managed tak unmanaged kód.
V prvním díle je popsána myšlenka jazyka a základní syntaktické konstrukty (základní typy, podmínky, cykly, pole, namespace a část tříd a objektů).
U čtenáře je předpokládána znalost .NET frameworku a nativního programování nejlépe v C++ (alespoň syntaxi a základy).
Dnešní článek bude pro Vbnet trochu netypický – zatímco většina článků tu se zabývá čistě managed světem (nebo algoritmy) a slovíčka jako
pointer
či
unsafe
blok jsou tu vzácná, tento článek bude pointerů plný a něco jako ne-unsafe kód ani v C++/CLI nenajdete. Tento článek je určen pro programátory, kteří znají (aspoň trochu) jak managed (C#, VB.NET) tak i unmanaged (C++ aspoň syntakticky a nativní programování obecně) programování a rádi by tyto dva “světy” nějakým programmer-friendly způsobem spojili.
A co to vlastně to C++/CLI (
C++/Common Language Infrastructure
) je? Je to Microsoftí rozšíření (jinak čistě unmanaged) jazyka C++, které do něj přidává jazykové konstrukce a další věci nutné pro programování pro .Net framework, přičemž z něj nic neubírá (pokud nezvolíte pure MSIL, více dále). Takže už obsahuje knihovny třech jazyků – C, C++ a .NET, což samozřejmě na přehlednosti nepřidává, nicméně dává programátorovi do ruky velmi mocnou zbraň – kombinovat a přímo používat managed a unmanaged kód v jednom projektu. Což jiný .NET jazyk od MS neumí (mimo MS jazyky to tuším uměl/umí ObjectPascal v novějších Delphi). Zkompilováním C++/CLI projektu vzniká tzv.
mixed assembly
, která obsahuje jak část v MSILu (včetně např. managed zapouzdření některých C++ tříd) tak část přímo ve strojovém (nativním) kódu. Managed třídy pak můžete používat (při dodržení určitých pravidel) i z jiných jazyků platformy .NET. Na závěr popisu ještě trošku historie - C++/CLI je obsažen až ve
Visual Studiu 2005
, 2003ka a nižší obsahovali tzv.
Managed Extensions for C++,
kterým chyběli některé featury a hlavně jejich syntaxe byla ještě chaotičtější než C++/CLI. MC++ je nyní deprecated a i když ho VS pořád podporuje, už se nedoporučuje v něm cokoli programovat.
Takže už víme, co to je, teď je třeba uvést, k čemu je to dobré. neboť, jak praví M. Valášek, neexistují špatné technologie, jsou jen technologie dobře použité a špatně použité.
-
Použití čistě unmanaged knihoven či funkcí
je důvod, který mě “nutí” používat C++/CLI nejčastěji. .Netu chybí podpora pro pohodlnou práci s HW na trochu nižší úrovní, případně se sice k HW dodávají knihovny pro komunikaci, nicméně pouze Cčko. Samozřejmě tento problém se týká všech unmanaged-only knihovnen, ke kterým neexistuje vyhovující managed alternativa, nicméně se stále se zvětšujícím rozšířením .NETu se tyto knihovny moc nevyskytují (nebo člověk má příliš exotické požadavky či úkoly;) ).
-
Zrychlení výpočetního jádra
se také občas hodí. I v .netu sice lze psát rychlé aplikace, ale často je to na úkor přehlednosti či “best-practices” managed platformy, která na to navíc není stavěná a člověk pak nese overhead GC a dalších managed featur, i když je zrovna při těch časově či paměťově náročných výpočtech vůbec nepotřebuje. Navíc C++ kompilátor generuje optimalizovanější kód než .netí JIT, což se projeví hlavně při nějakém složitějším kódu. Občas sice vidím ukázky toho, že .NET je stejně rychlý jako nativní kód psaný např. v C ale vždy na nějakém triviálním kódu, což je přinejmenším zavádějící.
-
A nebo naopak – mám nativní aplikaci v C++ a potřebuju k ní napsat nějaké pěkné GUI a nechce se mi zlobit se s nějakou více či méně dokonalou knihovnou na GUI v C++.
-
Připadně kdykoli jindy, když je potřeba nějak používat .NET z nativního kódu či naopak.
Samozřejmě v C++/CLI se dají psát i čistě managed aplikace, ale osobně to z důvodu vyšší komplikovanosti jazyka nedoporučuju, je lepší použít nějaký normální .NET jazyk.
Nicméně C++/CLi má kromě subjektivně složitější syntaxi i další nedostatky – zásadnim problémem .NET/native interoperability je nízká výkonnost. To lze pozorovat už i v P/invocích v “čistých” .NET jazycích – každý přechod mezi nativním a managed kódem s sebou nese jistou režii a v případě neopatrného používání může tento přechod nastávat často, což má pak vliv na výkonnost. Toto lze do jisté míry eliminovat striktím oddělováním managed a unmanaged části kódu a direktivami
#pragma managed a #pragma unmanaged
, nicméně je třeba přemýšlet. V neposlední řadě také C++/CLI způsobuje dost značnou obfuskaci výsledného kódu (což by někdo mohl vidět i jako výhodu …), jeden příklad uvedu později.
Kooperaci či interoperabilitu managed a unmanaged lze samozřejmě zajistit i jinými prostředky. Nejjednodušším příkladem jsou P/Invoky, kde ale nemůžu sdílet celé objekty, musím spravovat navíc ještě extern deklarace v managed jazyku a hlavně tím nezavolám managed kód z nativního programu. Lepší variantou jsou pak COM+ objekty, které jsou ale složitější, musí se registrovat a je s nimi obecně více práce. Na druhou stranu jsou obecnější.
Náš první C++/CLI projekt
Náš první projekt vytvoříme buď přímo přes klikátko New projekt v záložce Visual C++\CLR nebo ho uděláme z klasického C++ projektu nastavením Common Language Runtime support (co znamená Pure MSIL nebo safe MSIL je popsáno celkem pěkně na
http://msdn.microsoft.com/cs-cz/library/85344whh%28en-us%29.aspx
)
Pro tento článek budeme zatím uvažovat konzolovou aplikaci vytvořenou přes klikátko (CLR Console application).
Základní syntaxe
Nově vytvořený projekt obsahuje jen
main
funkci, import System namespacu a výpis “Hello worldu” na konzoli. C++kaře na první pohled upoutají nejspíše ony divné operátory ^ vypadající jak z Pascalu, mě jako C#áře zarazily hlavně :: a to, že tam není klasická statická třída Program, ale jen funkce main.
Nejdřív by asi bylo dobré vysvětlit základní strukturu projektu. I přes .NETí příchuť si projekt zachovává vzezření klasického Visual C++ projektu – máme klasické dělení na hlavičkové a .cpp soubory, máme stdafx.h, kam se píší standardní a knihovní hlavičky, které si kompilátor umí předkompilovat a nemusel je kompilovat při každém #include “stdafx.h”, čímž se kompilace výrazně urychlí. Preprocesor se chová stejně jako v C++, jen navíc přidává #using pro referencování jiných assembly. Ty je možné referencovat dvojím způsobem, buď v nastavení projektu v Common Properties nebo pomocí #usingů, které se používají #using <NázevAssembly> a píší se přímo do cpp kódu.
Základní typy
Základní typy jako int, double či char jsou přímo mapovány na jejich .NET ekvivalenty (koneckonců int či double vypadá v paměti stejně jak v .netu tak v C++). To se týká jak generických typů (tj. můžu psát System::Collections::Generic::List<int> stejně jako std::vector<int>), tak i statických metod a vlastností, které tyto typy v .NETu mají (tj můžu psát int::Parse()). Přehled mapování je v následující tabulce:
Visual C++ typ
|
.NET Framework typ
|
bool
|
System.Boolean
|
signed char
|
System.SByte
|
unsigned char
|
System.Byte
|
wchar_t
|
System.Char
|
double, long double
|
System.Double
|
float
|
System.Single
|
int, signed int, long, signed long
|
System.Int32
|
unsigned int, unsigned long
|
System.UInt32
|
__int64, signed __int64
|
System.Int64
|
unsigned __int64
|
System.UInt64
|
short, signed short
|
System.Int16
|
unsigned short
|
System.UInt16
|
void
|
System.Void
|
S řetězci je to již trošku složitější – v paměti sice vypadají podobně, ale rozhodně ne stejně. Navíc System::String je referenční typ a tak sedí v managed haldě. Tudíž nemohu jen tak přiřadit přímo System::String do char * či opačně. Ale mám několik možností, jak toho dosáhnout nepřímo:
-
Můžu použít třídu
System::Marshal
, která se používá pro obecnou interoperabilitu mezi managed a unmanaged
-
Nebo pro převod managed->unmanaged použiju funkci PtrToStringChars definovanou v <vcclr.h>
String ^str =gcnew String("ahoj"); //vytvorim instanci System::Stringu
pin_ptr<const wchar_t> str2 = PtrToStringChars(str); // prevod
std::wcout << (wchar_t *)str2; // vypsani
Syntaxi pořádně popíšu později, důležité je vědět a pamatovat, že PtrToStringChars nekopíruje paměť a proto str2 ukazuje kamsi doprostřed objektu str (který leží na managed haldě). Proto je potřeba objekt “připíchnout” pomocí pin_ptr, aby nám ho GC nikam nešoupl. Detaily o pin_ptr a interior_ptr popíšu později.
-
Pokud potřebujeme udělat z unmanaged stringu System::String, tak máme situaci jednoduchou, neboť máme pro tento případ jedno přetížení konstruktoru String.
Podmínky a cykly
If podmínky, Switch větvení, For, While a do .. while cykly jsou uplně stejné jako C++, novinkou je
for each
cyklus, což je ekvivalent C# foreach či Vb.Net ForEach cyklů. Umí procházet IEnumerable kolekce, unmanaged pole a dokonce i STL kontejnery. Má tvar
for each(
typ iterační_proměnná
in
kolekce).
Oproti C# či VbNet foreachi ale umí jednu užitečnou věc – nemusíme iterovat hodnotou ale můžeme i referencí, takže můžeme přímo ve foreachi měnit prvky kolekce nejen jejich obsah. V příkladu to používám pro vytvoření jagged pole (pole polí). Reference se zapisuje pomocí operátoru % a ještě o ní bude reč dále.
int main(array<System::String ^> ^args)
{
std::vector<int> vect; //stvoříme std::vector
vect.push_back(4); vect.push_back(6); // naplníme ho
for each(int i in vect) // a vypíšeme
Console::WriteLine(i); // ....
// vyvoření jagged array pomocí foreache
array<array<int> ^> ^jagged = gcnew array<array<int> ^>(5); //vytvoření v vnějšího pole
for each (array<int> ^% arr in jagged) // enumerace vytvoření vnitřního pole
arr = gcnew array<int>(6);
}
Pokud by vás zajímalo, jak je to interně řešené, tak vězte, že nikterak pěkně. Stačí si vzít Reflector a podívat se. Následující kód je v skoro-C# i když to tak na první pohled nevypadá. Konstrukci
try … fault
běžně neuvidíte, slouží pro obsluhu a práci se
Structured Exception Handling
, což je mechanismus Windows jak řešit chyby jako je špatný přístup do paměti. Je také pěkně vidět, jak mají Microsofti definice STL tříd i v .NETu.
internal static unsafe int main(string[] args)
{
int[][] $S4 = null;
int[][] jagged = null;
vector<int,std::allocator<int> > vect;
std.vector<int,std::allocator<int> >.{ctor}(&vect);
try
{
_Vector_const_iterator<int,std::allocator<int> > $S2;
_Vector_iterator<int,std::allocator<int> > local2;
int modopt(IsConst) num2 = 4;
std.vector<int,std::allocator<int> >.push_back(&vect, &num2);
int modopt(IsConst) num = 6;
std.vector<int,std::allocator<int> >.push_back(&vect, &num);
vector<int,std::allocator<int> >* modopt(IsImplicitlyDereferenced) $S1 = &vect;
_Vector_iterator<int,std::allocator<int> >* localPtr2 = std.vector<int,std::allocator<int> >.end($S1, &local2);
try
{
std._Vector_const_iterator<int,std::allocator<int> >.{ctor}(&$S2, (_Vector_const_iterator<int,std::allocator<int> > modopt(IsConst)* modopt(IsImplicitlyDereferenced)) localPtr2);
}
fault
{
___CxxCallUnwindDtor(std._Vector_iterator<int,std::allocator<int> >.{dtor}, (void*) &local2);
}
try
{
_Vector_const_iterator<int,std::allocator<int> > $S3;
_Vector_iterator<int,std::allocator<int> > local;
std._Vector_iterator<int,std::allocator<int> >.{dtor}(&local2);
_Vector_iterator<int,std::allocator<int> >* localPtr = std.vector<int,std::allocator<int> >.begin($S1, &local);
try
{
std._Vector_const_iterator<int,std::allocator<int> >.{ctor}(&$S3, (_Vector_const_iterator<int,std::allocator<int> > modopt(IsConst)* modopt(IsImplicitlyDereferenced)) localPtr);
}
fault
{
___CxxCallUnwindDtor(std._Vector_iterator<int,std::allocator<int> >.{dtor}, (void*) &local);
}
try
{
std._Vector_iterator<int,std::allocator<int> >.{dtor}(&local);
goto Label_0089;
Label_0081:
std._Vector_const_iterator<int,std::allocator<int> >.++(&$S3);
Label_0089:
if (std._Vector_const_iterator<int,std::allocator<int> >.!=((_Vector_const_iterator<int,std::allocator<int> > modopt(IsConst)* modopt(IsConst) modopt(IsConst)) &$S3, (_Vector_const_iterator<int,std::allocator<int> > modopt(IsConst)* modopt(IsImplicitlyDereferenced)) &$S2))
{
int i = *(std._Vector_const_iterator<int,std::allocator<int> >.*((_Vector_const_iterator<int,std::allocator<int> > modopt(IsConst)* modopt(IsConst) modopt(IsConst)) &$S3));
Console.WriteLine(i);
goto Label_0081;
}
}
fault
{
___CxxCallUnwindDtor(std._Vector_const_iterator<int,std::allocator<int> >.{dtor}, (void*) &$S3);
}
std._Vector_const_iterator<int,std::allocator<int> >.{dtor}(&$S3);
}
fault
{
___CxxCallUnwindDtor(std._Vector_const_iterator<int,std::allocator<int> >.{dtor}, (void*) &$S2);
}
std._Vector_const_iterator<int,std::allocator<int> >.{dtor}(&$S2);
jagged = new int[5][];
$S4 = jagged;
int $I = 0;
goto Label_00E8;
Label_00E4:
$I++;
Label_00E8:
if ($I < $S4.Length)
{
ref int[] arr = &($S4[$I]);
arr = new int[6];
goto Label_00E4;
}
}
fault
{
___CxxCallUnwindDtor(std.vector<int,std::allocator<int> >.{dtor}, (void*) &vect);
}
std.vector<int,std::allocator<int> >.{dtor}(&vect);
return 0;
}
Namespaces
.Net namespacy se používají úplně stejně jako klasickém C++, tj. import namespace, abychom je nemuseli vždy vypisovat (using konstrukce v C#) probíhá pomocí
using namespace <Namespace>
, umí to samozřejmě i vnořené namespacy,
using namespace System::Drawing,
operátor :: (scope resolution operátor, pro přístup ke statickým prvkům tříd k a explicitnímu vypisování namespacu k nějakému typu/funkci, PHPkářům jistě známý jako
T_PAAMAYIM_NEKUDOTAYIM
) je jistě všem C++ programátorům důvěrně známý. Pokud chceme, aby náš kód nebyl v defaultním namespacu, musíme uzavřít kód do konstrukce
namespace Neco { … }
(stejně jako v C++ a C#). Otravná nevýhoda tohoto konstruktu v C++ je, že neumí vnořený namespace napsat najednou, pokud chci tedy psát kód v Cermi::Aplikace namespace musím udělat:
namespace Cermi
{
namespace Aplikace
{
// muj kod
}
}
Managed a unmanaged pole jsou (stejně jako retězce) 2 rozdílné věci a proto máme v C++/CLI obojí. Unmanaged pole se deklarují a používají stejně jako v C++
int upole[20]; //pole na stacku
int *upole2 = new int[20]; // pole na haldě
Console::WriteLine(upole[5]); // vypíše náhodnou hodnotu, pole není inicializované
int *ptr = upole + 4; //ukazatel na 5. prvek
ptr++; //ptr je ted ukazatel na 6. prvek
Managed pole se vytváří pomocí klíčového slova
array,
které vypadá jako template třída array s 2 template parametry – první je typ pole, druhý je číslo značící počet dimenzí. Typ pole může být handle na refereční typ (type ^) či hodnotový managed typ nebo pointer (type *). Nesmí to být žádný nejednoduchý nativní typ. Obecná syntaxe je následující:
[qualifiers] array<[qualifiers]type1[, dimension]>^var = gcnew array<type2[, dimension]>(val[,val...])
, kde
qualifiers
jsou kvalifikátory jako const či static,
type1
je formální typ pole,
dimension
je dimenze pole,
type2
skutečný typ pole a
val1
…
valn
jsou rozměry jednotlivých dimenzí pole. Type1 a type2 jsou většinou stejné, vždy ale platí, že musí existovat konverze z Type2 do Type1. Pole podporují tzv.
kovarianci
, což znamená že pokud mám pole typu A a pole typu B a existuje konverze z A do B, tak můžu přiřadit pole A do pole B,
array<String ^> ^mpole = gcnew array<String ^>(4); // pole 4 řetězců
mpole[0] = "Ahoj"; // přístup k prvku pole
Console::WriteLine(mpole->Length); //vypiseme delku pole
array<int *,3> ^mpole2 = gcnew array<int *, 3>(2,3,4); //vytvoření 3dimenzinálního pole
mpole2[0,0,0]=NULL; // přístup k prvku pole
Můžu také vytvářet zubatá neboli
jagged arrays,
což je vlastně pole polí a tak se taky i zapisuje:
array<array<int> ^> ^jagged = gcnew array<array<int> ^>(5); //vytvoření v vnějšího pole
for each (array<int> ^% arr in jagged) //vytvoření vnitřního pole
arr = gcnew array<int>(6);
Třídy a struktury
Jak všichni víme, tak C++ podporuje jeden typ třídy (je jedno jestli je zapsaný jako
class
nebo
struct
), který mohu vytvořit buď na stacku nebo na haldě (co je zásobník a halda popsal Tomáš ve svém
článku o základech platformy .NET
), zatímco .NET rozlišuje referenční typy (class) a hodnotové typy (struct). V první řadě je třeba si uvědomit, že máme
jeden stack společný
pro managed a unmanaged objekty, ale
2 různé haldy
– jednu pro managed a druhou pro unmanaged objekty. Také nesmíme mixovat nativní a managed typy – tj. nemůžu jako member proměnnou v managed typu mít nějakou nativní či naopak (i když tam to jde obejít pomocí gcroot, což popíšu v dalším díle).
Handles
Protože nativní ukazatel na managed objekt nespolupracuje s GC (a ani ho přímo nemůžu vytvořit), tak se zavedly tzv.
handles
(českým ekvivalentem jsi nejsem jist), což jsou jiné handles než ty, které se používají jako “identifikátory” objektů Windows (jakýkoli objekt OS používaný z userspacu má své handle, pomocí něhož se s ním manipuluje). Vzhledem k tomu, že Win32 handly zde nebudu používat, tak pojem
handle
bude znamenat vždy
handle na managed objekt
. Když máme ujasněnou terminologii, tak vysvětlím, co handle znamená. Je to něco, co ukazuje na
celý
objekt na managed haldě, tady v podstatě klasická reference z C# či VB.NETu:
StringBuilder sb = new StringBuilder();
Slovo “celý” jsem zvýraznil proto, že existují ještě tzv. tracking references, které mohou ukazovat i na nějakou členskou proměnnou managed objektu. Důležité je to, že ukazuje na
objekt
a ne na kus paměti. Protože GC může, pokud uzná za vhodné, objekty přesouvat po paměti, tak ukazatel na paměť, kde se nachází objekt, je nám v podstatě k ničemu (pokud objekt “nepřipíchneme”, viz další díl), neboť nám nikdo nezaručí, že objekt se na dané adrese bude nacházet i v příští sekundě. Handles ale s GC “spolupracují” a tak se o toto nemusí starat a ukazují na objekt, ať už se nachází na jakékoli adrese.
Handles se deklarují pomocí operátoru ^ (stříška), který se píše před název proměnné. Pokud chceme přistupovat k prvkům objektu, na který máme handle, tak použijeme operátor –> stejně jako když přistupujeme k nativnímu objektu, na který mám ukazatel (nativní ukazatel, v tomto článku budu pojmem
ukazatel
vždy rozumět “hloupý” nativní ukazatel na nějakou adresu v paměti). Handle můžeme i dereferencovat pomocí operátoru * (hvězdička).
Unmanaged třídy
Unmanaged třídy se chovají a deklarují uplně stejně jako v C++, zde se nic nemění.
Hodnotový managed typ
Hodnotový managed typ se deklaruje pomocí klíčového slůvka
value
před slovem
class nebo struct
. Stejně jako v C++ platí, že mezi struct a class je pouze jediný rozdíl a to v tom, že členové bez určení viditelnosti jsou u class private, kdežto u struct public. Vytváří se na stacku, nicméně můžeme pomocí
gcref
vytvořit i jeho instanci na managed haldě (to ani v C# ani VbNetu nejde, pokud se nepletu).
value class Struktura
{
public:
int A,B;
};
int main(array<System::String ^> ^args)
{
int x = 0x78563412;
Struktura s;
s.B = 5;
return 0;
}
Z výpisu z paměti (červeně je x, zeleně s.A, modře s.B) je také vidět, že member proměnné v managed typech se inicializují na defaultní hodnotu:
Referenční managed typ
Rerefenční managed typy se deklarují pomocí slova
ref
před slovem
class nebo struct
. Instance se vytváří pomocí operátoru
gcnew
, což je
new
pro managed typy, a vrací handle na nově vytvořenou instanci. Ve starém Managed C++ se gcnew nepoužívalo, bylo všude pouze new, takže nebylo přímo z kódu jasné, jestli se dělá managed nebo unmanaged instance. Referenční typy se vždy vytváří na managed haldě. Je možné při deklaraci instancí použít i syntaxi pro vytváření struktur na stacku – instance se ale stejně interně vytvoří na managed haldě, ale používá se trochu jinak.
ref class Trida
{
public:
int A,B; //deklarace promennych
void VypisA() { std::cout << A << std::endl; } // deklarace funkce
void VypisB(); //druha moznost deklarace fce
};
void zmenA(Trida ^x)
{
x->A = 7;
}
void Trida::VypisB()
{
std::cout << B << std::endl;
}
int main(array<System::String ^> ^args)
{
Trida ^t = gcnew Trida();
t->VypisA(); //vypise 0
t->A = 5;
t->VypisA(); //vypise 5
zmenA(t);
t->VypisA(); //vypise 7
}
Konstruktory a destruktory
Konstruktor
se definuje jako C++ – jako metoda bez návratového typu se stejným názvem, jako má třída.
Statický konstruktor
máme také a definuje se jako statický konstruktor (nevím jak lépe to popsat, snad příkladem)
ref class Trida
{
public:
static int X;
static Trida()
{
X=7;
}
};
Destruktor
, který se definuje také stejně jako v C++ (tj.
~NázevTřídy
), ale u managed objektů není destruktorem v pravém slova smyslu, neboť managed objekty žádný nemají. Je to totiž do samé, jako kdybyste implementovali metodu Dispose z IDisposable rozhraní. Podle MSDN dokumentace se nedoporučuje implementovat IDisposable přímo, ale unmanaged resources uvolňovat právě pomocí destruktoru. Destruktory se volají
deterministicky
(narozdil od finalizeru), pokud nastane nějaká z následujících situací:
Objekt vytvořený pomocí “stack semantics” (tj. tak jak se vytvářejí hodnotové typy na stacku, bez
gcnew
) se zničí, když aplikace vyskočí z bloku, kde byl definován, ať už normálně nebo během stack unwinding při “probublávání” výjimky
Při vyhození výjimky v konstruktoru
Když se destruuje objekt, jehož je member proměnnou (nadeklarovanou přímo, ne jako handle nebo pointer)
Voláním operátoru delete na handle daného objektu
Voláním Dispose
Explicitním voláním desktoru
C++/CLI samozřejmě podporuje i
finalizery
– deklarují se jako metoda bez návratového typu se jménem
!NázevTřídy
. Jsou to stejné finalizery jako je známe z jiných .NET jazyků, tj. provádí se při ničení objektu Garbage Collectorem (pokud to není vypnuté, proto se na finalizery nevyplatí spoléhat).
Pokračování příště …
V dalším díle (a pravděpodobně posledním) dokončíme povídání o třídách a popíšeme eventy, pointery, reference a další důležité prvky C++/CLI.
» na začátek
hodnocení článku
/
2
hlasů
Hodnotit mohou jen registrované uživatelé.
Jakub, druhovým zařazením Liška obecná líná, pochází z Pardubic, kde vystudoval osmileté gymnázium, a v současné době pobývá v Praze, kde se snaží dopsat diplomku (zatím neúspěšně) a dostudovat MFF UK. Jinak se zabývá vývojem aplikací na technologiích firmy Microsoft - částečně jako zaměstnanec, částečně jako freelancer (má totiž zásadní problémy s pracovní dobou, docházením na místo a vyrušováním při práci). Poslední dobou si hraje s psaním webových aplikací (jak klasických tak teď i módním Azure), softwaru pro analýzu dat a v podstatě psaním všeho, co ho nějak zaujme. Občas také napíše nějaký článek či odvykládá přednášku, díky čemuž se účastní MSP programu.
http://www.jcermak.cz
@cermakj
Souhlasit nemusíte, realita je ovšem taková. JIT kompiluje jen kód, který je výslovně potřeba, a vzhledem k tomu, že při spouštění aplikace na to nemá tolik času, není zkrátka možné, aby prováděl všechny optimalizace. Když si dáte zkompilovat nějaký větší projekt v C++, kompilace trvá klidně hodinu. .NET aplikace se ve visual Studiu kompiluje jen do MSILu, což je de facto spíš jiný způsob zápisu zdrojového kódu než skutečná kompilace, samotný JIT má na to pár sekund. Nestíhá dělat komplexní optimalizace, jako třeba inlinování metod, operátorů, vlastností, dělá jen jednodušší věci. To, že optimalizuje pro daný procesor, rychlost aplikace zase o něco zvyšuje, ale nedostatek času pro provádění optimalizací má daleko vyšší význam.
Nechci tím tvrdit, že .NET je obecně pomalý, ze všech managed prostředí, která existují, je .NET rozhodně nejrychlejší a ve většině případů se jeho použití vyplatí - o jednotky procent nižší rychlost většinou nad rychlostí vývoje a bohaté knihovně funkcí nevyhraje. Samozřejmě na jisté typy úloh se .NET vyloženě nehodí.
Přesně jak říká Tomáš - menší čas na optimalizace je důležitější než zaměření na konkrétní platformu JITem, hodně se to projeví u složitějšího kódu. Samozřejmě, určitě se povede najít takový příklad, kdy výhody JIT kompilace převáží. Navíc i v C++ můžete kompilovat pro několik architektur - zvlášť pro "kompatibilní" i386, zvlášť pro stroje s SSE3 instrukcemi, pro IA64, atd atd.
Navíc managed aplikace obecně jsou paměťově náročnější, což má také vliv na rychlost, neboť je více cache missů. Samozřejmě nemá cenu řešit jednotky procent, neboť to je v podstatě statistická a systematická chyba (na každém CPU to bude jiné...). Hodně záleží na typu úlohy a dalších faktorech. Když člověk potřebuje super rychlost, tak použije realtime systém nebo třeba si spájí nějaký jednoúčelový obvod :)
Ano, ovšem to nic nemění na tom, že JIT i NGen tolik optimalizací jako C++ kompilátor ani neumí, protože je prostě v řádu jednotek sekund nemůžou stihnout provést.
Až od .NET Frameworku 4 bude například pořádně podporována tail rekurze (kvůli funkcionálnímu F#), na 64bitových systémech kompilátory až dosud třeba vůbec neinlineovaly a takových restíků je tam mnoho. Takových restíků je tam daleko víc. Nový runtime by měl být tedy znatelně rychlejší, protože velkou část z nich řeší.
Nový runtime by měl být tedy znatelně rychlejší
To říkají v Microsoftu vždycky :), ale je třeba podotknout, že vždy o něco rychlejší je, třeba mezi 1 a 2kou ten rozdíl byl znatelný (pozorováno okem na celkem starých stanicích, kde je každá optimalizace viditelná).
Já osobně zastávám názor, že (přehnaně řečeno) u frameworku a knihoven není žádná optimalizace dostatečná, neboť tvůrce knihovny nikdy nemůže vědět, k jakému účelu ji použije. Navíc zrychlením .NET frameworku se zrychlí obrovské množství aplikací, které na něm závisí, což už se vyplatí.
On ale ani ten C++ kompilátor z Visual Studia nestojí za nic. Skutečně kvalitní kompilátory kde jsou optimalizace na prvním místě jsou kolikrát dražší než celé Visual Studio. Příkladem budiž kompilátor od Intelu - Intel Compiler Suite Professional Edition for Windows. V tomto případě se jedná o skutečnou optimalizaci.
Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.
Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději
založit nové vlákno
?
Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
Mazány budou zejména komentáře obsahující vulgarity nebo porušující
pravidla publikování
.
Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.
© 2024
Tomáš Herceg
,
Tomáš Jecha
.
Máte-li dotazy, připomínky, nebo zájem o inzertní prostor,
kontaktujte nás
.
Přebírání článků nebo jejich částí je bez písemného svolení autorů
striktně zakázáno
.