Аннотация и ключевые слова
Аннотация (русский):
Данная статья является продолжением одной работы по определению оптимальной стратегии макрозамен при оптимизации программных кодов С++. Макрозамены для подпрограмм – это разворачивание кода функции во всех местах ее вызова. В статье описываются результаты экспериментов по определению факторов влияющих на скорость вызова подпрограмм. Изначально считалось, что на время вызова функций влияют время выделения стека функции и время на передачу управления подпрограмме. В связи с результатами эксперимента было установлено, что время на передачу управления является незначительным по сравнению с временем выделения стековой памяти. Время выделения стековой памяти подпрограммы напрямую зависит от объема кода и количества стековых объектов, используемы в данной подпрограмме. Для оптимизации скорости программы можно использовать 2 варианта макрозамен. Первый – использование специального модификатора языка С++ (inline/__forceinline), а второй – прямая подстановка кода в место вызова. В отношении эффективности между этими методами практически нет никакой разницы, за исключением того, что в стратегии подстановки кода программист сам должен контролировать возникающие ошибки. Поэтому для экспериментов был выбран метод использование специального модификатора. Был выбран модификатор __forceinline, так как при обычном inline компилятор может игнорировать рекомендации программиста и не выполнять макрозамену, если по алгоритмам оптимизации самого компилятора она не нужна. А __forceinline гарантированно выполнит макрозамену. В связи с полученными результатами можно сделать вывод, что макрозамены значительно увеличивают скорость работы программы, но могут приводить к увеличению размера исполняемого файла.

Ключевые слова:
программный код, оптимизация, модель, качество, производительность
Текст
Текст произведения (PDF): Читать Скачать

Введение
Целью проведенного исследования является: определение факторов, влияющих на скорость вызова функции в языке С++.
Значение прироста в производительности в результате макрозамен включает два слагаемых: 1) время на передачу управления в функцию и возврат из неё; 2) время на выделение стековой памяти функции, необходимой для размещения всех локальных переменных и массивов, - и может быть представлено в виде выражения (1):

                      (1)

     – время на передачу управления в функцию и возврат из нее без учета времени, необходимого на выделение локального стека функции. Эта величина является постоянной и соответствует времени выполнения машинной инструкции CALL(передающей управление процедуре на языке Ассемблера).
  – время, затрачиваемой на выделение локальной (стековой) памяти, предназначенной для размещения переменных, объявленных внутри блока функции. Это время зависит от суммарного размера локальных данных, ниже данное утверждение будет подтверждено экспериментально.
Чтобы оценить порядок величин  ,   и оценить их значимость в выражении (1) были проведены ряд экспериментов. Экспериментальные замеры проводились с помощью тестового кода, написанного на языке C++ в среде Visual Studio. Для получения максимально объективных оценок в настройках проекта параметру «Optimization» было присвоено значение «Disabled (/Od)», что предотвращает влияние встроенных в компилятор Microsoft методов оптимизации. Кроме того, параметр «Inline function expansion» был установлен в «Only __inline (/Ob1)» - данный флаг запрещает компилятору игнорировать инструкцию inline при построении исполняемого кода (рис.1). Замеры проводились в режиме «Release» для исключения диагностического кода.

 
Рис. 1. Настройки тестового проекта

1. Процесс тестирования
Для оценки величины времени вызова и возврата из функции    был проведен тест, в котором замерялось время выполнения кода, выполняющего в цикле функцию с пустым телом и без параметров (чтобы исключить влияние операторов выделения стека). Количество итераций цикла последовательно изменялось от 1000000 до 20000000 с шагом 1000000. Исходный код теста имеет вид:

    CString cstr="";
    for (int N=1000000; N<=20000000; N+=1000000){
        DWORD dwStart = GetTickCount();
        for (int i=0; i<N; i++){
            f();
        }
        DWORD dwTime = GetTickCount() - dwStart;
        CString cs; cs.Format("%u\n",dwTime);
        cstr += cs;
    }
    AfxMessageBox(cstr); 

Были проведены два замера: в первом функция была объявлена обычным образом:
void f()
{

}

Во втором случае с использованием инструкции _forceinline:

__forceinline void f()
{

}
Результаты замеров показали, что отличия во времени работы лежали в пределах статистической погрешности. Максимальное время выполнения (для 20000000 итераций) составило около 47 микросекунд.
Так как существовала возможность того, что компилятор игнорирует флаг запрета встроенной оптимизации и все-таки исключает функции с пустым телом из объектного кода, то были проведены 2 дополнительных теста: в первом вместо функции f() в цикле вызывалось выражение log(10.5):

