Как стать автором
Обновить
16
0
Bulashevich Dmitrii @Corviniol

Embedded Developer

Отправить сообщение
Это я и имел в виду под «позволяет легче читать огромные логи»
Что объект ExceptionContext лежит на стеке это понятно. Вопрос в том содержит ли он внутри себя указатель на кучу. Насколько я понимаю по умолчанию std::string свой внутренний буфер выделяет в куче, если не прибегать к особым ухищрениям с указанием конкретного аллокатора. Это описано, например, здесь. А здесь описан один из аллокаторов. Можно попробовать использовать их.
Если не нужно тащить все сообщения до catch-блока — то первая версия ExceptionContext в статье ровно это и делает, вообще не используя списки, а опираясь только на порядок вызова деструкторов.

Скорее уж, если мы не отказываемся от работы с кучей и используем С++11, я бы попробовал воспользоваться move-семантикой при добавлении сообщений в список в деструкторе ExceptionContext. Я не уверен, но мне кажется что если заранее зарезервировать (reserve) разумное количество элементов (по числу уровней вложенности приложения) то move-семантика для push_back не будет требовать выделения памяти и, соответственно, не вызовет исключений.
Да, почитал про ThreadContext в Log4j. Выглядит удобно (работал с подобным под Android) и частично решает проблему — позволяет легче читать огромные логи. Но, как мне кажется, оставляет проблему избыточности логирования — логирует даже абсолютно правильное исполнение 1000 итераций перед одной упавшей.
Или вы под NDC имели в виду что-то другое?
Если мне нужен ТОЛЬКО backtrace — то безусловно мне хватит libunwind. Или даже просто связки backtrace — addr2line. Но тогда может встать другая проблема — я знаю путь к тому что упало. Но если ошибка обусловлена входными данными и встречается довольно редко то мне всё равно будет сложно её локализовать. А так я могу попробовать заранее вставить логирование ключевых моментов и, если повезёт, в логах будет нужная мне информация. При этом я могу логировать «как можно больше» (в разумных пределах) не опасаясь за раздувание лога.
Основной упор я делаю на две возможности:
1) логировать входы-выходы не только из функций, но из любых областей видимости, включая итерации циклов.
2) добавлять в лог дополнительную информацию по моему усмотрению. В примере в статье — аргумент с которым я вызываю функцию.
Пожалуй += для std::string выглядит стрёмновато, тут я с вами соглашусь. А вот дальнейшую мысль я понял не совсем.
Может правильнее держать не строку, а некий связанный список(естесственно intrusive) и при создании контекста провязывать в него новый элемент, а в деструкторе соответственно вывязывать.

Вы предлагаете ExceptionContext::global_context сделать списком и добавлять/удалять конкретные сообщения?
Если добавлять сообщение в конструкторе, а удалять в деструкторе в случае нормального выполнения то получим много лишних операций добавления/удаления в список. Если добавлять в деструкторе, в случае раскручивания исключения, то точно также можно получить новое исключение. Хотя будет удобнее в использовании, так как можно будет контролируемо двигаться по уровням. Например — чтобы распечатать ближайшие 10 уровней.

Притом можно сделать все данные чисто на стеке

Если не ограничиваться константными строками, то не получится. Как ни странно даже простой sprintf требует наличия malloc. Опять же в catch-блоке вызывающей функции нижележащий стек уже размотан и созданные на нём объекты уничтожены. Так что при исключении придётся всё равно данные из стека куда-то переносить.
Каждый цикл скорее всего избыточно. По крайней мере в leaf-функции это не потребуется, т.к. самый вложенный уровень контекста доступен напрямую, в точке возбуждения исключения. Соответственно можно надеяться что наиболее узкие места, вроде внутреннего цикла в пузырьковой сортировке, не будут включать в себя сохранение контекста каждой итерации. Использование ExceptionContext целесообразно для сохранения целостности контекста между местом возбуждения исключения и местом обработки.

