Как стать автором
Обновить
86
0.3
Евгений Охотников @eao197

Велосипедостроитель, программист-камикадзе

Отправить сообщение
К сожалению, это не мой аргумент, а те рекомендации, которые рекомендуются к использованию уже пару десятков лет… :(
Ага.

Со временем подобный код превращается во что-то такое:
Gdiplus::Bitmap* bitmap; // (1)

    // Инициализация GDI+.
    Gdiplus::GdiplusStartup(&token, &input, &output);

    // Еще какие-то действия, которые появились в последствии.
    ...

    bitmap->SetSomeProperty(...); // Упс №1: bitmap еще не создан.
        // Из-за того, что в (1) переменная bitmap даже не получила
        // нулевого значения может произойти все, что угодно.

    // Еще какие-то действия, которые добавились позже.
    ...

    bitmap = new Gdiplus::Bitmap(filename); // Вот и создали объект.

    int w = bitmap->GetWidth();
    int h = bitmap->GetHeight();
    
    // Тут со временем появились какие-то проверки.
    if( w > SOME_LIMIT || h > SOME_ANOTHER_LIMIT )
      return; // Упс №2: утекла память, т.к. delete сделать забыли.

    for (int i = 0; i < w; i++)
        for (int j = 0; j < h; j++)
        {
           ...
        }
    delete bitmap;

Ну и работа с динамической памятью всегда дороже размещения объекта на стеке.

Ничего бы этого не было, если бы вы просто написали:
Gdiplus::Bitmap bitmap(filename);


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

Ну вот так в последствии C++ный код начинает и тормозить, и течь, и падать.

Зато разработчики PVS-Studio без работы и клиентов не останутся :)
Заканчивался 2016-й год, а C++ники продолжали вручную вызывать new и delete…
bitmap = new Gdiplus::Bitmap(filename);	
...
delete bitmap;

Скажите, а зачем вам вообще потребовалось Bitmap создавать динамически? Почему нельзя было объявить bitmap как автоматическую переменную на стеке?
Статический анализ — это полезная практика, особенно для старого кода на таких небезопасных языках, как C и С++.

Но у вас здесь получилось как в анекдоте «у рыбы нет шерсти, поэтому нет блох, а вот кстати о блохах».
> позволяющий выявлять ещё на этапе кодирования многие ошибки в программах на языке С, С++ и С#

Можно узнать, как инструмент статанализа, не имея привязок к конкретной предметной области, сможет выловить вот эту ошибку с округлением в системе Patriot?
Спасибо вам за проявленный интерес.
Еще мы подготовили ряд презентаций с рассказом об отдельных возможностях SO-5 в виде серии Deep Dive into SObjectizer-5.5. Некоторым такая форма подачи больше нравится, проще заходит. Найти эти презентации можно здесь.
Зависит, прежде всего, от двух вещей:
— уровня знания C++ (использование шаблонов и лябда-функций не должно вызывать проблем);
— от сложности предметной области. Иногда агенты могут быть сложными с запутанной логикой, но это не столько проблема SObjectizer, сколько сложность самой задачи, для которой агент написан.

Очень сильно облегчает погружение в чужой код то, что агенты в SO-5 — это классы со специальными методами (so_define_agent, so_evt_start, so_evt_finish). Нужно посмотреть, как и на что агент подписывается — смотришь в so_define_agent. Нужно узнать, что агент делает на старте — смотришь в so_evt_start и т.д. В эту же тему и то, что состояния агента описываются отдельными атрибутами. В эту же тему и то, что событие — это, обычно, отдельный метод.

Пример того, как может выглядеть реальный агент (не самый простой при этом), можно увидеть здесь.
Интересно, а почему вы в секции Reduce(accumulate) решили использовать в примерах std::for_each и функтор, а не std::accumulate?
Нет, в штатных очередях такой возможности нет.
В этом случае отлично работает схема collector-performer. Collector сначала складирует запросы у себе. Поскольку он знает структуру запроса, он знает, по какому критерию проверять сообщения на уникальность (т.е. что в сообщении является уникальным ключом). Плюс, Collector может использовать структуры данных, которые поддерживают возможность удаления элементов по ключу (например, это может быть Boost.Multindex, первым ключом может быть имя картинки, вторым ключом — порядковый номер).

Мы несколько раз использовали такую схему. И одной из задач Collector-а была как раз отбрасывание запросов-дубликатов.
Обычный send в SObjectizer асинхронный — он не блокируется. Сделано это для того, чтобы не заблокировать самого себя, если и получатель, и отправитель работают на одном и том же рабочем контексте. Это запросто возможно, т.к. агенты могут быть привязаны к одному и тому же диспетчеру.

Приостанавливает отправителя синхронный запрос (т.е. request_value вместо send). Но он приостанавливает в любом случае. Даже если нагрузка на получателя находится в разумных пределах.

