Оптимизирует определенные функции с Ассемблером в программе C/C++, действительно стоящей того?

Что случилось с ним? То, что Вы пересматриваете сборщик "мусора" и средство выделения памяти, у которых между ними есть намного большая идея о фактическом использовании памяти Вашего приложения во времени выполнения, чем Вы.

12
задан sbi 11 September 2009 в 08:36
поделиться

11 ответов

I ' Я бы сказал, что оно того не стоит. Я работаю над программным обеспечением, которое выполняет 3D-рендеринг в реальном времени (т. Е. Рендеринг без помощи графического процессора). Я действительно широко использую встроенные функции компилятора SSE - много уродливого кода, заполненного __ mm_add_ps () и другими, - но мне не нужно было перекодировать функцию в сборке в течение очень долгого времени.

Мой опыт показывает, что хорошие современные оптимизирующие компиляторы чертовски эффективны при сложной оптимизации на микроуровне. Они будут выполнять сложные преобразования цикла, такие как переупорядочивание, разворачивание, конвейерная обработка, блокировка, мозаика, заедание, разделение и тому подобное. Они запланируют инструкции, чтобы конвейер оставался заполненным, векторизовали простые циклы и внедрили несколько интересных хитростей. Современные компиляторы - невероятно очаровательные звери.

Сможете ли вы победить их? Хорошо обязательно, учитывая, что они выбирают оптимизацию для использования с помощью эвристики, они неизбежно иногда ошибаются. Но я обнаружил, что гораздо лучше оптимизировать сам код, глядя на картину в целом. Я размещаю свои структуры данных наиболее удобным для кеширования способом? Я делаю что-то необычное, что вводит компилятор в заблуждение? Могу ли я что-нибудь немного переписать, чтобы компилятор лучше подсказывал? Не лучше ли мне что-то пересчитать, а не хранить? Не удалось ли вставить справку по предварительной выборке? У меня где-то есть ложное совместное использование кеша? Есть ли небольшая оптимизация кода, которую компилятор считает небезопасной, но здесь допустима (например, преобразование деления в умножение на обратное)?

Мне нравится работать с компилятором, а не против него. Пусть он позаботится об оптимизации на микроуровне, чтобы вы могли сосредоточиться на оптимизации на мезоуровне.

27
ответ дан 2 December 2019 в 02:55
поделиться

Единственный возможный ответ на этот вопрос: да, если есть прирост производительности, который актуален и полезен.

Я предполагаю, что на самом деле вопрос должен заключаться в следующем: можете ли вы получить значительный прирост производительности используя ассемблер в программе C / C ++?

Ответ - да.

Случаи, когда вы получаете значимое повышение производительности, вероятно, уменьшились за последние 10-20 лет, поскольку библиотеки и компиляторы улучшились, но для такой архитектуры, как x86, в частности, ручная оптимизация в определенных приложениях (особенно связанных с графикой) это может быть выполнено.

Но, как и все остальное, не оптимизируйте, пока вам не понадобится.

Я бы сказал, что оптимизация алгоритмов и написание высокоэффективного C (в частности) даст гораздо больший прирост производительности при меньших затратах времени, чем переписывание на языке ассемблера в подавляющем большинстве случаев.

10
ответ дан 2 December 2019 в 02:55
поделиться

Сложность в том, можете ли вы сделать работу по оптимизации лучше, чем компилятор, учитывая архитектуру современных процессоров. Если вы разрабатываете для простого процессора (например, для встроенных систем), вы можете провести разумную оптимизацию, но для конвейерной архитектуры оптимизация намного сложнее, так как вам нужно понять, как работает конвейерная обработка.

Итак, учитывая, что , если вы можете выполнить эту оптимизацию, и вы работаете над чем-то, что профилировщик сообщает вам слишком медленно, и это часть, которая должна быть максимально быстрой, тогда да, оптимизация имеет смысл.

6
ответ дан 2 December 2019 в 02:55
поделиться

Возможно

Это полностью зависит от конкретной программы

Вам понадобится профиль, который вы получите с помощью инструмента профилирования, прежде чем вы узнаете. Некоторые программы проводят все свое время в ожидании базы данных или у них просто нет сконцентрированного времени выполнения на небольшой площади. Без этого сборка не очень помогает.