Не будет ли при таком подходе для больших программ ExceptionContext забивать весь код своим присутствием?
Вы имеете в виду строки исходного кода, размер бинарного файла, время выполнения или размер используемой памяти?

По строкам исходного кода — сколько-то будет, но не очень сильно. В зависимости от подробности сообщения. Если сводить только к месту вызова, наподобии ExceptionContext(string(__FILE_) + string(___LINE__)), то backtrace и addr2line или их аналоги действительно эффективнее. А вот экономного способа позволяющего логировать не только точку входа, но и дополнительный контекст (например аргументы или состояние переменных в вызывающей функции) я не видел и буду благодарен, если кто-то укажет имеющийся.

Про размер бинарного файла — сам по себе класс ExceptionContext вроде бы легковесен (точных замеро не проводил) и прирост обусловлен, в основном, кодом генерации сообщения. Здесь нужно искать баланс между информативностью и размером.

Время выполнения — да, оно вырастет, т.к. сообщение будет генерироваться всегда, даже если оно потом не востребовано. Это явно хуже, чем вариант с backtrace, где дополнительная обработка вызывается только при возбуждении исключения. Другой вопрос в том, будет ли это являться узким местом программы. Ну и соотнести пользу потенциальную пользу от лучшего логирования и вред от утяжеления.

Размер используемой памяти — мне кажется что им можно пренебречь для большинства систем. Конечно иногда речь идёт об embedded проектах с 16Кб RAM (а иногда и с 1Кб!), но это скорее экзотика. Да и С++ я для таких систем стараюсь не применять — обычно хватает С, где нет ни исключений, ни деструкторов. Если же речь идёт о более крупных проектах — давайте оценим потребление. Все экземпляры ExceptionContext имеют очень ограниченную область видимости. Фактически в одном потоке не может быть одновременно больше экземпляров, чем уровень вложенности областей видимости. И этот параметр для нерекурсивных алгоритмов поддаётся оценке на этапе проектирования, а не на этапе выполнения.

Понятно что во время сессии отладки поставив breakpoint на возбуждение исключения можно получить гораздо больше информации гораздо более лёгкими методами. Но речь-то идёт о том, чтобы получать осмысленную отладочную информацию после передачи программы на тестирование или в эксплуатацию.
Вариант с execinfo.h я тоже проходил, но он решает только половину задачи — определить StackTrace.
А вот проблему определения на какой итерации цикла (в каком контексте) произошло исключение он не решит.
Оптимально было бы их объединить — может быть напишу об этом в следующей статье, если данная покажет интерес сообщетсва.
А почему решили использовать Boxcar, а не Histogram?
Не являясь специалистом задам вопрос:
мне только кажется или более правильным решением, чем GET-запрос с параметрами, в этом случае будет использование cookies? Тогда можно было бы и открывать по ссылке предзаполненную форму, и передавать её другим без раскрытия ПД.
С другой стороны, есть книги, которые в принципе нужно читать не сильно связанные с программированием,

Это я и имел в виду под «совсем основополагающие вещи».
А перечитав Уоррена сразу лезешь в ассемблерный листинг и оптимизируешь деление на константу, подсчёт количества нулей и т.д.
Вообще хорошая статья, но где найти одну-две хорошие книги в месяц? Я вот навскидку не смогу вспомнить больше 10-15.
Мне кажется что из книг надо читать только совсем основополагающие вещи, не зависящие от языка программирования. Например — труды по алгоритмике или управлению проектами. А ЯП и конкретные технологии меняются слишком часто и лучше пользоваться Интернетом.
С одной стороны здесь требуется создание дополнительного типа на каждую функцию.
С другой стороны обычно в коде не так уж и много функций, которые имеют настолько длинный набор параметров, что надо делать именованные аргументы.
С третьей стороны для этого небольшого количества функций можно и «руками» завести структуру, инициализировать её через designated inits и передавать в функцию, чтобы не вызывать разрыва мозга у читателя кода.