А вот если send выполняется для message chain с ограничением на размер, то send может быть приостановлен на некоторый тайм-аут. Если в течении этого времени места для сообщения не нашлось, то SO-5 предпринимает одно из следующих действий: бросает исключение (т.е. игнорирует новое сообщение, но с большим треском), тихо выбрасывает новое сообщение, тихо выбрасывает самое старое сообщение или же вызывает std::abort. Так что на message chain можно делать backpressure, но здесь будут свои сложности. В том числе и в случае, когда отправитель и получатель работают на одном и том же контексте.
Это как раз то, о чем речь шла в статье: хороший механизм overload control затачивается под определенные условия. Тут пространство для вариантов весьма большое. Например, можно настроить несколько уведомлений для очереди: когда ее объем превышает 1000, 10000 и 100000 экземпляров. Причем уведомления могут быть как о превышении (т.е. когда объем превышает 10000), так и о падении ниже порога (т.е. когда объем опускается ниже 1000). Можно много чего придумать.

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

Как видно, у стандартных массивов в C++ много проблем. Поэтому в современном C++ стоит использовать std::array: его API схож с std::vector и другими контейнерами и ошибиться при его использовании труднее.
void Foo(std::array<uint8, 16> array)
{
  array.size(); //=> 16
}

Замена голых сишных массивов на std::array — это разумный совет, но вот к такому коду:
void f(std::array<T, N> v) {...}

нужно относиться осторожно, т.к. при вызове f в стек запихивается все содержимое v. Чем больше значение N, тем «тяжелее» окажется вызов f. А если f еще и рекурсивная…

Поэтому при рефакторинге кода вида
void f(const T v[N]){...}

нельзя бездумно менять его на
void f(std::array<T, N> v){...}

может быть лучше сделать так:
void f(const std::array<T, N> & v){...}

или так:
void f(gsl::span<T> v){...}

или через какой-нибудь самописный array_slice:
void f(my::array_slice<T, N> v){...}
Если реализация стандартной библиотеки не кривая, то сделает. Можете погуглить описание std::make_unique (например, тут или тут), или просто загляните заголовочные файлы, которые идут с вашим компилятором.

Пикантность, однако, в том, что авторы статьи предупреждают и об опасностях auto, и об неоднозначностях unique_ptr для массивов, но не могут показать простой пример того, как auto и make_unique не дают стрельнуть в ногу как раз в случае unique_ptr для массивов.
Ребят, если вы уж взялись говорить за современный C++, то хотя бы свои примеры пишите с использованием этого самого современного C++. Например:
auto vec = std::make_unique<char>(size);

вместо
char * vec = new char[size];


Пример с использованием unique_ptr для хранения FILE* выглядит как-то странно. Лямбда зачем-то понадобилась. Почему нельзя было без нее?
unique_ptr<FILE, decltype(&fclose)> file(fopen(...), fclose);

Да и вообще ваши рассуждения о современном C++ наводят на мысль, что для вас «современный» С++ начался в 2011-ом. А до этого был лишь «С с классами». Тогда как рекомендации использовать алгоритмы из STL вместо написанных вручную циклов, начали появляться еще на рубеже 2000-х. А тот же Boost.Ranges, который все это дело сильно упростил, вошел в Boost-1.33 еще в 2005-ом году. Такое ощущение, что никого из вас в свое время Александреску не покусал.
Не уверен, что понял вопрос. Если вы про классы-performer-ы, но наружу они не выставляют ничего похожего на work_started/work_finished. Там скорее что-то вроде start()/shutdown(), внутри которых может быть много задач обработано. А статистику нужно замерять как раз по задачам, которые внутри выполняются. Так что замерять время снаружи performer-ов нет смысла.
Думаю, что даже на вопрос использования акторов в реализации СУБД есть разные взгляды:
http://highscalability.com/blog/2013/11/25/how-to-make-an-infinitely-scalable-relational-database-manag.html
http://hrcak.srce.hr/file/105743

Кроме того, спорить на тему теоретической между разными подходами к concurrent computing не интересно. На практике используются инструменты. Один из них описан здесь. Использовать его для конкретной задачи или нет — это уже выбор конкретной проектной команды.
Параллелизм на асинхронных агентах и параллелизм на мьютексах не связаны между собой отношением «лучше/хуже».

Речь не о том, лучше или хуже. А о «проще» или «сложнее». Чем больше разделяемых данных и чем больше синхронизирующих их объектов (мутексов и условных переменных), тем сложнее все это удерживать в голове, тем больше вероятность ошибки. Асинхронное взаимодействие уменьшает эту сложность. Хотя платить приходится другими вещами.

Информация

В рейтинге
1 943-й
Откуда
Гомель, Гомельская обл., Беларусь
Зарегистрирован
Активность