MCP

воскресенье, 7 августа 2016 г.

Мегафичи моего архиватора

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

Полное шифрование без раскрытия информации

Т.е. вообще ничего не говорим. Даже что это архив нужного типа. Безопасность и красота. Есть рандомный набор байт, а является ли он архивом, или ещё чем-то — науке неизвестно. При этом оставлено обычное шифрование, которое можно оптимизировать в плане скорости, но оно раскрывает базовую информацию об архиве, что для обычных ситуаций не проблема, а наоборот удобство, но ведь надо периодически делать фичи и для параноиков.  

Управляющие блоки 

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

Восстановление данных (не сделано)

Мой архиватор по умолчанию использует CRC32C для проверки целостности, но хочется ещё и восстанавливать слегка повреждённые байты. И у меня есть прототип кода. Мало у кого есть подобное. У меня будет. Но пока не сделано, и это печально. Как будет сделано, буду хвалиться.


К чему я всё это написал? (Конечно же похвастаться!) К тому, что несовершенство мира раскрывается с каждой маленькой задачей. Вначале оказалась проблема с потоками и флашем, что привело к написанию моего архиватора, потом отсутствие управляющих последовательностей, восстановления данных... Что же будет дальше? Что ещё отсутствует из забавных фич в текущем наборе библиотек? Что ещё волшебного стоит мне добавить в архиватор?

воскресенье, 29 мая 2016 г.

Проблемы архиваторов, о которых никто не говорит

Я тут давно начал "писать" архиватор. Ну, т.е. как начал, сделал самое интересное, а допинать до конца терпения не хватило. Точнее почти хватило, но меня не устроили результаты, а, как известно, первая заповедь перфекциониста-прокрастинатора: лучше сделать хорошо, но никогда, чем плохо, но сейчас!

В общем, я полтора года не трогал свой архиватор, а тут решил, что всё-таки стоит пожертвовать 0.2% степени сжатия и 30МБ/c скорости, и выбрать простую и дубовую реализацию, так что. архиватор почти дописан, осталось сделать последний рывок и вычистить все баги, и добавить лоска. Впрочем, про сам мой алгоритм расскажу как-нибудь в другой раз.
Для начала расскажу про одну небольшую проблему, из-за которой, я начал писать архиватор, а не тупо взял готовый. Проблема весьма специфическая, но если вы в неё встрянете, то будет плохо, очень плохо.

В общем, вкратце, проблема называется Flush. Т.е. нормальная поддержка архиватором данной команды. Что это значит? А то, что большинство реализаций архиваторов внутри работают с блоками определённой длины (самой любимой) независимо! Т.е. команда Flush приводит к тому, что внутренний буфер отправляется в нижележащий стрим. И если в буфере много данных, то и проблем нет, а если мало, то результат становится очень неприятным с точки зрения качества сжатия.

Но ситуация может быть ещё хуже, архиваторы могут тупо проглатывать команду Flush (передаю привает реализации GZip в .NET, да она проглатывает Flush)! Что это значит? Это значит, что в некоторых задачах вы в принципе не можете использовать данную реализацию.

Собственно, про задачи-то я и забыл рассказать. Представьте, у вас есть TCP канал, в котором вы обмениваетесь сообщениями, например JSON'ом (так сейчас модно). Сообщения вам надо проталкивать на другую сторону и очень хочется их сжимать. А поскольку сообщения зависимые и похожие, то и сжимать зависимо. Проталкивать их надо командой Flush, что очевидно, а сообщения у вас небольшие. Что получается? Смотрите картинку:

В качестве тестовых данных, дамп википедии в 100 Мб (один из стандартных шаблонов для сжатия). По оси X - размер блока, который флушится, по оси Y - степень сжатия.
Что видно? ЧТо на блоке в 16 байт накладные расходны на заголовок превысили все ожидания (только GZip рулит за счёт мелкого заголовка). В дальнейшем, всё становится лучше, но до блока в 16Кб счастья особого нет. Т.е. сжимается, что уже неплохо, но могло быть и лучше.

Вот, если для примера взять бекап какой-нить базы (какой уже не помню, но это настоящая продуктовая база была). Особенность бекапа базы — очень выражена периодичность на 64Кб, да и вообще много похожести. Хотя флушить бекап базы не очень логично, но для примера пойдёт:

Результаты те же, только более явно выражены. И эта явность как раз и намекет, что 64Кб — это таки нашё всё. А лучше больше. Тот же график, но у него подрезано начала, чтобы был лучше виден масштаб:

Тут видно, что LZ4 за счёт максимального блока в 262Кб (по дефолту у данной реализации), вполне неплохо идёт дальше, но такой размер блока уже как-то лучше подходит для статичных данных, а там можно и более серьёзные аргументы, начинающиеся на цифру 7 применить.