Существует эмпирическое правило, согласно которому 90% времени выполнения приходится на 10% кода. Вам действительно нужно одно очень сильное узкое место, а оно есть не в каждой программе.

Кроме того, машины теперь настолько быстры, что некоторые из низко висящих плодов были съедены, так сказать, компиляторами и ядрами ЦП. Например, вы пишете код лучше, чем компилятор, и вдвое сокращаете количество инструкций. Даже в этом случае, если вы в конечном итоге выполните такое же количество обращений к памяти, и если они будут узким местом, вы можете не выиграть.

Конечно, вы можете начать предварительную загрузку регистров в предыдущих итерациях цикла, но компилятор, вероятно, уже пытается это сделать.

Изучение сборки действительно более важно как способ понять, чем на самом деле является машина, а не как способ обыграть компилятор. Но попробуйте!

5
ответ дан 2 December 2019 в 02:55
поделиться

Есть одна область, где оптимизация сборки все еще регулярно выполняется - встроенное программное обеспечение . Эти процессоры обычно не очень мощные и имеют множество архитектурных особенностей, которые не могут быть использованы компилятором для оптимизации. Тем не менее, это следует делать только для особо ограниченных областей кода и , это должно быть очень хорошо задокументировано.

4
ответ дан 2 December 2019 в 02:55
поделиться

Я предполагаю, что вы профилировали свой код и обнаружили небольшой цикл, который занимает большую часть время.

Сначала попробуйте перекомпилировать с более агрессивной оптимизацией компилятора, а затем перепрофилировать. Если вы по желанию запускаете все оптимизации компилятора, но вам все еще нужно больше производительности, я рекомендую взглянуть на сгенерированную сборку.

Что я обычно делаю после просмотра кода сборки для функции, так это смотрю, как я могу изменить код C, чтобы заставить компилятор писать лучшую сборку. Преимущество этого способа,

4
ответ дан 2 December 2019 в 02:55
поделиться

определенно да!

Вот демонстрация вычисления CRC-32, которое я написал на C ++, а затем оптимизировал на ассемблере x86 с помощью Visual Studio.

InitCRC32Table () следует вызывать по адресу запуск программы. CalcCRC32 () вычислит CRC для данного блока памяти. Обе функции реализованы как на ассемблере, так и на C ++.

На типичном компьютере с процессором Pentium вы заметите, что функция ассемблера CalcCRC32 () на 50% быстрее, чем код C ++.

Реализация ассемблера не является MMX или SSE, но простой код x86. Компилятор никогда не создаст код, столь же эффективный, как код ассемблера, созданный вручную.

    DWORD* panCRC32Table = NULL; // CRC-32 CCITT 0x04C11DB7

    void DoneCRCTables()
    {
        if (panCRC32Table )
        {
            delete[] panCRC32Table;
            panCRC32Table= NULL;
        }
    }

    void InitCRC32Table()
    {
        if (panCRC32Table) return;
        panCRC32Table= new DWORD[256];

        atexit(DoneCRCTables);

    /*
        for (int bx=0; bx<256; bx++)
        {
            DWORD eax= bx;
            for (int cx=8; cx>0; cx--)
                if (eax & 1)
                    eax= (eax>>1) ^ 0xEDB88320;
                else
                    eax= (eax>>1)             ;
            panCRC32Table[bx]= eax;
        }
    */
            _asm cld
            _asm mov    edi, panCRC32Table
            _asm xor    ebx, ebx
        p0: _asm mov    eax, ebx
            _asm mov    ecx, 8
        p1: _asm shr    eax, 1
            _asm jnc    p2
            _asm xor    eax, 0xEDB88320           // bit-swapped 0x04C11DB7
        p2: _asm loop   p1
            _asm stosd
            _asm inc    bl
            _asm jnz    p0
    }


