воскресенье, 27 июля 2008 г.

Про движок блога

Хороший движок, но написание прошлого сообщения меня утомило.

Во-первых, размер поле ввода маленькое - расчитано на короткие сообщение, а не на развернутый рассказ. Во-вторых, чтобы вставить форматированный код (что C++, что ASM) пришлось помучаться. И, в-третьих, весьма актуально была бы возможность делать "spoiler" как в MSDN, чтобы скрывать/раскрывать код (свой вариант я прикрутил, но чего-же теперь каждый кому понадобится такая функциональность должен делать ее сам?)

Если писать программу для работы с блогом - было бы не плохо реализовать эти опции, правда, чтобы их поддержать надо еще шаблон подправить.

Про MMX, SSE и оптимизацию

Вводная


Продолжаю делать H264 декодер. Надо соответственно сделать обратное преобразование блока 8х8 коэффициентов в 8х8 кусок плоскости (можно назвать это IDCT, но это будет не совсем правда, потому что там от косинусов осталось мало, а получилось что-то вроде Адамара, но не в этом суть). В спецификации H264 вся эта хрень осуществляется за 6 шагов (ну или за 7, как считать, может даже и за 8, для нашего текущего разговора это опять же не важно). Решил, что это хороший повод освежить свои навыки работы с ММХ и SSE.

Я уже как-то пытался попользовать эти мега расширения, но получилось достаточно смешно.

Дома у меня тогда стояла MSVS 2003 Standard. а Standard он очень хороший, только вот опции оптимизации компилятора в свойствах проекта он включить не дает (ну или я как обычно где-то, чего-то не нашел). Но я чего-то про это не особо задумывался. Соответственно, набросал кусок кода на С++, потом сделал код с той же функциональностью под SSE, сравнил скорость, и возрадовался. Разница была где-то раз в десять (естественно в пользу SSE). Правда радовался я до момента пока не скомпилил тот же тест на работе, где стояла MSVS 2003 Prof. Оказалось, что компилятор оптимизирует так же хорошо, как и я. Только у меня процесс оптимизации занимает два часа, а у него пару секунд. Но там задачка изначально, плохо ложилась на SSE, так что эксперимент был не слишком чистым.

Возвращаясь к H264. Здесь все очень красиво, преобразования с матрицей 8x8 (в матрице элементы типа short) плюс первые три шага используют только сложение вычитание строк этих матриц, как векторов. В общем, если не распробовать эти MMX с SSE здесь, то уж не понятно, где их и пробовать.

Формулировка задачи


Разбираться со всеми шагами сразу есть смысл только, если пишешь декодер, а для поиграться с MMX и SSE достаточно разобрать один шаг.

Итак, на входе матрица
short arBufferSrc[8][8];

Мы эту матрицу преобразуем, результат помещаем в новую память (можно собственно и на месте крутить, но оно только усложняет код, а мне этого на данном этапе не хотелось бы). На С++ это выглядит так:
показать код
short arBufferSrc[64];
short arBufferCPPDst[64];
short *pBufferSrc = arBufferSrc;
short *pBufferCPPDst = arBufferCPPDst; memset(pBufferCPPDst, 0, 64);
for (int i=0;i<8;i++)
{
pBufferCPPDst[0 * 8 + i] = pBufferSrc[0 * 8 + i] + pBufferSrc[4 * 8 + i];
pBufferCPPDst[1 * 8 + i] = -pBufferSrc[3 * 8 + i] + pBufferSrc[5 * 8 + i]
-pBufferSrc[7 * 8 + i] - (pBufferSrc[7 * 8 + i]>>1);
pBufferCPPDst[2 * 8 + i] = pBufferSrc[0 * 8 + i] - pBufferSrc[4 * 8 + i];
pBufferCPPDst[3 * 8 + i] = pBufferSrc[1 * 8 + i] + pBufferSrc[7 * 8 + i]
-pBufferSrc[3 * 8 + i] - (pBufferSrc[3 * 8 + i]>>1);
pBufferCPPDst[4 * 8 + i] = (pBufferSrc[2 * 8 + i]>>1) - pBufferSrc[6 * 8 + i];
pBufferCPPDst[5 * 8 + i] = -pBufferSrc[1 * 8 + i] + pBufferSrc[7 * 8 + i] +
pBufferSrc[5 * 8 + i] + (pBufferSrc[5 * 8 + i]>>1);
pBufferCPPDst[6 * 8 + i] = pBufferSrc[2 * 8 + i] + (pBufferSrc[6 * 8 + i]>>1);
pBufferCPPDst[7 * 8 + i] = pBufferSrc[3 * 8 + i] + pBufferSrc[5 * 8 + i] +
pBufferSrc[1 * 8 + i] + (pBufferSrc[1 * 8 + i]>>1);
}

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