В общем, цель моего архиватора: сделать так, чтобы Flush'и не оказывали серьёзного влияния, чтобы вы могли использовать архиватор не особо парясь. И как видите, всё вроде бы получается (мой - красненький). 1024 байта уже позволяют его весьма эффективно использовать.

Правда на блок уходит 8 лишних байт... и это меня бесит... Ждите следующий пост через пару лет...

воскресенье, 17 апреля 2016 г.

ZeroMQ vs NetMQ

Последнее время не спеша изучаю ZeroMQ, а так как пишу в основном на .NET, то решил сравнить производительность двух реализаций, "официальной" clrzmq4 (в nuget'е пакет называется ZeroMQ, и все примеры с сайта на нём), и альтернативный вариант NetMQ, написанный полностью на .NET и совместимый с ZeroMQ.

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

Для самого простого варианта я взял Publisher/Subscribe модель и начал закидывать сообщения по-кругу, и смотреть на результат. Результат оказался слегка неожиданным, но красноречивым. Не буду делать графики, ибо и без них всё понятно. Просто некоторые пункты:

  • clrzmq4/NetMQ одинаково быстры (около миллиона сообщений в секунду) при работе с массивами байт
  • Два паблишера на одного сабскрайбера (странный вариант, ну да ладно), гораздо быстрее работают у NetMQ 
  • При попытке работы со строками clrzmq4 начинает резко сливать производительность (в 3 раза). Судя по-всему, не очень удачная работа с маршаллингом
  • С clrzmq4 надо быть очень аккуратным, пропущенный Dispose даст прирост в скорости, но может вызвать неприятные ошибки
В общем, тестировать производительность дальше неинтересно, не думаю, что там есть какая-то волшебная разница на разных паттернах, с учётом того, что у NetMQ гораздо приятнее API, рекомендую рассматривать в качестве реализации API сразу же его и не париться с выбором.
NetMQ — быстрее и удобнее.

четверг, 7 апреля 2016 г.

Производительность SQL при поиске по ключу в зависимости от типа

Последнее время в проектах для SQL-таблиц в качестве идентификатора мы используем гуиды. Ибо уникально, не нужны идентити, и можно идшники генерить на клиенте (причём сразу для всего дерева объектов), а потом вставлять в базу (при этом, хоть асинхронно).

Но вот тут у меня закралась мысль, а насколько эффективно SQL в дальнейшем работает с данными типами? И не будут ли голые инты быстрее во всяких селектах и джойнах, да так, что перекроют с лихвой все недостатки при вставке (ведь в основном все селектят, это обычно важнее, чем вставка).

Тестировать можно долго и по-разному, но для простоты, я ограничился следующими условиями:

  • Таблицы с Id и Value (чтобы совсем грустные не были
  • Id типов int, bigint и guid (т.е. 4, 8 и 16 байт, меньшие типы несерьёзно тестировать)
  • Id - кластерный индекс и первичный ключ
  • Заполнение по Identity и по рандому (чтобы проверить, влияет ли более разреженный индекс на результаты). Для гуидов идентити эмулировалось вручную, генерацией "правильных" данных 
  • Заранее вставил 1000000 записей в каждую таблицу
  • Селекчу много раз определённую запись (глупо, но SQL ведь особых чудес ловкости отваги проявить тут не сможет, впрочем, попробовал разные записи, результат похож)
В качестве подходящей незагруженной железяки (чтобы не влияли на результаты) оказался сервер с SSD, достаточным количеством памяти, но с SQL 2008. Не очень свежо, но не думаю, что что-то менялось на подобных запросах. Впрочем, позднее перетестирую, и если результаты изменятся — обновлю пост.

В общем, замерил результаты и.... они оказались практически идентичными. Конечно, разница есть в пределах 3%, но это несущественно. Впрочем, смотрите картинку сами:
Вам может показаться, что разница существенная и в пользу инта, давайте немного поправим масштаб картинки:


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

Но дальше я решил разнообразить тесты и сделать точно такие же таблицы, но без ключей (не повторять самостоятельно, опасно для жизни!). Результаты оказались слегка обескураживающими
Инт тут не просто в пролёте, он в почти в 3 раза медленнее гуида! А бигинт самый быстрый (что, в принципе не так уж и странно, но не в случае такого провала с интом).
Вначале я думал, что табличке с интами не повезло и она нарвалась на длинный фулскан, пробовал селектить разные записи — результат идентичный. В чём дело, я не понимаю. Возможно проблемы SQL сервера, а возможно так и задумано, тут я не специалист. В общем, надо доисследовать, но ситуация очень неординарная. Бигинты круче интов в несколько раз...

Ну и напоследок размер таблиц. Понятно, что это всё сильно depends и от данных и от наполненности индекса, но общее представление можно поиметь:


Тут всё просто и логично, снос кластерного индекса экономит 4-5 мегабайт на миллионе записей, бигинт больше инта где-то на 4 мегабайта, гуид больше бигинта на 8-9 мегабайт в зависимости от наличия/отсутствия индекса.

Update: Обновил SQL Server с 2008 до 2014. Результаты: Поиск по индексу стал где-то на 40% быстрее (смысл обновляться оказывается имеется!), Поиск по таблицам без первичного ключа: с интами ничего не изменилось, гуиды стали быстрее на 30%, т.. практически сравнялись с бигнитами. Т.е. проблемы те же самые на месте, но гуиды стали ещё более привлекательнее. Картинка с данными:

PS: OLTP-таблицы дают офигенный прирост скорости для таблиц без индекса, но OLTP для таблиц с индексом даёт те же результаты в моём случае. В общем, OLTP, это предмет отдельного поста, просто, раз обновился, решил глянуть.

среда, 6 апреля 2016 г.

Windows Update ошибка 800F0922

Собственно, данный пост на случай, если его нагуглят, ибо гугление ошибок с виндовым апдейтом — самое милое дело. Других сведений про источник проблемы обычно нет.
Итак, я долго возился с тем, что у меня не ставились некоторые обновления. Занимался уже делением пополам и установкой того, что ставится (ибо, если не ставится одно, откатываются все пакетом, ибо транзакционность). Но на днях решил разобраться, в чём же причина.
Собственно, т.к. не ставилась куча апдейтов, код конкретного не привожу, но ошибка была с кодом 800F0922, и выглядела примерно так

Гуглёж по коду выдал кучу безумных рекомендаций вида — удалить драйвер Cisco VPN или увеличить служебный раздел System Reserved. А также стандартные пляски с бубном вида sfc /scannow и сброса состояния Windows Update. Но первые причины явно были не мои, а вторые помогают только, когда действительно сломано всё. У меня же были проблемы только с частью апдейтов.

В общем, я пошёл копать в логи, которые у апдейтов находятся в C:\Windows\Logs\CBS\CBS.log на предмет, того, что же там не так. А не так там оказалась подобная строчка (хана дизайну блога, ну да ладно):

2016-04-05 10:03:35, Error                 CSI    00000001 (F) Logged @2016/4/5:07:03:35.059 : [ml:262{131},l:260{130}]"events installer: online=1, install=1, component=wow64_Microsoft-Windows-Win32k_31bf3856ad364e35_6.2.9200.17461_neutral_release__."
[gle=0x80004005]
2016-04-05 10:03:35, Error                 CSI    00000002 (F) Logged @2016/4/5:07:03:35.090 : [ml:240{120},l:238{119}]"EventAITrace:Provider Microsoft-Windows-Win32k is already installed with GUID {e7ef96be-969f-414f-97d7-3ddb7b558ccc}.

"
[gle=0x80004005]


2016-04-05 10:03:35, Error                 CSI    00000003 (F) Logged @2016/4/5:07:03:35.090 : [ml:168{84},l:166{83}]"WmiCmiPlugin manproc.cpp(683): InstrumentationManifestAssert failed. HR=0x80073aa2."
[gle=0x80004005]


2016-04-05 10:03:35, Error                 CSI    00000004 (F) Logged @2016/4/5:07:03:35.090 : [ml:166{83},l:164{82}]"WmiCmiPlugin eventloghandler.cpp(192): ProcessEventsInstall failed. HR=0x80073aa2."
[gle=0x80004005]


2016-04-05 10:03:35, Error                 CSI    00000005 (F) Logged @2016/4/5:07:03:35.090 : [ml:170{85},l:168{84}]"WmiCmiPlugin eventloghandler.cpp(212): EventLogHandlerInstall failed. HR=0x80073aa2."
[gle=0x80004005]


2016-04-05 10:03:35, Error                 CSI    00000006@2016/4/5:07:03:35.090 (F) CMIADAPTER: Inner Error Message from AI HRESULT = HRESULT_FROM_WIN32(15010)
 [
[22]"Configuration error.

"
]

аКлючевое в этом: EventAITrace:Provider Microsoft-Windows-Win32k is already installed with GUID {e7ef96be-969f-414f-97d7-3ddb7b558ccc}, т.е. в переводе на русский, уже есть источник журнала событий, а его опять пытаются сделать. Почему бы не успокоиться, я не знаю. Но решение тут банальное:

  • идём в реестр в ветку HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WINEVT\Publishers\{e7ef96be-969f-414f-97d7-3ddb7b558ccc}
  • Экспортируем её на всякий случай
  • Удаляем
  • Перегружаемся (это важно)
  • Пробуем установить апдейт заново
И чудо! всё заработало. Все апдейты стали снова ставиться без проблем.
Это, естественно, не единственный вариант источника проблемы, но, может быть в вашем случае он поможет.

среда, 20 января 2016 г.

Не верьте бенчмаркам. Все врут!

Последние несколько дней я развлекаюсь с кодогенерацией и выяснением особенностей работы .NET в плане производительности и эффективности. В качестве кошки, на которой я решил тренироваться была выбрана идея Написать самый быстрый JSON Serializer для .NET.  Не факт, что я его напишу, но есть некоторые подвижки, а также обнадёживающие результаты.

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

Но также я видел всякие тесты, что сериализатор от ServiceStack, самый быстрый сериалайзер
И меня это смутило, что же там ещё можно сделать в .NET, чтобы было ещё быстрее (хотя я знаю что, но не верю, что в зравом уме, кто-то подобное будет делать). В общем нашёл несколько красивых графиков: тут, тут и тут. И решил-таки проверить на практике.

Взял простенький массив:
В этом массиве есть числа, даты, строки разных видов, энамы и нуллабельные поля. Т.е. полный фарш. Набрал всяких сериализаторов и пошёл сериализовать. Результаты следующие:
.net json serializers benchmarks
Как видно, майкрософтовский в полной жопе, за ним идёт сериализатор с говорящим названием fastJSON (понятие не имею, что это, может где-то он действительно быстр), И затем... ServiceStack! На 66% медленнее JSON.NET Вот и хвалёные тесты.

Самый быстрый оказался Jil, таки есть ещё более безумный человек чем я. Мой тоже неплохо себя ведёт, несмотря на то, что прототип. Хотя стоит ли мой мучать, при наличии Jil, это интересный вопрос.

PS: Моим бенчмаркам тоже не верьте. 

четверг, 31 декабря 2015 г.

Про велосипеды

Несмотря на то, что сегодня 31-е декабря, хочется написать не праздничный, а технический пост. Хотя и философский слегка. Как же без этого. 

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

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

Первая проблема это то, что в большинстве случаев сторонная библиотека не идеально подходит для вашей задачи.
Есть некая область, которая очень плохо вписывается в область внешней библиотеки. Почему? Потому что у её авторов своё видение архитектуры у вас своё. Авторы делают универсальную библиотеку, вы делаете конкретный проект. Всё это приводит к тому, что есть в коде моменты, в которых приходится извращаться, чтобы запихнуть вашу логику, в логику библиотеки. Не буду приводить конкретные примеры, но часто проблемы бывают с лишней абстракцией, недостаточной производительностью в определённых ситуациях, проблемами с расширением функционала. В общем, проблемы бывают и достаточно часто. Но при этом все любят считать время, потораченное на свою реализацию и не считать время, потраченное на борьбу с внешней библиотекой.
Например, у нас был случай, когда программист чуть ли не 2 дня потратил на интеграцию системы нотификаций в наш проект, т.к. она тянула за собой огромное число зависимостей и требовала кучу настроек. Правда, умела она тоже много чего, хоть нам это и не особо требовалось. Для нашего случая можно было бы в лоб отправить письмо, заняло бы это пару часов на простенькую реализацию. Но в конце-концов мы сделали весьма неплохую реализацию отправки сообщений, которую подключить можно за 10 минут, которую мы знаем досконально и точно можем сказать — это можно сделать, это нельзя, а это можно но костылём.

Вот, кстати, зависимости, больная тема. Некоторые проекты выглядят как-то так:
Красным цветом я выделил ваш проект, остальные круги — библиотеки. Чать их функционала пересекается друг с другом, Некоторые библиотеки используются только ради одного мелкого куска, но всё это тянет кучу файлов, кучу зависимостей, и они все не бесплатные. Приложение в результате может требоваить дополнительной установки компонетов, которые лично вам не нужны, но нужны библиотеке, размер приложения резко увеличивается, лезут неявные конфликты типа транзитивных зависимостей. А потом пишется инструкция в три страницы по установке, а очередной программист облачается в костюм хим.защиты и лезет выяснять, почему приложение стартует 20 секунд и жрёт гигабайт памяти на простейших тестовых данных.

В общем, что я хочу сказать — внешние библиотеки могут приводить к куче отложенных проблем, к вендор.локу, когда она ироникают во весь код и избавиться от данной реализации уже невозможно без полного переписывания и прочим неприятным проблемам. При этом, все могут героически с этим бороться или вообще не считают проблемой. В тоже время считать, что своё решение хуже и каждый раз спрашивать: А может стоило использовать технологию X? Может и стоило... а может и нет, ведь сейчас ты пытаешься решить незначительную проблему, от которой будут счастливы пользователи, а на технологии  X проблему в принципе не решить, ок, все-лишь менее счастливые пользователи, мир не перевернулся.