Вообщем спорное, но интересное решение.
Как-то очень рекламно звучит, причём почти без завлекательной информации и интересных подробностей.
Но общий посыл статьи не изменится — изменения важнее чем вызываемые ими чувства. Но «важнее» — не значит «лучше».
Да. А даже если бы это было не так, то решение работало бы только в случае параметров разных типов, что тоже не очень хорошо.
Безусловно корректность будет наследоваться. С этим я не спорю и даже сам пользуюсь таким подходом, хотя и в гораздо менее сложных теоретических случаях.
не обязательно опускаться на такой низкий уровень, когда надо считать нули вначале
Я боюсь что в некоторых вычислительных задачах это будет обязательно. Например при анализе вектора ошибки в каких-нибудь линейных кодах. Т.е. безусловно можно обойтись без этого. Или придётся сильно жертвовать быстродействием. Или после «ручных» оптимизаций узких мест доказывать уже только их корректность.
А вообще начинание, безусловно, интересное.
Кстати — а вы не изучали генерацию дизайнов ПЛИС средствами Matlab? Понимаю, что это не совсем то, чем вы собираетесь заняться, но вроде бы родственная штука.
После перевода в «любой язык» может возникнуть куча сложностей:
  1. Доказательство корректности перевода из математического описания в ЯП
  2. Ограниченность используемых возможностей ЯП в сгенерированном коде.
  3. Плохое оформление сгенерированного кода может привести к сложностям в доработках.
  4. Плохая поддержка «тонкостей» языка не связанных с математическим аппаратом. Например — поддержка инструкции count leading zeros. Либо мы её включаем как примитив в «математический» язык, что плохо. Либо нужно чтобы транслятор понимал что данный «математический» код сводится к единственной инструкции в «нативном» и т.д.

А вот применение в ускорителях вычислений на базе ПЛИС с сильно ограниченным интерфейсом — весьма интересно. Ну и в совсем суровых DSP.
Мне кажется что описанный вами композиционный подход имеет будущее не как язык программирования, а как язык общения математиков.
Возможно что будут (если ещё нет) программные пакеты для проверки истинности утверждений записанных в данном формализме.
Я думаю что такие языки не распространены по причине своей ограниченной применимости. Далеко не всегда стоит задача математически строгого обоснования решения. Приведу пример из курса математической физики, который нам читали в универе.
Мы тогда разбирали уравнение колебаний струны. На лекциях была доказана единственность решения и долго-долго разбирали доказательство существования решения и ограничения на граничные условия, чтобы доказать сходимость получаемого ряда. А на семинарах мы вовсю решали данные задачи и не занимались доказательством сходимости ряда, т.к. «из физической интуиции» ясно что задача имеет решение, а значит ряд удовлетворяющий уравнению является решением, по свойству единственности решения.

Аналогично возьмём пример из программирования — минимальное количество бинарных операций (вентилей) реализующие заданную функцию. Построить функцию гораздо легче, чем найти точную нижнюю границу количества операций. Для «прикладных» задач не всегда нужно точное математическое описание (хотя иногда необходимо), а Computer Science меньше оперирует языками программирования.

А если говорить по делу то, как мне кажется, полезно отделять описание алгоритма от его программной реализации. Для описания существуют, например, блок-схемы или UML. Они не зависят от окружения в котором будут исполняться и наглядны (иногда). А для реализации существует куча ЯП, роблегчающих тот или иной аспект. Пример — доступ к предпоследнему символу строки в языке Python: my_str[-2] — это короткая запись эквивалентна my_str[len(my_str) — 2], но проще, короче и менее подвержена ошибкам. И скрывает детали реализации, ненужные в данный момент прикладному программисту. Хотя и затрудняет верификацию программы.

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

Информация

В рейтинге
Не участвует
Откуда
Санкт-Петербург и область, Россия
Зарегистрирован
Активность