Все работает, код нам потом пригодится проверять результаты работы MMX-шного и SSE-шного вариантов

Оптимизация с использованием MMX


Принцип оптимизации простой. Если в исходном коде мы работали с одним столбцом за раз, то, используя MMX регистры, мы можем обработать сразу 4 столбца. Выглядит это примерно так:
показать код
short arBufferSrc[64];
short arBufferMMXDst[64];

short *pBufferSrc = arBufferSrc;
short *pBufferMMXDst = arBufferMMXDst; memset(pBufferMMXDst, 0, 64);

__asm
{
mov eax, pBufferSrc
mov ecx, pBufferMMXDst
mov edx, 2
$loop:
movq mm0, qword ptr [eax]
movq mm1, qword ptr [eax + 4*16]
paddsw mm0, mm1
movq qword ptr [ecx], mm0

movq mm0, qword ptr [eax]
movq mm1, qword ptr [eax + 4*16]
psubsw mm0, mm1
movq qword ptr [ecx + 2*16], mm0

movq mm0, qword ptr [eax + 3*16]
movq mm1, qword ptr [eax + 5*16]
movq mm2, qword ptr [eax + 7*16]
psubsw mm1, mm0
psubsw mm1, mm2
psraw mm2, 1
psubsw mm1, mm2
movq qword ptr [ecx + 1*16], mm1

movq mm0, qword ptr [eax + 1*16]
movq mm1, qword ptr [eax + 3*16]
movq mm2, qword ptr [eax + 7*16]
paddsw mm0, mm2
psubsw mm0, mm1
psraw mm1, 1
psubsw mm0, mm1
movq qword ptr [ecx + 3*16], mm0

movq mm0, qword ptr [eax + 2*16]
movq mm1, qword ptr [eax + 6*16]
psraw mm0, 1
psubsw mm0, mm1
movq qword ptr [ecx + 4*16], mm0

movq mm0, qword ptr [eax + 1*16]
movq mm1, qword ptr [eax + 5*16]
movq mm2, qword ptr [eax + 7*16]
psubsw mm2, mm0
paddsw mm2, mm1
psraw mm1, 1
paddsw mm2, mm1
movq qword ptr [ecx + 5*16], mm2

movq mm0, qword ptr [eax + 2*16]
movq mm1, qword ptr [eax + 6*16]
psraw mm1, 1
paddsw mm0, mm1
movq qword ptr [ecx + 6*16], mm0

movq mm0, qword ptr [eax + 1*16]
movq mm1, qword ptr [eax + 3*16]
movq mm2, qword ptr [eax + 5*16]
paddsw mm1, mm2
paddsw mm1, mm0
psraw mm0, 1
paddsw mm1, mm0
movq qword ptr [ecx + 7*16], mm1

add eax, 8
add ecx, 8
dec edx
jne $loop
emms
}

Все чудесно, чтобы было еще чудеснее надо кое-чего подоптимизировать, но вначале промеряем время и порадуемся нашим способностям в использовании технологий десятилетний давности.

Замеры скорости


Понятно, что отмерить время выполнения одного преобразования не представляется возможным, по причине и его крайней малости, поэтому, мы будем выполнять наши куски по 1 000 000 (одному миллиону) раз. Так же, чтобы никто нам не помешал, выставим побольше приоритет текущему процессу (SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_TIME_CRITICAL);) и не забудем вернуть его в нормальный по окончанию работы.

