| Россия |
Расширенная индексная нотация
Презентацию к лекции Вы можете скачать здесь.
Язык программирования должен предоставлять разработчику удобное средство отображения параллелизма данных в задаче на параллельную архитектуру. Языком, располагающим удобными и разнообразными средствами работы с массивами, является Fortran. В C/C++ нет удобных средств работы с массивами. C/C++ - доминирующий язык разработки приложений.
Массивы – основная структура данных в вычислительных приложениях.
Расширенная индексная нотация – главное отличие CilkTM от CilkTM Plus.
Определение:
{<имя массива или указатель на него>[<нижняя граница значений индекса>:<длина>[: <шаг изменения индекса>]]}
Символ ":" является указанием на множество элементов массива (секцию или сечение массива).
Символ ":", используемый без указания длины и шага, является указанием на множество всех элементов массива.
Синтаксис расширенной индексной нотации отличается от синтаксиса сечений в Fortran!
Использование расширенной индексной нотации является сигналом компилятору выполнить векторизацию кода. Компилятор векторизует код с расширенной векторной нотацией, отображая его на целевую архитектуру.
Примеры:
A[:] // Все элементы вектора A
B[3:5] // Элементы с 3 по 5 массива B
C[:][7] // Столбец 7 матрицы C
D[0:3:2] // Элементы 0,2 и 4 массива D
E[0:5][0:4] // 20 элементов с E[0][0] по E[5][4]
Большинство "стандартных" арифметических и логических операций C/C++ могут применяться к секциям массивов:
+, -, *, /, %, <,==,!=,>,|,&,^,&&,||,!,-(unary), +(unary),++,--, +=, -=, *=, /=, *(p)
Операторы применяются ко всем элементам секции массива:
a[:] * b[:] // поэлементное умножение
a[3:2][3:2] + b[5:2][5:2] // сложение матриц 2x2
Операции могут выполняться с разными элементами параллельно.
Секции, используемые в качестве операндов, должны быть конформными (иметь одинаковые ранг и экстент):
a[0:4][1:2] + b[1:2] // так не должно быть!
Скалярный операнд автоматически расширяется до секции необходимой формы:
a[:][:] + b[0][1] // сложение b[0][1] со всеми элементами матрицы a
Оператор присваивания выполняется параллельно для всех элементов секции:
a[0:n] = b[0:n] + 1;
Ранги правой и левой частей должны совпадать. Допустимо использование скалярных величин:
a[:] = c; // c заполняет массив a
e[:] = b[:][:]; // ошибка!
Допустимо пересечение правой и левой частей в операторе присваивания (в этом случае используются временные массивы):
a[1:s] = a[0:s] + 1; // используется старое значение a[1:s-1]
Поэлементные векторные операции
Пример:
a[:]+b[:]
Пример. Сложение двух массивов
#include <iostream>
int main() {
double a[4] = {1.,2.,3.,4.};
double b[4] = {5.,7.,11.,13.};
double c[4] = {0.,0.,0.,0.};
std::cout << "Вывод a:\n" << a[:] << " ";
std::cout << std::endl << std::endl;
std::cout << "Вывод b:\n" << b[:] << " ";
std::cout << std::endl << std::endl;
std::cout << "Вывод c:\n" << c[:] << " ";
std::cout << std::endl << std::endl;
c[:] = a[:] + b[:];
std::cout << "c = a + b:\n" << c[:] << " ";
std::cout << std::endl << std::endl;
}
#include <iostream>
int main() {
bool x[4] = {0, 0, 1, 1};
bool y[4] = {0, 1, 1, 0};
double a[4] = {1.,2.,3.,4.};
double b[4] = {5.,7.,11.,13.};
double c[4] = {0.,0.,0.,0.};
std::cout << "Вывод a:\n" << a[:] << " ";
std::cout << std::endl << std::endl;
std::cout << "Вывод b:\n" << b[:] << " ";
std::cout << std::endl << std::endl;
std::cout << "Вывод до c:\n" << c[:] << " ";
std::cout << std::endl << std::endl;
c[:] = x[:] && y[:] ? a[:] : b[:];
std::cout << " Вывод после c:\n" << c[:] << " ";
std::cout << std::endl << std::endl;
}
Пример. Реализация со встроенными функциями
#include <pmmintrin.h>
void foo(float* dest, short* src, long len, float a) {
__m128 xmmMul = _mm_set1_ps(a);
for(long i = 0; i < len; i+=8) {
__m128i xmmSrc1i = _mm_loadl_epi64((__m128i*) &src[i]);
__m128i xmmSrc2i = _mm_loadl_epi64((__m128i*) &src[i+4]);
xmmSrc1i = _mm_cvtepi16_epi32(xmmSrc1i);
xmmSrc2i = _mm_cvtepi16_epi32(xmmSrc2i);
__m128 xmmSrc1f = _mm_cvtepi32_ps(xmmSrc1i);
__m128 xmmSrc2f = _mm_cvtepi32_ps(xmmSrc2i);
xmmSrc1f = _mm_mul_ps(xmmSrc1f, xmmMul);
xmmSrc2f = _mm_mul_ps(xmmSrc2f, xmmMul);
_mm_store_ps(&dest[i], xmmSrc1f);
_mm_store_ps(&dest[i+4], xmmSrc2f);
}
}
Сравнение машинных кодов для обеих реализаций
Интринсики (встроенные функции)
movq (%rsi,%rax,2), %xmm1 #9.18
movq 8(%rsi,%rax,2), %xmm2 #10.18
pmovsxwd %xmm1, %xmm1 #9.18
pmovsxwd %xmm2, %xmm2 #10.18
cvtdq2ps %xmm1, %xmm3 #12.25
cvtdq2ps %xmm2, %xmm4 #13.25
mulps %xmm0, %xmm3 #15.18
mulps %xmm0, %xmm4 #16.18
movaps %xmm3, (%rdi,%rax,4) #18.21
movaps %xmm4, 16(%rdi,%rax,4) , #19.21
addq $8, %rax #5.34
cmpq %rdx, %rax #5.24
jl ..B1.3 # Prob 82% #5.24
Индексная нотация
movq (%rsi,%rcx,2), %xmm2 #2.45
pmovsxwd %xmm2, %xmm2 #2.45
cvtdq2ps %xmm2, %xmm3 #2.45
mulps %xmm1, %xmm3 #2.58
movaps %xmm3, (%rdi,%rcx,4) #2.15
movq 8(%rsi,%rcx,2), %xmm4 #2.45
pmovsxwd %xmm4, %xmm4 #2.45
cvtdq2ps %xmm4, %xmm5 #2.45
mulps %xmm1, %xmm5 #2.58
movaps %xmm5, 16(%rdi,%rcx,4) , #2.15
addq $8, %rcx #2.15
cmpq %r8, %rcx #2.15
jb ..B1.15 # Prob 44% #2.15
