Лекция 2:
Оптимизация ветвлений и циклов
(Branch prediction and loop optimization)
Курносов Михаил Георгиевич
к.т.н. доцент Кафедры вычислительных систем
Сибирский государственный университет
телекоммуникаций и информатики
http://www.mkurnosov.net
Вычислительный конвейер (Pipeline)
22
.text
movl %ebx, %eax
cmpl $0x10, %eax
jne not_equal
mov %eax, %ecx
jmp end
not_equal:
mov $-0x1, %ecx
end:
Branch Prediction Unit (BPU)
Step IF ID EX ST
1 movl
2 cmpl movl
3 jne cmpl movl
4 ??? jne cmpl movl
5
6
7
Модуль предсказания переходов
33
� Модуль предсказания условных переходов
(Branch Prediction Unit, BPU) – компонент процессора,
имеющего конвейерную архитектуру, определяющий
направление ветвлений (будет ли выполнен условный
переход) в исполняемой программе.
� Вероятность предсказания переходов в современных
процессорах превышает 0.9.
Branch Prediction Unit (BPU)
44
� Статическое предсказание (Static prediction)
Фиксированное правило работы предсказателя –
условный переход либо выполняются всегда, либо не
выполняются никогда.
� Ранние SPARC, MIPS: условные переходы никогда не
выполняются;
� Современные CPU: обратный переход (переход на
более младшие адреса), является циклом и выполняется,
а любой прямой переход (на более старшие адреса),
не выполняется
Статическое предсказание переходов
55
Intel 64 and IA-32 Architectures Optimization
Reference Manual
� Процессоры Pentium 4, Pentium M, Intel Core Solo и Intel
Core Duo имеют схожие алгоритмы статического
предсказания переходов:
o безусловные переходы выполняются
o косвенные переходы (indirect jump, jmp *%eax)
не выполняется
o условные переходы предсказываются динамическим
алгоритмом (даже при первом выполнении)
Динамическое предсказание переходов
66
� Динамическое предсказание (Dynamic prediction) –
такие методы осуществляют накопление и анализ истории
ветвлений.
� Информация о предыдущих ветвлениях хранится
в буфере предсказания переходов (Branch Target Buffer)
0xFF01 l1: movl %ebx, %eax
0xFF02 cmpl $0x10, %eax
0xFF03 jne l2
0xFF04 mov %eax, %ecx
0xFF05 jmp l3
0xFF06 l2: mov $-0x1, %ecx
0xFF07 l3:
0xFF08 cmpl $0xFF, %ebx
0xFF09 jne l1
Addr
(low bits)
History Target
0xFF03 1 0xFF06
0xFF09 0
1-bit dynamic predictioner
77
Record Branch history Target
0 1 0xAF06
1 0 0x1134
2 1 0x01FC
3 0 0xFF06
...
2k – 1 1 0xBEAF
Адрес
инструкции
перехода
0011
63 0k бит
Branch
predictor
Taken
1-bit dynamic predictioner
88
Not
takenTaken
1 0
Not taken
Taken
Not taken
Taken
1-bit dynamic predictioner
99
Not
takenTaken
1 0
Not taken
Taken
Not taken
for (i = 0; i < 9; i++)
{
/* code */
}
movl $0, %ecx
jmp .L2
.L1:
/* code */
addl $1, %ecx
.L2: cmpl $8, %ecx
jle .L1
Taken
1-bit dynamic predictioner
1010
Not
takenTaken
1 0
Not taken
Taken
Not taken
i = 0, predicted 0, MISPREDICTION (state -> 1)
i = 1, predicted 1, OK, (state -> 1)
i = 2, predicted 1, OK, (state -> 1)
i = 3, predicted 1, OK, (state -> 1)
...
i = 9, predicted 1, MISPREDICTION (state -> 0)
80% ветвлений
предсказано
корректно
Taken
1-bit dynamic predictioner
1111
Not
takenTaken
1 0
Not taken
Taken
Not taken
for (i = 0; i < 9; i++) {
if ((i & 1) == 0)
/* Code 1 */
else
/* Code 2 */
}
Strongly
not taken
Saturating counter (2-bit, Bimodal predictor)
1212
Weakly
not takenWeakly
taken
Strongly
taken
Not taken
00 01 10 11State:
Taken Taken TakenTaken
Not taken Not taken Not taken
� При выполнении перехода состояние увеличивается на 1
� При невыполнении перехода состояние уменьшается на 1
� Прогноз: если состояние 00 или 01, то переход не состоится,
иначе состоится
� Использовался в Intel Pentium (не MMX версии)
Two-level adaptive predictor
1313
Branch prediction in Intel Core2
1414
� Для предсказания условных переходов используется
гибридный предсказатель (hybrid predictor) включающий
двухуровневый предсказатель (8 битный глобальный
буфер) и счетчик циклов (loop counter)
� Misprediction penalty: 15 clock cycles
Ветвления в программах
1515
if (x == 0)
else if (x == 1)
else
switch (x) {
case 0:
break;
case 1:
break;
default:
}
for (i = 0; i < 10; i++) {
}
while (data > 0) {
data--;
}
do {
data--;
} while (data > 0);
11 переходов
(условие вне цикла
и 10 его итераций)
20 переходов
(условие цикла и
ветвление в его теле)
/* Исходный фрагмент */
for (i = 0; i < 10; i++) {
if (value > 10)
data++;
else
data--;
}
/* Модифицированная версия */
if (value > 10) {
for (i = 0; i < 10; i++)
data++;
} else {
for (i = 0; i < 10; i++)
data--;
}
Типовые ошибки
1616
Типовые ошибки
1717
void MyFunc(int size, int blend, float *src,
float *dest, float *src_1, ...)
{
int j;
for (j = 0; j < size; j++) {
if (blend == 255)
dest[j] = src_1[j];
else if ( blend == 0 )
dest[j] = src_2[j];
else
dest[j] = (src_1[j] * blend + src_2[j]
* (255 - blend)) / 256;
}
}
Типовые ошибки
1818
void MyFunc (int size, int blend, float *src,
float *dest, float *src_1, ... )
{
int j;
if (blend == 255) /* Инвариантное ветвление */
for (j = 0; j < size; j++)
dest[j] = src_1[j];
else if (blend == 0)
for (j = 0; j < size; j++)
dest[j]= src_2[j];
else
for (j = 0; j < size; j++)
dest[j] = (src_1[j] * blend + src_2[j]
* (255-blend)) / 256;
}
Типовые ошибки
1919
void fun()
{
if (t1 == 0 && t2 == 0 && t3 == 0) {
/* Code 1 */
} else {
/* Code 2 */
}
}
cmpl $0, -4(%rbp)
jne .L2
cmpl $0, -8(%rbp)
jne .L2
cmpl $0, -12(%rbp)
jne .L2
/* Code 1 */
jmp .L3
.L2:
/* Code 2 */
.L3:
3 ветвления
Типовые ошибки
2020
void fun()
{
if ((t1 | t2 | t3) == 0) {
/* Code 1 */
} else {
/* Code 2 */
}
}
movl -8(%rbp), %eax
movl -4(%rbp), %edx
orl %edx, %eax
orl -12(%rbp), %eax
testl %eax, %eax
jne .L2
movl $133, -16(%rbp)
jmp .L3
.L2:
movl $333, -16(%rbp)
.L3:
1 ветвление
GCC branch annotation
2121
#define likely(x) __builtin_expect (!!(x), 1)
#define unlikely(x) __builtin_expect (!!(x), 0)
const char *home;
home = getenv("HOME");
if (likely(home))
printf("Your home is %s\n", home);
else
fprintf (stderr, "HOME not set!\n");
Расщепление циклов (Loop splitting)
2222
void fun()
{
for (i = 0; i < n; i++) {
v1[i] = 0;
v2[i] = 0;
/* ... */
vN[i] = 0;
}
}
Расщепление циклов (Loop splitting)
2323
void fun()
{
for (i = 0; i < n; i++) {
v1[i] = 0;
v2[i] = 0;
}
for (i = 0; i < n; i++) {
v3[i] = 0;
v4[i] = 0;
}
/* ... */
}
Слиянии циклов (Loop fusion)
2424
void fun()
{
for (i = 0; i < n; i++) {
v1[i] = 0;
}
for (i = 0; i < n; i++) {
v2[i] = 0;
}
}
Слиянии циклов (Loop fusion)
2525
void fun()
{
for (i = 0; i < n; i++) {
v1[i] = 0;
v2[i] = 0;
}
}
Разворачивание циклов (Loop unrolling)
2626
enum { n = 40 * 1024 * 1024 };
int main()
{
int *p;
int i, sum;
p = (int *)malloc(sizeof(*p) * n);
for (i = 0; i < n; i++)
p[i] = rand();
for (sum = 0, i = 0; i < n; i++) {
sum += p[i];
}
return 0;
}
Зависимость по данным
между инструкциями
enum { n = 40 * 1024 * 1024 };
int main()
{
int *p;
int i, sum;
p = (int *)malloc(sizeof(*p) * n);
for (i = 0; i < n; i++)
p[i] = rand();
for (sum = 0, i = 0; i < n; i += 4) {
sum += p[i];
sum += p[i + 1];
sum += p[i + 2];
sum += p[i + 3];
}
return 0;
}
Разворачивание циклов (Loop unrolling)
2727
Разворачивание циклов (Loop unrolling)
2828
enum { n = 40 * 1024 * 1024 };
int main()
{
int *p;
int i, sum, t1, t2, t3, t4;
/* Initialization code ... */
t1 = t2 = t3 = t4 = 0;
for (sum = 0, i = 0; i < n; i += 4) {
t1 += p[i];
t2 += p[i + 1];
t3 += p[i + 2];
t4 += p[i + 3];
}
sum = t1 + t2 + t3 + t4;
return 0;
}
Разворачивание циклов (Loop unrolling)
2929
void fun()
{
for (i = 0; i < 1024; i++) {
if (i & 0x01)
fun_a(i);
else
fun_b(i);
}
}
Разворачивание циклов (Loop unrolling)
3030
void fun()
{
for (i = 0; i < 1024; i += 2) {
fun_a(i);
fun_b(i + 1);
}
}
Перестановка циклов
3131
void fun()
{
for (i = 0; i < 4; i++) {
a[i] = 0;
for (j = 0; j < 4; j++) {
a[i] += b[j][i];
}
}
}
Перестановка циклов: расщепление
3232
void fun()
{
for (i = 0; i < 4; i++)
a[i] = 0;
for (i = 0; i < 4; i++) {
for (j = 0; j < 4; j++) {
a[i] += b[j][i];
}
}
}
Перестановка циклов: перестановка
3333
void fun()
{
for (i = 0; i < 4; i++)
a[i] = 0;
for (j = 0; j < 4; j++) {
for (i = 0; i < 4; i++) {
a[i] += b[j][i];
}
}
}
Hardware Performance Counters
3434
� PAPI
� $ perf list
cpu-cycles OR cycles [Hardware event]
instructions [Hardware event]
cache-references [Hardware event]
cache-misses [Hardware event]
branch-instructions OR branches [Hardware event]
branch-misses [Hardware event]
bus-cycles [Hardware event]
ref-cycles [Hardware event]
Profilers
3535
Sampling {Static, Binary}
Instrumentation
� Perf, Valgrind, Intel Vtune, OProfile
$ perf stat –e branch-misses ./loopunrolling
Performance counter stats for './loopunrolling':
709.737801 task-clock # 0.995 CPUs utilized
81 context-switches # 0.114 K/sec
5 CPU-migrations # 0.007 K/sec
2,379 page-faults # 0.003 M/sec
2,106,950,459 cycles # 2.969 GHz
398,589,229 branches # 561.601 M/sec
1,280,498 branch-misses # 0.32% of all