А как Вы будете непосредственно время измерять - вариантов, масса сами придумаете.

А я пока расскажу про свои итоги. Вариант номер раз, C++ код - время выполнения миллиона преобразований составляет 111 мс. Вариант номер два, оптимизация под MMX - 24 мс. Я считаю, что это очень достойный результат оптимизации.

Чтобы улучшить этот и без того прекрасный результат надо немного подумать, и, во-первых, не загружать данные в регистры из памяти по два раза, а, во-вторых, развернуть цикл. Регистров для этого вполне хватает, я пробовал. Если все это проделать вместо 24 мс мы получим 20 мс и это хорошая плата, за легкое напряжение мозга.

Основное что нам надо запомнить на данном этапе, это то, что при использовании MMX скорость увеличилась в 4-5 раз. Перейдем теперь к SSE.

Оптимизация с использованием SSE


Принцип оптимизации тот же что и с MMX, только теперь мы будем обрабатывать все 8 столбцов за раз. В данном случае представленный код уже прооптимизирован (в меру моих скромных способностей), на предмет исключения повторной загрузки из памяти в регистры.
показать код
short arBufferSrc[64];
short arBufferSSEDst[64];

short *pBufferSrc = arBufferSrc;
short *pBufferSSEDst = arBufferMMXDst; memset(pBufferSSEDst, 0, 64);

__asm
{
mov eax, pBufferSrc
mov ecx, pBufferSSEDst

movdqa xmm0, [eax]
movdqa xmm2, xmm0
movdqa xmm1, [eax + 4*16]
paddsw xmm0, xmm1
movdqa [ecx], xmm0

psubsw xmm2, xmm
movdqa [ecx + 2*16], xmm2

movdqa xmm0, [eax + 2*16]
movdqa xmm2, xmm0
movdqa xmm1, [eax + 6*16]
psraw xmm0, 1
psubsw xmm0, xmm1
movdqa [ecx + 4*16], xmm0

psraw xmm1, 1
paddsw xmm1, xmm2
movdqa [ecx + 6*16], xmm1

movdqa xmm0, [eax + 1*16]
movdqa xmm1, [eax + 3*16]
movdqa xmm2, [eax + 5*16]
movdqa xmm3, [eax + 7*16]
movdqa xmm4, xmm0
movdqa xmm5, xmm1
movdqa xmm6, xmm2
movdqa xmm7, xmm3
psubsw xmm6, xmm1
psubsw xmm6, xmm3
psraw xmm7, 1
psubsw xmm6, xmm7
movdqa [ecx + 16], xmm6

paddsw xmm4, xmm3
psubsw xmm4, xmm5
psraw xmm5, 1
psubsw xmm4, xmm5
movdqa [ecx + 3*16], xmm4

movdqa xmm6, xmm2
psubsw xmm3, xmm0
paddsw xmm3, xmm6
psraw xmm6, 1
paddsw xmm3, xmm6
movdqa [ecx + 5*16], xmm3

paddsw xmm1, xmm2
paddsw xmm1, xmm0
psraw xmm0, 1
paddsw xmm1, xmm0
movdqa [ecx + 7*16], xmm1

emms
}

Меряем для этого кода время аналогично тому, как делали для MMX - получаем 19 мс. Ага. Прирост относительно MMX варианта, прооптимизированого для работы с памятью, 5%. Упс. Но, как выясняется, столь скромный результат связан с тем, что у меня AMD Athlon, на Intel-овском процессоре получилось ускорение на 50% (соответственно, для MMX получили 17 мс, для SSE - 9 мс). Выводы делать не буду, собственно, для меня понятно, что надо пользовать SSE, если возможно, потому что и 5% тоже хлеб, а 50% это уже и масло.

Надо сделать одно замечание по поводу SSE.

Для загрузки данных из памяти в регистры мы используем оператор movdqa, для него есть аналог movdqu, если использовать его, то вместо прироста скорости относительно MMX мы получим спад. Однако, чтобы использовать movdqa и не получить Run time error, исходные данные должны быть выровнены в памяти к 16 байтам. Это можно сделать либо автоматически:
__declspec(align(16) ) short arBufferSrc[64];

