Общие сведения
Дата проведения: 19 апреля 2008 года
Продолжительность: 1 час
Место проведения: irc-канал сайта Ogre3D.ru
Опубликовали статью
Сайт Ogre3D.ru: ссылка
Коммунити "Gamedev Lecture" сайта Gamedev.ru: ссылка
Мой личный сайт: ссылка
Блог сайта Gamedeff.com: ссылка
Лог
18:01:04 Rageous: всем привет
18:01:11 Rageous: пару слов о том, как будет проходить лекция
18:01:21 Rageous: сначала я прочитаю то, что подготовил, после этого можно будет задать вопросы
18:01:34 Rageous: пожалуйста, не пишите их в личку :)
18:02:06 Rageous: лекция, которую я сегодня читаю, посвящена моим исследованиям в области компресси векторных данных
18:02:30 Rageous: в связи с тем, что мне уже задавали вопросы, что есть векторные данные, я проясню этот вопрос отдельно
18:02:52 Rageous: здесь и далее под векторными данными я буду подразумевать функции f(t), где t - время, а f(t) - функция, возвращающая некоторое рациональное значение
18:03:16 Rageous: с подобными данными мы работаем каждый день: в играх это анимации персонажей, пути перемещения объектов, траектории движения физических тел
18:03:35 Rageous: в софте это информация по трекингу машин автопарка, к примеру
18:03:58 Rageous: здесь, к примеру, можно увидеть сохраненный путь автомобиля. сохраненные данные используются для анализа скорости, времени и количества остановок:
18:04:21 Rageous: здесь зеленоградский автопарк предоставил свои данные для анализа дорожной обстановки на одной из самых нагруженных московских трасс, чем облегчил жизнь множеству автомобилистов:
18:04:47 Rageous: итак, векторные данные полезны. часто бывает полезно их сохранять. к сожалению их объем нередко далек от "незначительного"
18:05:17 Rageous: в связи с этим возникает вопрос об их компрессии
18:05:54 Rageous: к примеру, если вы хотите сохранить реплей заезда в автосимуляторе, чтобы поделиться им с друзьями, вам придется думать о том, как переслать по интернету до десятка мегабайт данных
18:06:56 Rageous: если же вы занимаетесь ПО и хотите хранить годами информацию по имеющимся в вашем распоражении автомобилям или морским судам или самолетам или еще чему - вы можете легко столкнуться с проблемой огромного объема этих сведений
18:07:19 Rageous: к счастью существуют архиваторы :)
18:07:48 Rageous: и к сожалению сжатие векторных данных - одно из самых низких для большинства современных алгоритмов
18:08:00 Rageous: так, например, zip сжимает такие данные в 2-3 раза
18:08:17 Rageous: rar и 7zip обеспечивают сжатие до 25 раз, но делают это весьма медленно
18:09:07 Rageous: слабым местом архиваторов является то, что они предоставляют lossless (без потерь) компрессию, а так же не располагают сведениями о структуре предоставленных им данных
18:10:17 Rageous: целью, которую я ставил перед собой, когда начинал заниматься компрессией векторных данных, было сжатие в 100-1000 раз при скорости достаточной, чтобы осуществлять это сжатие в реальном времени без серьезного удара по производительности основного приложения
18:10:34 Rageous: итак, что я делал
18:11:17 Rageous: первое - все данные были разбиты на отдельные потоки вида v(t), где v - это функция времени, возвращающая одно или несколько рациональных чисел
18:12:07 Rageous: имеющиеся данные были слегка изменены, чтобы избавиться от избыточности: вместо полных матриц 4*4 трансормации я писал вектора и нормализованные (3 компоненты) кватернионы
18:12:48 Rageous: потоки, объединяющие в себе несколько флоатов для одного момента времени пригодились для записи нормализованных кватернионов - уменьшив погрешность
18:13:27 Rageous: второе - так как данные нужно было сжимать и сохранять в реальном времени, все данные были разбиты на пакеты фиксированной длины (обычно порядка 30-90 секунд)
18:14:09 Rageous: в памяти одновременно находятся два пакета: текущий и прошлый. прошлый сжимается вместе с наполнением текущего, что позволяет избежать пиковых нагрузок при сохранении пакета в файл или иное хранилище
18:14:33 Rageous: после сохранения сжатого пакета его место, соот-но, занимает текущий, а текущий начинает наполняться новыми данными
18:16:08 Rageous: третье - данные в библиотеку приходят очень часто. в играх - раз в кадр. в софте - при каждом замере. в большинстве ситуаций это означает избыточность данных, т.к. объект, который движется почти прямолинейно, создает такой же объем сведений, как и объект, который бешенно меняет свою позицию и ориентацию
18:16:19 Rageous: чтобы избавиться от этой избыточности нужно провести аппроксимацию
18:16:54 Rageous: наиболее частым и простым подходом в данной ситуации является аппроксимация кусочно-линейным сплайном
18:17:01 Rageous: или, проще говоря, ломаной
18:17:31 Rageous: фиттинг (вычисление минимального набора необходимых ключей) для такого сплайна осуществляется очень просто и быстро
18:18:03 Rageous: но результат оставляет желать лучшего как с т.з. компрессии, так и с т.з. внешнего вида полученных после обработки данных
18:18:15 Rageous: машина начинает двигаться "угловато"
18:18:49 Rageous: вторым подходом является фиттинг сплайнов более высокого порядка - квадратичных и кубических сплайнов
18:19:25 Rageous: сразу скажу, несмотря на интуитивно-большую гибкость кубических, я остановился на квадратичных
18:19:46 Rageous: это связано с тем, что фиттинг кубических сплайнов - весьма сложный процесс, который требует тонкой настройки
18:20:02 Rageous: в противном случае на сплайне появляются выбросы, которые сводят на нет все преимущества
18:20:10 Rageous: либо же компрессия становится чрезвычайно низкой
18:20:41 Rageous: неплохая статья с исходниками по кубическим сплайнам находится здесь: http://alglib.sources.ru/interpolation/spline3.php
18:21:00 Rageous: про фиттинг можно почитать здесь: http://www.stanford.edu/~boyd/cvx/examples/cvxbook/Ch06_approx_fitting/html/fig6_20.html и здесь: http://ask.metafilter.com/50554/Generating-Bezier-curves-from-discrete-point-sets
18:21:22 Rageous: что же квадратичный сплайн. я взял сплайн безье: http://ru.wikipedia.org/wiki/Кривая_Безье
18:21:54 Rageous: он имеет очень простую формулу, которая легко обращается, дабы вычислить опорную точку по имеющимся точкам начала и конца, а так же некоторой промежуточной
18:22:05 Rageous: потому его фиттинг легко объяснить на пальцах:
18:22:24 Rageous: мы берем весь пакет данных, вычисляем для него усредненную опорную точку
18:22:55 Rageous: с ней прогоняем функцию оценки ошибки. если ошибка слишком велика, делим данные пополам - и проводим ту же процедуру с первой половиной
18:23:37 Rageous: так - до тех пор, пока не найдем кусок данных максимальной длины, который можно представить в виде начальной, конечной точек и опорного значения (P1 в формулах на вики)
18:24:09 Rageous: с оставшимися данными процесс повторяется до тех пор, пока весь пакет не будет представлен в виде наборов квадратичных сплайнов
18:24:26 Rageous: что дал квадратичный сплайн:
18:24:42 Rageous: скорость компрессии в сравнении с линейным примерно вдвое
18:25:11 Rageous: однако коэффициент компрессии в среднем вырос на порядок
18:25:39 Rageous: все, что оставалось - сохранить результаты в минимальном объеме
18:26:05 Rageous: для этого хорошо подходит квантование и дельта-компрессия
18:26:37 Rageous: мы анализируем данные в пределах пакета, находим макс. и мин. значения, а так же определяем характер функции: монотонная ли она. если да, то возрастает или убывает
18:27:28 Rageous: полученный диапазон, пользуясь размером допустимой ошибки как размером кванта, мы дискретизируем
18:28:20 Rageous: и сохраняем в заголовок пакета пределы отклонения, характер и функции. а в данные пишем целочисленные значения
18:28:34 Rageous: тут, разумеется, очень кстати битстрим
18:28:45 Rageous: за счет которого мы пишем дельты с минимально необходимым битрейтом
18:29:25 Rageous: на этом математические подходы заканчиваются, но мы можем выжать еще немного за счет более рациональной подготовки данных
18:29:48 Rageous: так, например, мы можем менять допустимую погрешность между пакетами и задавать ее отдельно для разных потоков данных
18:30:11 Rageous: это позволит нам уменьшать точность позиционирования удаленных от наблюдателя объектов
18:30:43 Rageous: но за счет этого сильно (до 10 раз и выше) экономить объем данных
18:31:15 Rageous: время, очевидно, мы так же подвергаем квантованию
18:31:26 Rageous: точнее говоря, можем это сделать, но здесь таится одна тонкость
18:31:36 Rageous: проще всего будет ее понять, посмотрев рисунок:
18:32:15 Rageous: по горизонтали отложено время, вертикальными прямыми отмечены границы квантов
18:32:35 Rageous: черная кривая - начальные значения. серые коридор вокруг нее - макс. допустимая погрешность
18:32:42 Rageous: синяя кривая - восстановленные значения
18:33:06 Rageous: из рисунка видно, что из-за квантования первая точка излома сместилась на полкванта влево
18:33:20 Rageous: что привело к многократному превышению допустимой погрешности
18:33:36 Rageous: если бы вы писали юнит-тесты на систему, этот тест провалился бы. мой провалился :)
18:34:01 Rageous: к счастью подобные ошибки строго локализованы во времени - и не могут длиться дольше длины кванта
18:34:36 Rageous: что по сути означает, что ваш объект не улетит в неизвестность и не проедет в стене, а всего лишь чуть медленнее сменит ориентацию или позицию
18:34:47 Rageous: скажем, не за 1мс, а за 2мс - по размеру кванта времени
18:35:09 Rageous: итак, результаты всей этой математики
18:35:18 Rageous: сводная таблица: http://www.everfall.com/paste/id.php?vv6fv52bihxg
18:35:37 Rageous: поясню заголовки столбцов в ней: length - продолжительность теста в секундах
18:35:47 Rageous: packet - длина пакета в секундах
18:35:58 Rageous: quantum - размер кванта времени в секундах
18:36:09 Rageous: maxerror - предельно допустимая погрешность
18:36:19 Rageous: quantize - использовалось ли квантование времени
18:36:47 Rageous: RESULT состоит из двух значений. знак + или - означает, прошел ли данный тест проверку на макс. погрешность
18:36:57 Rageous: xNNNN - это степень компрессии, записанная в разах
18:37:55 Rageous: к примеру, превый тест Default длился чуть больше 8 минут, использовал пакеты по полторы минуты, погрешность в 1мм и квант времени 20мс
18:38:18 Rageous: данные после компрессии занимают в 630 раз меньше тех, что пришли в библиотеку
18:38:35 Rageous: так же стоит отметить по этой таблице некоторые закономерности:
18:39:00 Rageous: 1. размер погрешности и кванта влияют на качество нелинейно, потому придется повозиться с их тонкой настройкой под конкретный проект
18:39:26 Rageous: 2. квантование времени дает дополнительные 30% сжатия, но имеет недостатки, которые я описал выше
18:39:47 Rageous: 3. размер пакета в большинстве случае можно варьировать не опасаясь серьезной потери компрессии
18:40:11 Rageous: однако, слишком маленькие пакеты дают низкое соотношение размера заголовка пакета к полезным данным, что все же сказывается на силе сжатия
18:40:47 Rageous: как известно, любые данные можно сжать до одного байта. к сожалению, до сих пор не найден алгоритм их декомпрессии из этого байта )
18:41:03 Rageous: дабы не оставлять это щекотливый момент в стороне, пара слов про декомпрессию
18:41:26 Rageous: декомпрессия имеет линейную сложность при непрерывном воспроизведении и логарифмическую при перемотке
18:41:51 Rageous: перемотку, кстати, пользуясь обычными архиваторами, сделать было бы значительно более затруднительно - без декомпрессии всех данных целиком
18:42:14 Rageous: детальный отчет по одному из тестов можно найти здесь: http://www.everfall.com/paste/id.php?ittkm4w5swo0
18:42:30 Rageous: опять же, ключевые моменты: Performance 38.1 Mb/s
18:42:43 Rageous: это скорость сжатия в мегаБИТах в секунду
18:43:00 Rageous: что такое 5МБ/сек. это 10 мегабайт десятиминутного реплея сжатые за 2 секунды
18:43:09 Rageous: то есть сжатие съест менее 1% от времени работы вашего приложения
18:43:26 Rageous: статистика по объемам данных: Floats 80%, Time 15% и Headers 3%
18:43:58 Rageous: как мы и отмечали раньше - размер заголовков сравнительно мал, а квантованное время значительно меньше влияет на объем, чем сами данные
18:44:33 Rageous: если сравнить показатели по затраченному времени - это 29 + 88мсек для сжатия и 32мсек для декомпрессии - станет ясно, что декомпрессия втрое быстрее сжатия
18:45:50 Rageous: библиотека обеспечивает желаемый уровень компрессии при соблюдении заданных требований
18:46:09 Rageous: так что на этом все. и я готов ответить на ваши вопросы
Вопросы и ответы
- Five_Stars: потоки это несколько параллельных, или несколько последовательных?
- Rageous: потоки идут параллельно. например, позиция - это один поток из трех флоатов или три потока по одному
.
- Five_Stars: интересно, а как ты устроил передаваемые пакеты? все потоки в нём пишутся последовательно одним большим куском (поток1, поток2, поток3), или всё как-то в зависимости от времени(10сек Потока1, 10сек Потока2... и сначала)... или по-байтно(10байт Потока1, 100байт Потока2... и сначала)
- Rageous: в каждом пакете набор потоков. в нем они лежат последовательно - все данные одного потока целиком, потом второго и т.д.
.
- Five_Stars: а это удобно? нет, я понимаю, что у тебя были не очень большие пакеты и декомпресс может отработать всё сразу, чтобы незаметно, но, а насчёт варианта разбивки (по времени, например, там, правда, наверное, синхронизировать придётся ) ты не думал? Для баальших баальших пакетов...
- Rageous: это как раз регулируется длиной пакета. если ее поставить меньше, в памяти он будет не настолько велик - и проблема отпадет
- Rageous: с этим нельзя не согласиться
- Wraith: декомпресс тоже выполняется с двойной буферизацией: один буфер ты играешь, другой заполняется данными
- Rageous: нет, при декомпрессе там не нужно двойной буферизации, только загрузка асинхронная
- Wraith: даже так?
- Rageous: у воспроизведения сложность очень низкая: библиотека играет много быстрее, чем жмет
.
- AnnyCh: вы говорили о "битстриме", за счет которого достигается минимально возможный битрейт... к сожалению мне не понятен этот термин... это какая-то разновидность энтропийного сжатия?
- Rageous: нет, это всего лишь класс, обеспечивающий упаковку данных в поток бит. то есть, к примеру, числа можно записать не в 32 бита, а в любое заданное количество. разумеется, если значение числа в это количество не умещается, произойдет своеобразное переполнение: будут записаны лишь N младших бит. булевые данные, к примеру, в битстрим обычно пишутся одним битом
.
- Joric: не проще ли по карте контрольные точки расставить и не заниматься ерундой?
- Rageous: речь о сохранении данных, которые пришли со спутниковой системы, к примеру, и как раз о том, чтобы эти точки выбрать
.
- AnnyCh: вы говорили, что данные о компонентах вектора при сжатии помещаются в буфер последовательно, и что распаковка идет напрямую из битстрима... т.е. при таком подходе пока не будут получены все части сжатого буфера распаковка все равно невозможна?
- Rageous: распаковка может идти только при наличии всех данных одного пакета
- AnnyCh: ясно
- Rageous: хотя в принципе, если воспроизводить последовательно - можно воспроизводить и частично загруженные пакеты - при условии того, что загружен их заголовок и необходимый объем данных (от начала пакета до текущего времени). но т.к. это не особо часто требуется - например, если хочется делать что-то типа сетевого телевидения, то можно просто уменьшить длины пакетов, то подобной поддержки я не делал - она исключается построением индексов вида (время - позиция в потоке) при загрузке пакета, которые используются для ускорения перемотки
- AnnyCh: да, пакет тут будет более правильным словом