CString cstr="";
    for (int N=1000000; N<=20000000; N+=1000000){
        DWORD dwStart = GetTickCount();
        for (int i=0; i<N; i++){
            log(10.5);
        }
        DWORD dwTime = GetTickCount() - dwStart;
        CString cs; cs.Format("%u\n",dwTime);
        cstr += cs;
    }
    AfxMessageBox(cstr); 

А во втором тесте выражение log(10.5); было помещено в тело функции f():

void f()
{
    log(10.5);
}

Сравнение результатов этих двух тестов также показало очень незначительные расхождение. Это говорит о том, что затраты на вызов функции и возврат из неё незначительны. Выражение (1) примет следующий вид:
                     (2)

Для оценки зависимости времени выделения стековой памяти от размера локальных данных был проведен следующий эксперимент: число итераций было зафиксировано значением 20000000, Далее были проведены 10 замеров для каждого из которых менялся размер массива «y», объявленного в теле функции f()  (Листинг 1) от 100000 до 1000000 байт включительно:

void f()
{
    char y[1000000];
    y[0] = 'a';
}
2. Результаты экспериментов
Аналогичный эксперимент был проведен для варианта функции f() с использованием модификатора __forceinline. Результаты обоих экспериментов приведены в таблице 1.
Полученные результаты говорят о высокой эффективности использования модификатора __forceinline: очевидно, что объявление данных компилятор поместил перед циклом. На графике, изображенном на рисунке 2 хорошо виден линейный характер зависимости времени выделения локального стека функции от его размера: поверх кривой экспериментальных данных, взятых из первой и второй колонок таблицы 1, проведена прямая (пунктиром), коэффициенты, которой получены аппроксимацией данных методом МНК.
Таблица 1 - Результаты замеров времени выделения локальной памяти

Размер данных    время работы обычной функции    время работы __forceinline функции
100000    579    47
200000    1312    47
300000    2187    47
400000    3468    47
500000    4343    47
600000    5187    47
700000    6079    47
800000    6922    47
900000    7781    47
1000000    8641    47

 
Рис. 2. Кривая экспериментальных данных и прямая, описывающая линейную зависимость времени выделения стековой памяти от её размера.

На графике, изображенном на рисунке 2 ось абсцисс, соответствует размеру стека (в байтах), а ось ординат – времени выделения стека (в миллисекундах).
Проведенные эксперименты показали, что в выражение (1) можно упростить, убрав из него представив выигрыш от макрозамены  , ввиду его фактической несущественности. Кроме того, подтвержденный экспериментально линейный характер зависимости времени выделения стека от его размера позволяет с высокой степенью точности использовать для прогнозирования времени выделения стека коэффициенты пропорциональности либо более наглядный показатель скорости выделения стека, с учетом этого выражение (1) можно преобразовать в следующее (2):
                         (2)
  – суммарный размер локальных переменных (стека функции);
     – скорость выделения стека.

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

Список литературы

1. Томаев М.Х., Асланов Г.А., Ванюшенкова Н.В. Использование оптимизационных моделей экстремального программирования в проектировании ПО // IT-Технологии: теория и практика, Материалы семинара, 2017.

2. Соколова Е.А., Определение параметров быстродействия алгоритма компрессии статичных изображений // Междунар. науч. конф. молодых ученых, студентов и аспирантов «Перспектива-2008»: сб. матер. Нальчик: Каб.-Балк. ун-т, 2008. С.143-147.

3. Соколова Е.А. Математическая модель компрессии статичных изображений вариабельными фрагментами с учетом погрешностей // деп. в ВИНИТИ 19.07.07. № 748-В2007, указатель № 9, 12 с.

4. Соколова Е.А. К проблеме повышения эффективности компрессии изображений // Безопасность информационных технологий, Министерство образования и науки РФ, МИФИ, ВНИИПТИ. 2008. № 2. С.57-60.

5. Соколова Е.А. Разработка метода сохранения пикселей в массив с дифференциацией на цветовые компоненты // Международный научно-исследовательский журнал. 2017. № 7 (61). С. 7.

6. Соколова Е.А. Метод компрессии цифровых трехмерных изображений с помощью анализа таблицы текстурных координат // Международный научно-исследовательский журнал. 2017. № 7 (61). С. 9.

Войти или Создать
* Забыли пароль?