Метапрограмирането на шаблона по-бързо ли е от еквивалентния C код? (Говоря за производителността по време на изпълнение) :)
Метапрограмирането на шаблони по-бързо ли е от еквивалентния C код?
Отговори (8)
Първо, отказ от отговорност: това, за което мисля, че питате, е не само шаблонно метапрограмиране, но и общо програмиране. Двете понятия са тясно свързани и няма точна дефиниция какво обхваща всяко от тях. Но накратко, шаблонното метапрограмиране по същество е писане на програма, използваща шаблони, която се оценява по време на компилиране. (което го прави напълно безплатно по време на изпълнение. Нищо не се случва. Стойността (или по-често тип) вече е изчислена от компилатора и е достъпна като константа (или константна променлива, или enum), или като typedef вложен в клас (ако сте го използвали за "изчисляване" на тип).
Генеричното програмиране използва шаблони и, когато е необходимо, шаблонно метапрограмиране, за да създаде общ код, който работи по същия начин (и без загуба на производителност), с всички и всеки тип. Ще използвам примери и за двете по-долу.
Обща употреба на метапрограмиране на шаблони е да се даде възможност на типовете да се използват в генерично програмиране, дори ако не са предназначени за това.
Тъй като метапрограмирането на шаблони технически се извършва изцяло по време на компилация, вашият въпрос е малко по-подходящ за генерично програмиране, което все още се извършва по време на изпълнение, но е ефективно, защото може да бъде специализирано за точните типове, с които се използва по време на компилация.
Така или иначе...
Зависи как дефинирате "еквивалентния C код".
Номерът на шаблонното метапрограмиране (или генеричното програмиране като цяло) е, че позволява много изчисления да бъдат преместени във времето за компилиране и позволява гъвкав, параметризиран код, който е също толкова ефективен, колкото твърдо кодираните стойности.
Кодът, показан тук, например изчислява число в редицата на фибоначи по време на компилация.
C++ кодът 'unsigned long fib11 = fibonacci<11uL>::value
' разчита на шаблонната метапрограма, дефинирана в тази връзка, и е толкова ефективен, колкото C кода 'unsigned long fib11 = 89uL
'. Шаблоните се оценяват по време на компилиране, като се получава константа, която може да бъде присвоена на променлива. Така че по време на изпълнение кодът всъщност е идентичен с обикновено присвояване.
Така че, ако това е "еквивалентният C код", производителността е същата. Ако еквивалентният C код е "програма, която може да изчислява произволни числа на Фибоначи, приложени за намиране на 11-то число в последователността", тогава C версията ще бъде много по-бавна, защото трябва да бъде внедрена като функция, която изчислява стойността по време на изпълнение. Но това е „еквивалентен C код“ в смисъл, че това е C програма, която проявява същата гъвкавост (това не е просто твърдо кодирана константа, а действителна функция, която може да върне всяко число в последователност на фибоначи).
Разбира се, това често не е полезно. Но това е до голяма степен каноничният пример за метапрограмиране на шаблони.
По-реалистичен пример за генерично програмиране е сортирането.
В C имате стандартната библиотечна функция qsort
, която приема масив и указател на функция за сравнение. Извикването на този указател на функция не може да бъде вградено (освен в тривиални случаи), защото по време на компилиране не се знае коя функция ще бъде извикана.
Разбира се, алтернативата е ръчно написана функция за сортиране, предназначена за вашия конкретен тип данни.
В C++ еквивалентът е шаблонът на функцията std::sort
. Той също изисква компаратор, но вместо това да е функционален указател, той е функционален обект, изглеждащ така:
struct MyComp {
bool operator()(const MyType& lhs, const MyType& rhs) {
// return true if lhs < rhs, however this operation is defined for MyType objects
}
};
и това може да бъде вградено. На функцията std::sort
се предава шаблонен аргумент, така че тя знае точния тип на сравнителния инструмент и така знае, че сравнителната функция не е просто указател на неизвестна функция, а MyComp::operator()
.
Крайният резултат е, че C++ функцията std::sort
е точно толкова ефективна, колкото ръчно кодираната ви реализация в C на същия алгоритъм за сортиране.
Така че отново, ако това е "еквивалентният C код", тогава производителността е същата. Но ако „еквивалентният C код“ е „обобщена функция за сортиране, която може да се приложи към всеки тип и позволява дефинирани от потребителя компаратори“, тогава генеричната програмна версия в C++ е много по-ефективна.
Това наистина е трикът. Генеричното програмиране и шаблонното метапрограмиране не са „по-бързи от C“. Те са методи за постигане на общ, многократно използваем код, който е толкова бърз, колкото ръчно кодиран и твърдо кодиран C
Това е начин да получите най-доброто от двата свята. Ефективността на твърдо кодираните алгоритми и гъвкавостта и повторното използване на общите, параметризирани такива.
Шаблонното метапрограмиране (TMP) се „изпълнява“ по време на компилиране, така че всъщност не се сравняват ябълки с ябълки, когато се сравнява с нормален C/C++ код.
Но ако имате нещо, оценено от TMP, тогава изобщо няма разходи за изпълнение.
Ако имате предвид многократно използваем код, тогава да без съмнение. Метапрограмирането е превъзходен начин за създаване на библиотеки, а не на клиентски код. Клиентският код не е общ, той е написан да прави конкретни неща.
Например, погледнете qsort от C стандартна библиотека и C++ стандартен sort. Ето как работи qsort:
int compare(const void* a, const void* b)
{
return (*(int*)a > *(int*)b);
}
int main()
{
int data[5] = {5, 4, 3, 2, 1};
qsort(data, 5, sizeof(int), compare);
}
Сега погледнете сортирай:
struct compare
{
bool operator()(int a, int b)
{ return a < b; }
};
int main()
{
int data[5] = {5, 4, 3, 2, 1};
std::sort(data, data+5, compare());
}
Сортирането е по-чисто, по-безопасно и по-ефективно, защото функцията за сравнение е вградена в сортирането. Това е ползата от метапрограмирането според мен, вие пишете общ код, но компилаторът произвежда код като ръчно кодирания !
Друго място, където намирам метапрограмирането за много красиво, е когато пишете библиотека като boost::spirit или boost::xpressive, с spirit можете да напишете EBNF в C++ и да оставите компилацията да провери EBNF синтаксиса вместо вас и с xpressive можете да пишете регулярен израз и да оставите компилатора да провери и синтаксиса на регулярния израз вместо вас!
Не съм сигурен дали питащият има предвид под TMP изчисляване на стойности по време на компилация. Това е пример, който написах с помощта на boost :)
unsigned long greatestCommonDivisor = boost::math::static_gcd<25657, 54887524>::value;
Каквото и да правите със C, не можете да имитирате горния код, основно трябва да го изчислите на ръка, след което да присвоите резултата на greatestCommonDivisor!
Отговорът е, че зависи.
Метапрограмирането на шаблон може да се използва за лесно писане на рекурсивни езикови парсери и те могат да бъдат неефективни в сравнение с внимателно изработена C програма или базирана на таблица реализация (напр. flex/bison/yacc).
От друга страна, можете да пишете метапрограми, които генерират разгънати цикли, което може да бъде по-ефективно от по-конвенционална C реализация, която използва цикли.
Основното предимство е, че метапрограмите позволяват на програмиста да прави повече с по-малко код.
Недостатъкът е, че ви дава и пистолет за гатлинг, с който да се простреляте в крака.
Метапрограмирането на шаблон може да се разглежда като изпълнение по време на компилация.
Времето за компилиране ще отнеме повече време за компилиране на вашия код, тъй като той трябва да компилира и след това да изпълни шаблоните, да генерира код и след това да компилира отново.
Не съм сигурен за разходите за време на изпълнение, не би трябвало да е много повече, отколкото ако го напишете сами в C код, бих си представил.
Работих по проект, в който друг програмист беше изпробвал метапрограмиране. Беше ужасно. Беше пълно главоболие. Аз съм средностатистически програмист с много опит в C++ и опитът да измисля какво, по дяволите, се опитват да направят, отне много повече време, отколкото ако го бяха написали направо в началото.
Изтощен съм от C++ MetaProgramming заради този опит.
Аз съм твърдо убеден, че най-добрият код е най-лесен за четене от средностатистически програмист. Четливостта на софтуера е приоритет №1. Мога да накарам всичко да работи, използвайки всеки език... но умението е да го направя четимо и лесно работещо за следващия човек в проекта. C++ MetaProgramming не успява да премине теста.
Метапрограмирането на шаблони не ви дава никакви магически сили по отношение на производителността. По същество това е много сложен препроцесор; винаги можете да напишете еквивалента на C или C++, просто може да ви отнеме много време.
Не мисля, че има шум, но ясен и прост отговор относно шаблоните се дава от ЧЗВ за C++: https://isocpp.org/wiki/faq/templates#overview-templates
Относно първоначалния въпрос: не може да се отговори, тъй като тези неща не са сравними.