/*
DWORD inline CalcCRC32(UINT nLen, const BYTE* cBuf, DWORD nInitVal= 0)
{
    DWORD crc= ~nInitVal;
    for (DWORD n=0; n<nLen; n++)
        crc= (crc>>8) ^ panCRC32Table[(crc & 0xFF) ^ cBuf[n]];
    return ~crc;
}
*/
DWORD inline __declspec (naked) __fastcall CalcCRC32(UINT        nLen       ,
                                                     const BYTE* cBuf       ,
                                                     DWORD       nInitVal= 0 ) // used to calc CRC of chained bufs
{
        _asm mov    eax, [esp+4]         // param3: nInitVal
        _asm jecxz  p2                   // __fastcall param1 ecx: nLen
        _asm not    eax
        _asm push   esi
        _asm push   ebp
        _asm mov    esi, edx             // __fastcall param2 edx: cBuf
        _asm xor    edx, edx
        _asm mov    ebp, panCRC32Table
        _asm cld

    p1: _asm mov    dl , al
        _asm shr    eax, 8
        _asm xor    dl , [esi]
        _asm xor    eax, [ebp+edx*4]
        _asm inc    esi
        _asm loop   p1

        _asm pop    ebp
        _asm pop    esi
        _asm not    eax
    p2: _asm ret    4                    // eax- returned value. 4 because there is 1 param in stack
}

// test code:

#include "mmSystem.h"                      // timeGetTime
#pragma comment(lib, "Winmm.lib" )

InitCRC32Table();

BYTE* x= new BYTE[1000000];
for (int i= 0; i<1000000; i++) x[i]= 0;

DWORD d1= ::timeGetTime();

for (i= 0; i<1000; i++)
    CalcCRC32(1000000, x, 0);

DWORD d2= ::timeGetTime();

TRACE("%d\n", d2-d1);
2
ответ дан 2 December 2019 в 02:55
поделиться

Для типичного разработчика небольшого магазина, пишущего приложение, компромисс между приростом производительности и усилиями почти никогда не оправдывает написание ассемблера. Даже в ситуациях, когда сборка может удвоить скорость некоторого узкого места, усилия часто неоправданны. В более крупной компании это может быть оправдано, если вы "парень производительности".

Однако для автора библиотеки даже небольшие улучшения с большими усилиями часто оправданы, потому что это экономит время тысяч разработчиков и пользователей, которые использовать библиотеку в конце. Тем более для разработчиков компиляторов. Если вы можете добиться 10% -ного выигрыша в эффективности функции базовой системной библиотеки, это может буквально сэкономить тысячелетия (или больше) времени автономной работы, распределенной среди вашей пользовательской базы.

4
ответ дан 2 December 2019 в 02:55
поделиться

Я бы сказал, что для большинства людей и большинства приложений это того не стоит. Компиляторы очень хороши в оптимизации именно для той архитектуры, для которой они компилируются.

Это не означает, что оптимизация в сборке не является необоснованной. Многие математические операции и низкоуровневый интенсивный код часто оптимизируются с помощью специальных инструкций ЦП, таких как SSE * и т. Д., Чтобы избежать использования сгенерированных компилятором инструкций / регистров. В конце концов, человек точно знает суть программы. Компилятор может предполагать только так много.

Я бы сказал, что если вы не на том уровне, где вы знаете, что ваша собственная сборка будет быстрее, тогда я позволю компилятору сделать тяжелую работу.

1
ответ дан 2 December 2019 в 02:55
поделиться

Не забывайте, что при переписывании в сборке вы теряете переносимость. Сегодня вам все равно, но завтра ваши клиенты могут захотеть, чтобы ваше программное обеспечение было на другой платформе, и им эти фрагменты сборки действительно повредят.

1
ответ дан 2 December 2019 в 02:55
поделиться

Good answers. I would say "Yes" IF you have already done performance tuning like this, and you are now in the position of

  1. KNOWING (not guessing) that some particular hot-spot is taking more than 30% of your time,

  2. seeing just what assembly language the compiler generated for it, after all attempts to make it generate optimal code,

  3. knowing how to improve on that assembler code.

  4. being willing to give up some portability.

Compilers do not know everything you know, so they are defensive and cannot take advantage of what you know.

As one example, they write subroutine entry and exit code in a general way that works no matter what the subroutine contains. You, on the other hand, may be able to hand-code little routines that dispense with frame pointers, saving registers, and stuff like that. You're risking bugs, but it is possible to beat the compiler.

1
ответ дан 2 December 2019 в 02:55
поделиться