MOVAPS + MOVNTPS, или выигрываем очередные микросекунды

Изменено: . Разместил: FadeDemon | Меню
Количество просмотров1`751 / FD872-6-0

Не думал что буду писать об этом, но раз уж начал…


Однажды, делая, как положено, лабораторную работу на Си по операциям с матрицами, на меня нашло непонятное желание сотворить чтонибуть этакое. В задании требовалось всего-то переставить местами две строки матрицы, я уже было хотел добросовестно написать цикл для свопа переменных, но спустя секунду, передумал. Зачем, скажите, зачем переставлять каждую ячейку, если их массив всеравно распологается одним блоком в памяти (матрица была представлена двумерным массивом)? Веть нельзя не согласиться, что проще переставить всю строку целиком, тоесть свопнуть непосредственно блоки памяти, примерно по тому же самому алгоритму, что и отдельные переменные. А как это сделать быстрее всего?…


Здесь и пришло на ум расширение SSE, и в частности, инструкция для пересылки данных из 128-битного xmm регистра в память, по выровненному адресу, минуя кэш, имеющая название movntps. На сколько мне помниться, алгоритмы с этой инструкцией чуть-чуть выигрывали в производительности у rep movsd на больших обьемах данных, что в общем, не должно казаться странным. Конечно, понятно что рассуждать о таких вещах переставляя строчки в матрице – не самое актуальное занятие, но… Как известно, совершенству нет предела. И в итоге получасового размышления и беглого чтения мануалов, родилось вот это:


Код: C++
void memcpy_sse(void *src, void *dst, long size){;
    //как выяснилось, тут используется AT&T синтаксис
    //но он успешно заменился на более известный Intelовский с помощью -masm=intel

    static void *s __asm("s");
    static void *d __asm("d");
    static long c __asm("c");
    
    //иначе из-за static получалось такое, что лучше бы оно и не получалось...
    s = src;
    d = dst;
    c = size;
    
    //алгоритм в целом тривиален, но не совсем
    __asm("push esi");
    __asm("push edi");
    
    //источник и приемник, по традиции это регистры esi и edi
    __asm("mov esi, s");
    __asm("mov edi, d");
    
    //количество - ECX
    __asm("mov ecx, c");
    
    //использование SSE традиции нарушает
    __asm("mov edx, ecx");  //запоминаем количество байтов
    __asm("sar ecx, 4");    //делим ecx на 16
    
    //если в ecx ноль, то нам чуть дальше
    __asm("test ecx, ecx");
    __asm("jz less16");
    
    //дальше идет собственно цикл копирования через movntps
    //вообще можно и rep movntps забабахать, но из документации 
    //следует, что эта возможность у процессоров какбы недокументирована...
    __asm("ssemov:"); //меточка
    
    //пересылка 16 байтов за 1 присест
    __asm("movaps xmm0, [esi]");    //берем из памяти
    __asm("movntps [edi], xmm0");   //кладем обратно в память
    
    //увеличиваем esi и edi
    __asm("add esi, 16");
    __asm("add edi, 16");
    
    //вернуться к меточке, если ecx больше нуля
    __asm("loop ssemov");
    
    
    __asm("less16:");
    //на этом месте, у нас остаются недокопированные байты, которых всегда меньше 16
    //их мы докопировываем при помосчи более известных нам rep movsd и rep movsb
    
    //восстанавливаем ecx
    __asm("mov ecx, edx");
    //оставляем последнюю тетраду, т.е то самое число
    __asm("and ecx, 0xf"); 
    //и делим его на 4! (чуть не забыл про это)
    __asm("sar ecx, 2"); 
    
    //esi и edi у нас уже содержат нужные адреса..
    __asm("rep movsd");
    
    //еще раз, только со значениями менее 4х байт
    __asm("mov ecx, edx");
    __asm("and ecx, 3");
    
    __asm("rep movsb");
    
    //ВСЕ!))
    
    //регистры эти важные, и поэтому мы их восстанавливаем
    __asm("pop edi");
    __asm("pop esi");
}

Думаю что коментарии в коде если и не полностью, то все-таки прояснят происходящее. Коротко только надо отметить, что с помощью SSE комманд, можно скопировать не менее 16 байт, и поэтому если размер блока не кратен этой величине, остальные байты нужно добивать с помощью привычных rep movsd/movsb. Ну, в общем, как в обычном алгоритме копирования памяти, ничего нового тут не изобретено…


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


За производительность этого произведения ручаться не могу, ибо не делал бенчмарков, некогда. Вообще один из первых моих перлов на языке Си. Надо сказать еще, что для того чтобы использовать это в перестановках строк матриц, пришлось ячейки матриц ровнять по 16, я думаю нетрудно дргадаться по какой причине :) Впрочем, если это алгоритм и кому-то будет полезен, то точно не для операций с матрицами. Надеюсь, творческий порыв прошел не зря. Всем читателям спасибо за внимание…

Ключевые слова Программизм, SSE, movntps
QR-код
Интересное
Модулируем логические уровни прямоугольником Поразмыслив, задачу нашу, в некотором смысле, можно назвать модуляцией: мы должны постоянный сигнал промодулировать некоторой несущей частотой, и уже полученный результат скормить схеме контроллера.

Читать »»
Случайные фото