либо выделяя памяти побольше и подравнивая руками.

Заключение


Итак, MMX и SSE оптимизация не представляет сложностей, все работает на задаче, которая к этой оптимизации хорошо подходит. Относительно исходного C++ варианта использование SSE дает нам ускорение от 5 раз в худшем случае, до 10 раз в лучшем.

пятница, 25 июля 2008 г.

Про термины

Чем отличается руководящая должность от ответственной?

Правильно, на руководящей - руководят, на ответственной - отвечают.

Иногда эти должности совпадают, т.е. сам наруководил, сам отвечаешь.

А иногда нет, один руководит, а другой отвечает за то, что наделал под руководством первого.

вторник, 15 июля 2008 г.

Про спецификации

Пытаюсь заделать H264 декодер, соответственно, разбираюсь со спецификацией плюс скачал пару опенсырцовых исходников, чтобы контролировать процесс и сравнивать их результаты со своими.

А надо отметить, что я парень запасливый и спецификаций у меня штуки три от разных дат. Собственно сегодня довелось сравнить.

Итак на картинке кусок трех спецификаций, сверху вниз:

1. Draft от 7-14 марта 2003 года

2. Prepublished от марта 2005

3. Насколько я понимаю, окончательный вариант, от ноября 2007



Причем судя по всему, рабочий вариант - второй. Много думал.

понедельник, 7 июля 2008 г.

Про iPod shuffle

Собственно думал много написать, но время позднее, потому "буду краток"(с).

iPod shuffle - удобный агрегат, с хорошим дизайном, делающий все, что мне надо от плеера.

iTunes, которую впаривают для закачки файлов на тот самый iPod shuffle - убогое говно, спроектированное и реализованное людьми, с триппером мозга.

И не имею ни малейшего желания понимать, что iTunes это много больше, чем uploader для железного плеера. Мне другого варианта закачать файлы на shuffle компания, с жеваным яблоком вместо логотипа у которой я тот плеер купил, не предложила.

Судя по тому, что пишут в интернет, я отнюдь не одинок в оценке ситуации.

Если людям, на MacOS приходится пользоваться подобным софтом, я им искренне сочувствую.

воскресенье, 6 июля 2008 г.

Про документацию (продолжение)

На этот раз без лишних размусоливаний. Как известно, сейчас chm уже не так моден как раньше, а модно что-то на подобии MSDN желательно еще и встроенное в это самое MSDN.

И собственно тот набор xslt, которые я рекламировал в предыдущем сообщении, генерируют набор html и кое-какой дополнительной ерунды как раз, чтобы компилировать ее при помощи MSHelp 2.0. Я попробовал - тоже не плохо. Не очень понятно как оно при этом с портируемостью на разные машины, но думаю, что если и сложнее чем с chm, то не намного.

Значит план действий такой.

Заходим в MSVisual Studio, главное меню, File\New\Project\, в появившемся диалоге смотрим есть ли в дереве папка Other Projects\Help Projects\, а в ней вот такие вот шаблоны проектов:




Если их там не оказалось. Качаем Visual Studio .NET Help Integration Kit 2003. (Это ссылка на тот, который для MSVS 2003, для MSVS 2005 тоже есть, и google наверняка знает где.)

Ставим эту милую приблуду. Она встраивается в Visual Studio. Теперь можно создать проект Help-а. Создаем, добавляем туда все html и прочую ерунду, аналогично тому как мы делали для chm. Компилируем. Дивимся на результат.

пятница, 4 июля 2008 г.

Про документацию

Решил, что совсем неплохо было бы задокументировать то, что есть на данный момент в STDU Viewer. А как минимум COM-объекты (конечное приложение документировать особого смысла нет)

В результате хотелось получить, что-то похожее на MSDN (потому что мне нравиться MSDN, ага).

План действий такой.

Этап первый

Обрабатываем исходники doxygen-ом. Только генерим не html (он получается не совсем такой какой хочется), а xml.

