Vladikavkaz, Vladikavkaz, Russian Federation
Vladikavkaz, Vladikavkaz, Russian Federation
This article is a continuation of one work to determine the optimal strategy of macro-replacement in the optimization program codes of C++. Macro-replacement for routines is to deploy the function code in all the places her call. The article describes the results of experiments to determine the factors affecting the speed of subroutines. Initially, it was believed that function call times are affected by the time the function stack is allocated and the time to transfer control to the function. In connection with the results of the experiment, it was established that the time for transfer of control is insignificant compared to the time of allocation of memory. The time that the stack memory of a function allocated depends on the amount of code and the number of stack objects used by the function. To optimize the speed of the program, you can use two variants of macro-replacement. The first is the use of a special C++ language modifier (in-line/__forceinline), and the second is a direct substitution of the code in the place of the call. There is almost no difference in the effectiveness of these methods, except that the programmer has to control the errors in the code substitution strategy. Therefore, the method of using a special modifier was chosen for experiments. Was selected modifier __forceinline, because in a typical inline, the compiler may ignore the programmer recommendations and not done macro-replacement, if the optimization algorithms of the compiler don't need it. And __forceinline is guaranteed to apply macro-replacement. In connection with the results obtained, it can be concluded that macro-replacement significantly increases the speed of the program but can lead to an increase in the size of the executable file.
program code, optimization, model, quality, output
Введение
Целью проведенного исследования является: определение факторов, влияющих на скорость вызова функции в языке С++.
Значение прироста в производительности в результате макрозамен включает два слагаемых: 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. Tomaev M.KH., Aslanov G.A., Vanyushenkova N.V. (2017). Use of optimization models of extreme programming in software design. IT-Technologies: theory and practice, Seminar materials. [in Russian language]
2. Sokolova E.A. (2008). Determination of the parameters of the speed of the compression algorithm for static images. International scientific conference young scientists, students and post-graduate students «Perspective-2008»: Cal. mater. Nalchik: Cab-Balk. University, pp.143-147. [in Russian language]
3. Sokolova E.A. (2007) Mathematical model of compression of static images with variable fragments taking into account errors. Dep. in VINITI No. 748-B2007, the index No. 9. [in Russian language]
4. Sokolova E.A. (2008). To the problem of increasing the efficiency of image compression. Information Technology Security, Ministry of Education and Science of the Russian Federation, MEPhI, VNIIPTI, (2), pp. 57-60. [in Russian language]
5. Sokolova E.A. (2017). Development of a method for storing pixels in an array with differentiation into color components. International Scientific and Research Journal, 61(7). [in Russian language]
6. Sokolova E.A. (2017). The method of compression of digital three-dimensional images using the analysis of the table of texture coordinates. International Scientific and Research Journal, 61(7). [in Russian language]