Как сохранить недоступный код? - PullRequest
3 голосов
/ 02 апреля 2010

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

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

int Test()
{
    int x = 2;
    for (int i=0 ; i<10 ; i++)
    {
        x *= 2;

        __asm {NOP}; // to skip it replace this
        __asm {NOP}; // by JMP 2 (after the goto)
            x *= 2; // Op to skip or not

        x *= 2;
    }
    return x;
}

В основной части моего теста я копирую эту функцию во вновь выделенную исполняемую память и заменяю NOP на JMP 2, чтобы следующее x * = 2 не выполнялось. JMP 2 действительно «пропускает следующие 2 байта».

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

Альтернатива, которая могла бы решить эту проблему:

__asm {NOP}; // to skip it replace this
__asm {NOP}; // by JMP 2 (after the goto)
goto dont_do_it;
    x *= 2; // Op to skip or not
dont_do_it:
x *= 2;

Я бы тогда хотел пропустить или нет goto, который имеет фиксированный размер. К сожалению, в режиме полной оптимизации goto и x * = 2 удаляются, потому что они недоступны во время компиляции.

Отсюда необходимость сохранить этот мертвый код.

Я использую VStudio 2008.

Ответы [ 7 ]

6 голосов
/ 02 апреля 2010

Вы можете сократить стоимость ветки до 10, просто переместив ее из цикла:

int Test()
{
    int x = 2;
    if (should_skip) {
        for (int i=0 ; i<10 ; i++)
        {
            x *= 2;
            x *= 2;
        }
    } else {
        for (int i=0 ; i<10 ; i++)
        {
            x *= 2;
            x *= 2;
            x *= 2;
        }
    }

    return x;
}

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

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

    int x = 2;
    if (should_skip) {
        doLoop<true>(x);
    } else {
        doLoop<false>(x);
    }

И убедитесь, что компилятор вставляет его.

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

4 голосов
/ 02 апреля 2010

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

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

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

4 голосов
/ 02 апреля 2010

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

1 голос
/ 02 апреля 2010

Вот фактический ответ на актуальный вопрос!

volatile int y = 0;

int Test() 
{
    int x = 2; 
    for (int i=0 ; i<10 ; i++) 
    { 
        x *= 2; 

        __asm {NOP}; // to skip it replace this 
        __asm {NOP}; // by JMP 2 (after the goto) 
        goto dont_do_it;
    keep_my_code:
        x *= 2; // Op to skip or not 
    dont_do_it: 
        x *= 2; 
    }
    if (y) goto keep_my_code;
    return x; 
} 
0 голосов
/ 02 апреля 2010

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

0 голосов
/ 02 апреля 2010

Это может дать понимание:

# Прагма оптимизировать для Visual Studio .

Тем не менее, для этой конкретной проблемы я бы вручную кодировал в ASM, используя вывод asm VS в качестве контрольной точки.

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

0 голосов
/ 02 апреля 2010

Это х64? Вы можете использовать указатели на функции и условное перемещение, чтобы избежать предиктора ветвления. Загрузить адрес процедуры на основе пользовательских настроек; одна из процедур может быть пустышкой, которая ничего не делает. Вы должны быть в состоянии сделать это вообще без встроенного ASM.

...