Я попытался сравнить производительность встроенного языка ассемблера и кода C ++, поэтому я написал функцию, которая добавляет два массива размером 2000 для 100000 раз. Вот код:
#define TIMES 100000
void calcuC(int *x,int *y,int length)
{
for(int i = 0; i < TIMES; i++)
{
for(int j = 0; j < length; j++)
x[j] += y[j];
}
}
void calcuAsm(int *x,int *y,int lengthOfArray)
{
__asm
{
mov edi,TIMES
start:
mov esi,0
mov ecx,lengthOfArray
label:
mov edx,x
push edx
mov eax,DWORD PTR [edx + esi*4]
mov edx,y
mov ebx,DWORD PTR [edx + esi*4]
add eax,ebx
pop edx
mov [edx + esi*4],eax
inc esi
loop label
dec edi
cmp edi,0
jnz start
};
}
Вот main()
:
int main() {
bool errorOccured = false;
setbuf(stdout,NULL);
int *xC,*xAsm,*yC,*yAsm;
xC = new int[2000];
xAsm = new int[2000];
yC = new int[2000];
yAsm = new int[2000];
for(int i = 0; i < 2000; i++)
{
xC[i] = 0;
xAsm[i] = 0;
yC[i] = i;
yAsm[i] = i;
}
time_t start = clock();
calcuC(xC,yC,2000);
// calcuAsm(xAsm,yAsm,2000);
// for(int i = 0; i < 2000; i++)
// {
// if(xC[i] != xAsm[i])
// {
// cout<<"xC["<<i<<"]="<<xC[i]<<" "<<"xAsm["<<i<<"]="<<xAsm[i]<<endl;
// errorOccured = true;
// break;
// }
// }
// if(errorOccured)
// cout<<"Error occurs!"<<endl;
// else
// cout<<"Works fine!"<<endl;
time_t end = clock();
// cout<<"time = "<<(float)(end - start) / CLOCKS_PER_SEC<<"\n";
cout<<"time = "<<end - start<<endl;
return 0;
}
Затем я запускаю программу пять раз, чтобы получить циклы процессора, которые можно рассматривать как время. Каждый раз я вызываю только одну из функций, упомянутых выше.
И тут приходит результат.
Функция сборки версии:
Debug Release
---------------
732 668
733 680
659 672
667 675
684 694
Average: 677
Функция версии C ++:
Debug Release
-----------------
1068 168
999 166
1072 231
1002 166
1114 183
Average: 182
Код C ++ в режиме выпуска почти в 3,7 раза быстрее кода сборки. Зачем?
Я полагаю, что код сборки, который я написал, не так эффективен, как код, сгенерированный GCC. Обычному программисту, как я, трудно писать код быстрее, чем его оппонент, сгенерированный компилятором. Значит ли это, что я не должен доверять производительности языка ассемблера, написанного моими руками, сосредоточиться на C ++ и забыть о языке ассемблера?
источник
Ответы:
Да, в большинстве случаев.
Прежде всего, вы исходите из неверного предположения, что низкоуровневый язык (в данном случае ассемблер) всегда будет генерировать более быстрый код, чем высокоуровневый язык (в данном случае C ++ и C). Неправда. Всегда ли код C быстрее, чем код Java? Нет, потому что есть другая переменная: программист. То, как вы пишете код и знание деталей архитектуры, сильно влияет на производительность (как вы видели в этом случае).
Вы всегда можете создать пример, в котором ручной ассемблерный код лучше скомпилированного кода, но обычно это вымышленный пример или отдельная подпрограмма, а не настоящая программа, содержащая более 500 000 строк кода C ++). Я думаю, что компиляторы будут производить лучший ассемблерный код 95% раз, а иногда, только в редких случаях, вам может понадобиться написать ассемблерный код для нескольких коротких, часто используемых , критичных к производительности подпрограмм или когда вам нужно получить доступ к функциям вашего любимого языка высокого уровня не выставляет. Хотите прикосновения этой сложности? Прочитайте этот удивительный ответ здесь на SO.
Почему это?
Прежде всего потому, что компиляторы могут выполнять оптимизации, которые мы даже не можем себе представить (см. Этот короткий список ), и они будут делать их в считанные секунды (когда нам могут понадобиться дни ).
Когда вы пишете код на ассемблере, вы должны создавать четко определенные функции с четко определенным интерфейсом вызовов. Однако они могут принимать во внимание оптимизацию всей программы и межпроцедурную оптимизацию, такую как распределение регистров , постоянное распространение , удаление общего подвыражения , планирование команд и другие сложные, неочевидные оптимизации ( например, модель многогранника ). Об архитектуре RISC ребята перестали беспокоиться об этом много лет назад (например, планирование инструкций очень трудно настраивать вручную ), а современные процессоры CISC имеют очень длинные конвейеры слишком.
Для некоторых сложных микроконтроллеров даже системные библиотеки пишутся на C, а не на ассемблере, потому что их компиляторы создают лучший (и простой в обслуживании) конечный код.
Иногда компиляторы могут автоматически использовать некоторые инструкции MMX / SIMDx , и если вы их не используете, вы просто не можете их сравнивать (другие ответы уже хорошо рассмотрели ваш код сборки). Просто для циклов это краткий список оптимизаций цикла того, что обычно проверяется компилятором (как вы думаете, вы могли бы сделать это самостоятельно, когда ваш график был выбран для программы на C #?) Если вы пишете что-то в сборке, я думаю, что вы должны рассмотреть хотя бы несколько простых оптимизаций . Пример учебника для массивов - развернуть цикл (его размер известен во время компиляции). Сделайте это и запустите тест снова.
В наши дни очень редко нужно использовать язык ассемблера по другой причине: множество разных процессоров . Вы хотите поддержать их всех? У каждого есть определенная микроархитектура и несколько определенных наборов команд . Они имеют разное количество функциональных блоков, и инструкции по сборке должны быть расположены так, чтобы они все были заняты . Если вы пишете на C, вы можете использовать PGO, но при сборке вам понадобятся глубокие знания этой конкретной архитектуры (а также переосмыслить и переделать все для другой архитектуры ). Для небольших задач компилятор обычно делает это лучше, а для сложных задач обычно работа не оплачивается (икомпилятор может быть лучше в любом случае).
Если вы сядете и посмотрите на свой код, то, вероятно, вы увидите, что вы получите больше, чтобы перестроить свой алгоритм, чем перевести на сборку (см. Этот великолепный пост здесь, в SO ), есть высокоуровневые оптимизации (и подсказки компилятору) вы можете эффективно применить его, прежде чем прибегнуть к языку ассемблера. Вероятно, стоит упомянуть, что, часто используя встроенные функции, вы получите прирост производительности, который ищете, и компилятор все равно сможет выполнять большинство его оптимизаций.
Все это говорит о том, что даже если вы можете создавать сборочный код в 5-10 раз быстрее, вам следует спросить своих клиентов, предпочитают ли они платить одну неделю своего времени или покупать процессор на 50 $ быстрее . Чрезвычайная оптимизация чаще всего (и особенно в приложениях LOB) просто не требуется от большинства из нас.
источник
Ваш ассемблерный код неоптимален и может быть улучшен:
loop
инструкцию, которая, как известно, очень медленная на большинстве современных процессоров (возможно, в результате использования древней сборочной книги *)Поэтому, если вы не значительно улучшите свои навыки в отношении ассемблера, вам не имеет смысла писать код на ассемблере для повышения производительности.
* Конечно, я не знаю, действительно ли вы получили
loop
инструкцию из древней сборочной книги. Но вы почти никогда не видите его в коде реального мира, так как каждый компилятор достаточно умен, чтобы не излучатьloop
, вы видите это только в ИМХО плохих и устаревших книгах.источник
loop
(и многие «устаревшие» инструкции), если вы оптимизируете по размеруЕще до углубления в сборку существуют преобразования кода, которые существуют на более высоком уровне.
может быть преобразовано в Loop Rotation :
что намного лучше, насколько локальность памяти идет.
Это может быть дополнительно оптимизировано, выполнение
a += b
X раз эквивалентно тому, чтоa += X * b
мы получаем:однако, кажется, мой любимый оптимизатор (LLVM) не выполняет это преобразование.
[править] Я обнаружил, что преобразование выполняется, если у нас есть
restrict
спецификатор tox
иy
. Действительно, без этого ограничения,x[j]
иy[j]
может псевдоним в том же месте, что делает это преобразование ошибочным. [конец редактирования]Во всяком случае, это , я думаю, оптимизированная версия C. Уже намного проще. Основываясь на этом, вот мой взлом в ASM (я позволил Clang генерировать его, я бесполезен в этом):
Боюсь, я не понимаю, откуда берутся все эти инструкции, однако вы всегда можете повеселиться и попробовать сравнить их ... но я все равно буду использовать оптимизированную версию C, а не сборочную, в коде, гораздо более портативный.
источник
x
иy
. То есть компилятор не может быть уверен, что для всехi,j
у[0, length)
нас естьx + i != y + j
. Если есть перекрытие, то оптимизация невозможна. Язык C ввелrestrict
ключевое слово, чтобы сообщить компилятору, что два указателя не могут использовать псевдонимы, однако он не работает для массивов, потому что они все еще могут перекрываться, даже если они не имеют точно псевдоним.__restrict
). SSE2 является базовой для x86-64, и с тасовкой SSE2 может делать 2x 32-битные умножения одновременно (производя 64-битные продукты, следовательно, тасование, чтобы собрать результаты обратно). godbolt.org/z/r7F_uo . (SSE4.1 необходим дляpmulld
: упакованный 32x32 => 32-битное умножение). В GCC есть хитрый прием преобразования постоянных целочисленных множителей в сдвиг / сложение (и / или вычитание), что хорошо для умножителей с несколькими установленными битами. Переполненный в случайном порядке код Clang будет препятствовать пропускной способности в процессорах Intel.Краткий ответ: да.
Длинный ответ: да, если вы действительно не знаете, что делаете, и у вас есть причина для этого.
источник
Я исправил свой код asm:
Результаты для релизной версии:
Код сборки в режиме выпуска почти в 2 раза быстрее, чем C ++.
источник
xmm0
вместоmm0
), вы получите еще одно ускорение в два раза ;-)paddd xmm
(после проверки совпадения междуx
иy
, так как вы не использовалиint *__restrict x
). Например, gcc делает это: godbolt.org/z/c2JG0- . Или после встраивания вmain
него не нужно проверять наличие совпадений, потому что он может видеть распределение и доказывать, что они не перекрываются. (И в некоторых реализациях x86-64 можно было бы принять 16-байтовое выравнивание, что не относится к автономному определению.) И если вы скомпилируетеgcc -O3 -march=native
, вы можете получить 256-битный или 512-битный векторизации.Да, это именно то, что это значит, и это верно для каждого языка. Если вы не знаете, как писать эффективный код на языке X, то вы не должны доверять своей способности писать эффективный код на X. И поэтому, если вы хотите эффективный код, вам следует использовать другой язык.
Сборка особенно чувствительна к этому, потому что то, что вы видите, это то, что вы получаете. Вы пишете конкретные инструкции, которые вы хотите, чтобы ЦП выполнял. С языками высокого уровня в компиляторе есть компилятор, который может преобразовать ваш код и устранить многие недостатки. Со сборкой ты сам по себе.
источник
В настоящее время единственной причиной использования ассемблера является использование некоторых функций, недоступных языку.
Это относится к:
Но современные компиляторы достаточно умны, они могут даже заменить два отдельных оператора, например,
d = a / b; r = a % b;
одной инструкцией, которая вычисляет деление и остаток за один раз, если он доступен, даже если в C такого оператора нет.источник
Это правда, что современный компилятор проделывает потрясающую работу по оптимизации кода, но я все равно рекомендую вам продолжить изучение ассемблера.
Во-первых, вас это явно не пугает , это большой, большой плюс, затем - вы на правильном пути, выполняя профилирование для проверки или отклонения ваших предположений о скорости , вы запрашиваете мнение опытных людей , и вы иметь величайший оптимизирующий инструмент, известный человечеству: мозг .
По мере того, как ваш опыт увеличивается, вы узнаете, когда и где его использовать (обычно самые тесные, самые внутренние циклы в вашем коде после глубокой оптимизации на алгоритмическом уровне).
Для вдохновения я бы порекомендовал вам посмотреть статьи Майкла Абраша (если вы не слышали о нем, он - гуру оптимизации; он даже сотрудничал с Джоном Кармаком в оптимизации программного рендеринга Quake!)
источник
Я изменил код asm:
Результаты для релизной версии:
Код сборки в режиме выпуска почти в 4 раза быстрее, чем C ++. IMHo, скорость сборки кода зависит от программиста
источник
shr ecx,2
излишне, потому что длина массива уже задана,int
а не в байтах. Таким образом, вы в основном достигаете той же скорости. Вы можете попробоватьpaddd
ответить от Гарольда, это действительно будет быстрее.это очень интересная тема!
Я изменил MMX по SSE в коде Саши.
Вот мои результаты:
Код ассемблера с SSE в 5 раз быстрее, чем C ++
источник
Большинство компиляторов языков высокого уровня очень оптимизированы и знают, что делают. Вы можете попробовать сбросить код дизассемблирования и сравнить его с вашей нативной сборкой. Я верю, что вы увидите несколько приятных трюков, которые использует ваш компилятор.
Просто например, даже в том, что я не уверен, что это больше правильно :)
Выполнение:
стоить больше циклов, чем
который делает то же самое.
Компилятор знает все эти хитрости и использует их.
источник
Компилятор победил тебя. Я попробую, но не буду давать никаких гарантий. Я буду считать , что «умножение» на TIMES предназначается , чтобы сделать его более актуальным тест производительности, что
y
иx
в 16-выровнены, и чтоlength
является ненулевым кратно 4. Это, наверное , все верно в любом случае.Как я уже сказал, я не даю никаких гарантий. Но я буду удивлен, если это будет сделано намного быстрее - узким местом здесь является пропускная способность памяти, даже если все это удар L1.
источник
mov ecx, length, lea ecx,[ecx*4], mov eax,16... add ecx,eax
а затем просто будете использовать [esi + ecx] везде, где вы будете избегать остановки одного цикла на инструкцию, ускоряя циклы. (Если у вас последняя версия Skylake, это не относится). Добавление reg, reg только делает цикл более тесным, что может или не может помочь.Просто слепая реализация одного и того же алгоритма, инструкция за инструкцией, в сборке гарантированно будет медленнее, чем то, что может сделать компилятор.
Это потому, что даже самая маленькая оптимизация, которую выполняет компилятор, лучше, чем ваш жесткий код без какой-либо оптимизации.
Конечно, можно обойти компилятор, особенно если это небольшая локализованная часть кода, мне даже пришлось сделать это самому, чтобы получить прибл. В 4 раза быстрее, но в этом случае мы должны полагаться на хорошее знание аппаратного обеспечения и многочисленные, казалось бы, не интуитивные трюки.
источник
Как компилятор я бы заменил цикл с фиксированным размером на множество задач выполнения.
будет производить
и в конце концов он узнает, что "a = a + 0;" бесполезен, поэтому он удалит эту строку. Надеемся, что-то в вашей голове теперь готовы прикрепить некоторые параметры оптимизации в качестве комментария. Все эти очень эффективные оптимизации сделают скомпилированный язык быстрее.
источник
a
является изменчивым, есть хороший шанс, что компилятор просто сделает этоint a = 13;
с самого начала.Это именно то, что это значит. Оставьте микрооптимизации компилятору.
источник
Мне нравится этот пример, потому что он демонстрирует важный урок о низкоуровневом коде. Да, вы можете написать ассемблер так же быстро, как ваш C-код. Это тавтологически верно, но не обязательно что- то значит . Явно кто-то может, иначе ассемблер не узнает соответствующих оптимизаций.
Аналогично, тот же принцип применяется, когда вы поднимаетесь по иерархии языковой абстракции. Да, вы можете написать синтаксический анализатор на C так же быстро, как быстрый и грязный Perl-скрипт, и многие это делают. Но это не значит, что, поскольку вы использовали C, ваш код будет быстрым. Во многих случаях языки высокого уровня выполняют оптимизации, которые вы, возможно, даже не рассматривали.
источник
Во многих случаях оптимальный способ выполнения некоторой задачи может зависеть от контекста, в котором выполняется задача. Если подпрограмма написана на ассемблере, последовательность команд, как правило, не может быть изменена в зависимости от контекста. В качестве простого примера рассмотрим следующий простой метод:
Компилятор для 32-битного кода ARM, учитывая вышеизложенное, скорее всего, отобразит его примерно так:
или возможно
Это может быть немного оптимизировано в собранном вручную коде, например:
или
Оба из собранных вручную подходов потребовали бы 12 байтов кода, а не 16; последний заменит «нагрузку» на «добавление», что на ARM7-TDMI выполнит два цикла быстрее. Если бы код собирался выполняться в контексте, где r0 не знал / не заботился, версии на ассемблере были бы несколько лучше, чем скомпилированная версия. С другой стороны, предположим, что компилятор знал, что какой-то регистр [например, r5] будет содержать значение, которое находилось в пределах 2047 байтов от желаемого адреса 0x40001204 [например, 0x40001000], и дополнительно знал, что собирается какой-то другой регистр [например, r7] хранить значение, младшие биты которого были 0xFF. В этом случае компилятор может оптимизировать C-версию кода, чтобы просто:
Гораздо короче и быстрее, чем даже оптимизированный вручную код сборки. Далее, предположим, что set_port_high произошел в контексте:
Совсем неправдоподобно при кодировании для встроенной системы. Если
set_port_high
написано в коде сборки, компилятор должен переместить r0 (который содержит возвращаемое значениеfunction1
) куда-то еще, прежде чем вызывать код сборки, а затем переместить это значение обратно в r0 (посколькуfunction2
его первый параметр в r0 будет ожидать), поэтому для «оптимизированного» кода сборки потребуется пять инструкций. Даже если компилятор не знает ни одного регистра, содержащего адрес или значение для хранения, его версия из четырех команд (которую он может адаптировать для использования любых доступных регистров - не обязательно r0 и r1) превзойдет «оптимизированную» сборку языковая версия. Если компилятор имел необходимые адрес и данные в r5 и r7, как описано ранее, с одной инструкцией:function1
не изменил бы эти регистры, и, таким образом, он мог бы заменитьset_port_high
strb
четыре инструкции меньше и быстрее, чем «оптимизированный вручную» код сборки.Обратите внимание, что оптимизированный вручную ассемблерный код может часто превосходить компилятор в тех случаях, когда программист знает точный поток программы, но компиляторы работают лучше в тех случаях, когда фрагмент кода написан до того, как известен его контекст, или когда один фрагмент исходного кода может быть вызывается из нескольких контекстов [если
set_port_high
он используется в пятидесяти различных местах кода, компилятор может независимо решить для каждого из них, как лучше его расширить].В целом, я хотел бы предположить, что язык ассемблера имеет тенденцию давать наибольшие улучшения производительности в тех случаях, когда к каждому фрагменту кода можно подходить с очень ограниченным числом контекстов, и это может отрицательно сказаться на производительности в местах, где фрагмент к коду можно подходить из разных контекстов. Интересно (и удобно) случаи, когда сборка наиболее выгодна для производительности, часто в тех случаях, когда код наиболее прост и удобен для чтения. Места, в которых код на ассемблере превращается в неприятный беспорядок, часто бывают теми, где написание на ассемблере дает наименьшее преимущество в производительности.
[Незначительное замечание: в некоторых местах ассемблерный код может использоваться для создания гипероптимизированного тупого беспорядка; например, один фрагмент кода, который я сделал для ARM, должен был извлечь слово из ОЗУ и выполнить одну из примерно двенадцати подпрограмм, основанных на верхних шести битах значения (многие значения сопоставлены одной и той же подпрограмме). Я думаю, что я оптимизировал этот код до чего-то вроде:
Регистр r8 всегда содержал адрес главной таблицы диспетчеризации (в цикле, где код тратит 98% своего времени, ничто никогда не использовало его для каких-либо других целей); все 64 записи относятся к адресам в 256 байтах, предшествующих ему. Поскольку основной цикл имел в большинстве случаев жесткий предел времени выполнения около 60 циклов, выборка и отправка из девяти циклов были очень полезны для достижения этой цели. Использование таблицы из 256 32-битных адресов было бы на один цикл быстрее, но поглотило бы 1 КБ очень ценной оперативной памяти [флэш-память добавила бы более одного состояния ожидания]. Использование 64 32-битных адресов потребовало бы добавления инструкции для маскировки некоторых битов из извлеченного слова, и все равно потребляло бы на 192 байт больше, чем таблица, которую я фактически использовал. Использование таблицы 8-битных смещений позволило получить очень компактный и быстрый код, но не то, что я ожидал, когда-нибудь придет компилятор; Я также не ожидал бы, что компилятор выделит регистр «полный рабочий день» для хранения адреса таблицы.
Приведенный выше код был разработан для работы в качестве автономной системы; он мог бы периодически вызывать код C, но только в определенные моменты времени, когда аппаратное обеспечение, с которым оно взаимодействовало, могло безопасно переводиться в состояние «ожидания» на два интервала примерно в одну миллисекунду каждые 16 мс.
источник
В последнее время все оптимизации скорости, которые я проводил, заменяли поврежденный мозг медленный код просто разумным кодом. Но так как скорость была действительно критической, и я приложил серьезные усилия, чтобы сделать что-то быстрое, результатом всегда был итеративный процесс, где каждая итерация давала более глубокое понимание проблемы, находя способы решения проблемы с меньшим количеством операций. Окончательная скорость всегда зависела от того, насколько я понял проблему. Если бы на каком-то этапе я использовал ассемблерный код или код C, который был чрезмерно оптимизирован, процесс поиска лучшего решения пострадал бы, а конечный результат был бы медленнее.
источник
Когда я кодирую в ASM, я реорганизую инструкции вручную, чтобы ЦП мог выполнять больше их параллельно, когда это логически возможно. Например, я почти не использую ОЗУ, когда кодирую в ASM: в ASM может быть более 20000 строк кода, и я никогда не использовал push / pop.
Вы можете потенциально перейти в середину кода операции, чтобы самостоятельно изменить код и поведение без возможного штрафа за самоизменяющийся код. Доступ к регистрам занимает 1 такт (иногда занимает 0,25 тактов) процессора. Доступ к ОЗУ может занять сотни.
В моем последнем приключении с ASM я ни разу не использовал оперативную память для хранения переменной (на тысячи строк ASM). ASM может быть потенциально невообразимо быстрее, чем C ++. Но это зависит от множества переменных факторов, таких как:
Сейчас я изучаю C # и C ++, потому что я понял, что производительность имеет значение! В свободное время вы можете попытаться создать самые быстрые программы, используя только ASM. Но чтобы что-то производить, используйте язык высокого уровня.
Например, последняя программа, которую я написал, использовала JS и GLSL, и я никогда не замечал проблем с производительностью, даже говоря о JS, которая работает медленно. Это потому, что простая концепция программирования GPU для 3D делает скорость языка, который посылает команды в GPU, почти неактуальной.
Скорость только ассемблера на голом металле неопровержима. Может ли это быть еще медленнее внутри C ++? - Это может быть потому, что вы пишете ассемблерный код с компилятором, не использующим ассемблер для начала.
Мой личный совет - никогда не писать ассемблерный код, если вы можете его избежать, хотя я люблю ассемблер.
источник
Все ответы здесь, кажется, исключают один аспект: иногда мы не пишем код для достижения конкретной цели, а просто для удовольствия . Возможно, это не экономно тратить время на это, но, возможно, нет большего удовлетворения, чем победа по быстродействующему фрагменту кода, оптимизированному для компилятора, с альтернативой asm, созданной вручную.
источник
Компилятор c ++ после оптимизации на организационном уровне генерирует код, который будет использовать встроенные функции целевого процессора. HLL никогда не будет опережать или превосходить ассемблер по нескольким причинам; 1.) HLL будет скомпилирован и выведен с кодом Accessor, проверкой границ и, возможно, встроенным сборщиком мусора (ранее рассматривавшим область действия в манере ООП), все требующие циклов (триггеры и флопсы). В наши дни HLL отлично справляется со своей задачей (включая более новый C ++ и другие, такие как GO), но если они превосходят ассемблер (а именно ваш код), вам необходимо обратиться к документации по процессору - сравнения с неаккуратным кодом, безусловно, неубедительны, и компилируемые языки, такие как ассемблер, все решают вплоть до кода операции HLL абстрагирует детали и не устраняет их, иначе приложение не будет запущено, даже если оно будет распознано операционной системой хоста.
Большая часть кода на ассемблере (прежде всего объекты) выводится как «безголовый» для включения в другие исполняемые форматы с гораздо меньшей необходимой обработкой, следовательно, это будет намного быстрее, но гораздо более небезопасно; если исполняемый файл выводится ассемблером (NAsm, YAsm и т. д.), он все равно будет работать быстрее, пока полностью не совпадет с кодом HLL по функциональности, тогда результаты могут быть точно взвешены.
Вызов объекта кода на основе ассемблера из HLL в любом формате по своей сути добавит накладные расходы на обработку, а также вызовы пространства памяти, использующие глобально распределенную память для переменных / постоянных типов данных (это относится как к LLL, так и к HLL). Помните, что конечный результат в конечном итоге использует ЦП как его api и abi относительно аппаратного обеспечения (код операции), и ассемблеры, и «компиляторы HLL» по существу / принципиально идентичны, за исключением единственного истинного исключения - читаемости (грамматика).
Консольное приложение Hello world в ассемблере, использующем FAsm, имеет размер 1,5 КБ (а в Windows даже меньше во FreeBSD и Linux) и превосходит все, что GCC может выбросить в свой лучший день; Причины - неявное заполнение с помощью nops, проверка доступа и проверка границ. Настоящая цель - чистые библиотеки HLL и оптимизируемый компилятор, который нацелен на процессор «хардкорным» способом, и большинство делает это в наши дни (наконец). GCC не лучше, чем YAsm - это практика кодирования и понимание разработчика, которые находятся под вопросом, и «оптимизация» приходит после изучения новичка и промежуточного обучения и опыта.
Компиляторы должны связывать и собирать для вывода в том же коде операции, что и ассемблер, потому что эти коды - это все, что ЦП, кроме (CISC или RISC [PIC тоже]). YAsm оптимизировал и значительно очистил ранние NAsm, что в конечном итоге ускорило весь вывод этого ассемблера, но даже тогда YAsm, как и NAsm, по-прежнему создает исполняемые файлы с внешними зависимостями для библиотек ОС от имени разработчика, поэтому пробег может варьироваться. В завершение C ++ находится в точке, которая невероятна и намного более безопасна, чем ассемблер на 80+ процентов, особенно в коммерческом секторе ...
источник
ld
, но это не имеет значения, если вы не пытаетесь действительно оптимизировать размер файла (а не только размер файла). текстовый сегмент). См. Вихревое руководство по созданию действительно исполняемых исполняемых файлов ELF для Linux .std::vector
скомпилированы в режиме отладки. С ++ массивы не такие. Компиляторы могут проверять вещи во время компиляции, но если вы не включите дополнительные параметры защиты, проверка во время выполнения не будет. Посмотрите, например, функцию, которая увеличивает первые 1024 элементаint array[]
аргумента. Выходные данные asm не проверяются во время выполнения: godbolt.org/g/w1HF5t . Все, что он получает, это указательrdi
, без информации о размере. Программист должен избежать неопределенного поведения, никогда не вызывая его с массивом, меньшим 1024.new
, удалите вручнуюdelete
, без проверки границ). Вы можете использовать C ++ для создания дерьмового раздутого ассм / машинного кода (как и большинство программного обеспечения), но это ошибка программиста, а не C ++. Вы даже можете использовать,alloca
чтобы выделить пространство стека в виде массива.g++ -O3
генерации границ проверки коды для простого массива, или делать все остальное , что вы говорите. C ++ делает его гораздо проще генерировать раздутые двоичные файлы (и на самом деле вы должны быть осторожны , не к тому, если вы стремитесь к производительности), но это не в буквальном смысле неизбежно. Если вы понимаете, как C ++ компилируется в asm, вы можете получить код, который несколько хуже, чем вы могли бы писать вручную, но с встраиванием и постоянным распространением в большем масштабе, чем вы могли бы управлять вручную.Сборка может быть быстрее, если ваш компилятор генерирует много кода поддержки OO .
Редактировать:
Для downvoters: ОП написал "я должен ... сосредоточиться на C ++ и забыть о языке ассемблера?" и я поддерживаю мой ответ. Вы всегда должны следить за кодом, генерируемым ОО, особенно при использовании методов. Не забывать о языке ассемблера означает, что вы будете периодически просматривать сборку, создаваемую вашим ОО-кодом, которая, я считаю, необходима для написания хорошо работающего программного обеспечения.
На самом деле, это относится ко всему компилируемому коду, а не только к ОО.
источник