Здесь надо учесть, что doxygen не очень хорошо понимает макросы типа STDMETHOD и т.п., а так же испытывает проблемы с такими ключевыми словами как __interface, которые используются в attributed dll-s. А у меня dll-ки именно что attributed.

Большая часть проблем решается стандартным для doxygen способом - в конфигурационном файле, прописываем параметр PREDEFINED. Получается что-то вроде:


PREDEFINED = "DECLARE_INTERFACE(name)=class name" \
"STDMETHOD(result,name)=virtual result name" \
"PURE= = 0" \
. . .


К сожалению, это не решает все проблемы. Например, при описании интерфейса в attributed dll у нас имеются строчки типа:

[id(200), propget] HRESULT PageFormat([in] long Index, [out, retval] long *pVal);

В реализации (классе наследованном от этого интерфейса), это будет выглядеть уже так

STDMETHOD(get_PageFormat)(long Index, long *pVal)

а после замены doxygen-ом макроса STDMETHOD(get_PageFormat) на virtual HRESULT get_PageFormat, мы получим нормальный метод, но этого метода нет в интерфейсе, соответственно, в документации отобразиться не правильное наследование, ну и пошли поехали проблемы.

Для решения пишем простенькое консольное приложение, и прописываем его в качестве параметра INPUT_FILTER в конфигурации doxygen-а. Для каждого файла документации doxygen вызовет этот фильтр, передав ему в качестве параметра путь к файлу, а мы в программе будем читать файл построчно, подправлять то, что нам надо и выдавать строчку на выход при помощи printf.

С первым этапом все.

Этап второй

Если все прошло гладко, то в результате работы doxygen мы получили набор xml файлов.
Теперь надо преобразовать их в html я пользовался набором xslt шаблонов вот отсюда (там кстати есть add-on под Visual Studio, вот только он под VS2005, а я, на своих проектах, до сих пор сижу на 2003-ей и перелезать особого смысла не вижу).

Чтобы применить шаблоны к xml нам понадобиться какая-нибудь программа, осуществлющая эту затейливую операцию. Правильнее всего воспользоваться nsxlt.exe, как это предлагается на сайте где брались xslt шаблоны, а можно написать свою.

Итак запускаем, применяем, получаем набор html-файлов.

Этап третий

Он самый короткий, создаем chm файл. Можно ручками (но лучше таки программно) создать проект, добавить туда все html, полученные на предыдущем этапе, и не забыть css и картинки. Компилим с помощью hhc.exe. И наслаждаемся.

Теперь немного лирических замечаний.

Чем хорош способ?

Во-первых, способ хорош тем, что он легко автоматизируется. Я для себя написал, небольшую оболочку, которая позволяет

1. загрузить конфигурацию doxygen, добавить/удалить файлы по которым будем строить документацию, сохранить конфигурацию.

2. запустить последовательно doxygen, и программу конвертирующую xml в html.

3. сгенерировать проект для создания chm (чтобы не добавлять html руками) и запустить chm-компилятор.

Т.е. практически все на автомате.

Во-вторых, система позволяет документировать код, не отвлекаясь от его написания. Не писать комментарии в коде можно только в случае, если уверено полагаешь, что после того как код написан и сдан, его уже никогда не придется править (более точно никогда не придется править тебе :) ). Такая наивность жестоко наказывается реальностью. Если же детство уже прошло, то комментировать код будешь так или иначе, и совсем не сложно приучить себя комментировать его с учетом синтаксиса doxygen.

В-третьих, то что получается в результате, лично мне, очень нравится.

Чего пока не хватает?

Надо прикрутить автоматическую или полуавтоматическую генерацию оглавления в chm файле. Думаю это не сложно сделать, но пока руки не дошли поковыряться.

Надо посмотреть возможность добавить страницы с общим описанием, т.е. не привязанным к конкретным классам, методам и т.п. Тоже пока просто не пробовал.

Надо несколько упорядочить описания. Пока на главной странице все свалено в один список, интерфейсы, классы, причем из всех длл-к. Это не шибко удобно. Мысли про варианты решения есть, но надо пробовть.