Pull to refresh
89
0.5
Евгений Охотников @eao197

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

Send message
В итоге для классического подхода считаю нужно выбирать Future, далее объясню что имею ввиду.

Для классического подхода к чему?
Нет гарантии что все акторы инициализировались правильно при старте приложения.

С Akka не работал, поэтому не сильно понимаю, как такое возможно.
Нет гарантий что все акторы передают сообщения тем акторам которым должны и принимаю сообщения от тех от которых должны.

Ну вот это как сильная, так и слабая сторона. Сильная сторона в том, что позволяет писать в стиле fire-and-forget, а так же позволяет легко подменять отправителей/получателей. С другой стороны, все проверки только в run-time. Но, опять же, за счет легкой подмены отправителей/получателей тестировать легко, а ведь тестировать саму обработку сообщения все равно нужно, даже если правильность подписки по типам гарантируется в compile-time.

Кроме того, это не столько проблема самой Actor Model, скорее отдельных ее реализаций. В CAF, например, делают каких-то типизированных акторов, у которых соответствие типов сигнала/слота проверяется в compile-time. Я, правда, не сильно понимаю, насколько это полезно на практике, мы как-то с подобными проблемами не сильно сталкивались. Да и с расширяемостью не очень понятно.

Плюс очень сильно жизнь упрощается, когда у фреймворка есть средства трассировки процесса доставки сообщений.
построение stateful вместо stateless приложений когда на каждую сущность в системе ровно один актор

Об этом речь пойдет во второй части статьи.
Пока, у меня возник, только, один вопрос: а почему агенты обязаны быть объектами — экземплярами классов C++?

Ну тут, скорее, смешение деталей реализации и опыта использования. В агенте должны быть какие-то атрибуты, которые нужны самому фреймворку для обслуживания агента. Удобно это оформлять в виде агента. Кроме того, практика показала, что в реальной жизни агенты очень быстро перерастают маленькие однострочники и могут достигать размеров в сотни, а то и в тысячи строк. Плюс временами активно используется наследование. Т.е. какой-то базовый класс агента вбирает в себя основной функционал, а другие классы-наследники только уточняют его работу. Так что использование здесь объектов и принципов ООП себя, на мой взгляд, оправдывает.

Кроме того, у нас есть такое понятие, как ad-hoc агент, когда агент просто конструируется из лямбда-функций без необходимости определять класс агента и создавать его экземпляр. Вот простой пример. А вот еще один — тут агент ponger реализуется в виде ad-hoc агента с лямбдой-однострочником.
Контракты я использовал в Eiffel-е и, частично, в D. Во-первых, это не совсем про то. Контрактами, например, нельзя показать, что функция чистая. Или что она нерекурсивная. В том же D ввели отдельный атрибут pure, который к контрактам не относится. Во-вторых, что было для меня неожиданностью: Eiffel располагает к использованию контрактов, там это происходит само собой, естественным образом. А в D контракты хоть и были, но нужно было заставлять себя придерживаться дисциплины и описывать их.

Ну и дело вообще-то не в том, чтобы [[implies(std::nothrow)]] появился. А чтобы движение началось :)
В середине 90-х слышал байку, что реализацию STL для MS VC++ писал большой любитель Lisp-а и делал он это на каком-то вполне себе читабельном DSL-е. Из которого уже генерировалось вот этот вот все непотребство с нечитабельными именами. Причем нечитабельные имена были выбраны специально. Вроде как даже несколько причин было для этого: начиная от того, что первые реализации STL должны были работать даже на компиляторах без поддержки namespaces, и заканчивая тем, чтобы не возникало конфликтов имен у пользователей, если кто-то отнаследуется от std::vector и захочет добавить парочку своих атрибутов в класс-наследник.

Понятно, что байка. Но когда заглядываешь в исходники некоторых реализаций STL в нее начинаешь верить :)

Тем не менее, об STL-е нужно судить не по коду самого STL (там вполне ожидаемо буде хардкор, непонятный половине действующих C++ников), а по коду, который использует STL.
Есть вот такая смутная хотелка. Пока что сумбур и, очевидно, что мне этого просто не поднять. Но вроде как подобные вещи интересны и нужны не только мне. Так что может кто-нибудь заинтересуется и загорится. Ну или просто подтвердит, что направление перспективное.
По поводу плагинов в виде .dll/.so: приходилось использовать и очень активно. Вначале использовались свои обертки вокруг «родного» API OS, затем перешли на работу посредством ACE.
Зависит от того, как человек представляется (или как его представляют). Если представляют по имени-отчеству, то обращаюсь на «Вы», до тех пор, пока он сам не предложит перейти на «ты».
Если ко мне обращаются на «Вы», а мне с этим человеком приходится много общаться, то сам предлагаю перейти на «ты», чтобы было проще. Если только он не старше меня лет на 15-20, тогда самому как-то не комфортно «тыкать» уже немолодому человеку.
Если они не инлайнятся — значит, компилятор считает, что так будет быстрее и вполне возможно, что он прав.
Или в принципе не может это сделать, т.к. они лежат в отдельной dll-е.
Какая разница, как это записать?
Ну если вы не увидели разницы между приведенными двумя примерами кода, значит ее нет, а я во всем неправ.
Не так все просто. Во-первых, в том же C++ деструкторы не всегда инлайнятся. Т.е. если где-то в коде написано что-то вроде:
void f() {
  File a(...);
  ...
  File b(...);
  ...
} // (1)

а в каждом деструкторе делается вызов close(), то код с деструкторами будет чуть-чуть дороже, чем код с прямым вызовом close():
void f() {
  int a = open(...);
  ...
  int b = open(...);
  ...
  close(b);
  close(a);
}

Во-вторых, запись действий по очистке вручную может позволить записать действия более компактно. Т.е. если в коде на C++ между вызовами деструкторов a и b пройдет вызов еще нескольких деструкторов, то данные и код для вызова деструктора b уже могут уйти из кэша. Тогда как в C может быть записано что-то вроде:
void f() {
  int a, b;
  ...
  a = open(...);
  ...
  b = open(...);
  ...
cleanup:
  ... // Какие-то другие действия.
  close(a);
  close(b);
}

Так что на практике выплывают некоторые мелочи из-за которых незначительное преимущества в скорости у C все-таки образуются.

Другое дело — стоят ли они того…
А можно пояснить, как в случае №5 код соотносится с описанием «проблемы»? В конструкторе G4PhysicsModelCatalog создается статический объект типа modelCatalog, указатель на который сохраняется в статическом же члене класса. Такое впечатление, что разработчики хотели достичь того, чтобы экземпляр modelCatalog создавался бы только при создании первого экземпляра G4PhysicsModelCatalog. Если же G4PhysicsModelCatalog нет, то нет и экземпляра modelCatalog.
Зачем вы увиливаете от ответа?

Ответ вам был дан. Почему вы не можете его прочитать и понять?
Я вас спрашиваю, какая реализация сортировки в общем случае используется в С?

Тогда позвольте вас спросить: мы будем под общий случай выдавать конкретную ситуацию? Тогда давайте посмотрим на такой вот «общий» случай:
void f(char * b, size_t i) { b[i] = 0; }

Значения b и i определяются в run-time, заранее они не известны.

Ну и, видимо, нужно более четко обозначить свою точку зрения, ибо очевидно, что для местных комментаторов она не очевидна:

— в Rust-е не так уж много особенностей, которые бы не позволили компилятору Rust-а сгенерировать такой же эффективный код, как и компилятору C. Одна особенность — это включенный по умолчанию bounds checking, вторая — это генерация таблиц для обработки panic (хотя это лишь косвенное влияние оказывает). А так, в принципе, у компилятора Rust-а достаточно информации, чтобы сгенерировать даже более оптимальный код;
— однако, производительность кода будет определяться не столько возможностями компилятора, сколько программистом. Rust является языком более высокого уровня, позволяет решать более сложные задачи, что дает возможность разработчику использовать более высокие уровни абстракции. Чем выше, тем меньше внимания уделяется тому, что внизу, откуда и проявляется некоторый проигрыш в производительности/ресурсоемкости по сравнению с более низкоуровневыми языками. Это не уникально для Rust-а, это уже проходилось, на моей памяти, как минимум с Ada, Eiffel и C++ (если говорить про то, что транслируется в нативный код);
— под общий случай лучше брать не какой-то микробенчмарк и уж тем более не часть микробенчмарка (вроде упомянутой вами сортировки), а решение какой-то большой задачи. Например, реализация MQ-шного брокера (хоть MQTT-брокера, хоть AMQP) или сервера СУБД. На таком объеме языки более высокого уровня сильно выигрывают в трудозатратах, качестве и надежности, но на какие-то проценты проигрывают в производительности тому же C (причины см. в предыдущем пункте). Если не верите, попробуйте пообщаться с разработчиками PostgreSQL или Tarantool-а.
Вопрос по коду примеров. А почему вы создаете объект Callback через new:
  std::unique_ptr<Callback> callback_ptr{new Callback()};
  storage->foreach (qp, callback_ptr.get());
  callback_ptr->wait();

В этом есть какой-то тайный смысл? Или это можно переписать так:
Callback callback;
storage->foreach(qp, &callback);
callback.wait();
Тут уже привели минимум один пример: если из возраста 100 вычесть возраст 120 (оба валидны), получится отрицательное число, которое возрастом не является.

ЕМНИП, вывалится исключение в run-time. Т.е. когда в Ada описывается тип-диапазон, то при работе с экземплярами этого типа в run-time добавляются необходимые проверки, а в compile-time, там где компилятор видит константы, он может выдать предупреждение.
Подозреваю, что это намек на то, что обобщенные реализации sort-а быстрее qsort с косвенными вызовами.

Повторю то, что уже говорил:
При этом в Rust-е вполне можно, если задаться целью, получить и более быстрый код, чем на C (в C++ это так же возможно и продемонстрировано), но речь будет идти не про общий случай, а про частную задачу, вероятно, не очень большого объема.

Какой будет следующий намек?
Давайте вернемся к истокам спора. Началось все с фразы:
rust в общем случае не должен уступать по скорости C

На мой взгляд, это утверждение неверно. Т.к. в общем случае (а не тогда, когда какой-то код затачивается под максимальную производительность в микробенчмарке посредством ухода в unsafe) пользователи будут пользоваться преимуществами Rust-а, как то:
— встроенные проверки выхода за пределы векторов;
— более высокие уровни абстракции, т.е. trait-ы, которые в ряде случаев будут работать так же, как и виртуальные методы в обычных ОО-языках;
— RAII через Drop, в том числе и при паниках;
— возврат значений (в том числе и не маленьких) через Result, вместо использования привычных для C-шников параметров-указателей.

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

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

При этом в Rust-е вполне можно, если задаться целью, получить и более быстрый код, чем на C (в C++ это так же возможно и продемонстрировано), но речь будет идти не про общий случай, а про частную задачу, вероятно, не очень большого объема.

Ну и да, речь не про фундаментальные преимущества, которые _могут_, а про постылую обыденность, в которой идеальный быстрый код пишут словами в камментах на профильных форумах.
Я веду к тому, что Rust, в отличие от C++, может грамотно распорядиться своей системой типов и инвариантами, которые из нее можно вывести.

А может и не распорядиться и работать с трайтами, как с таблицей виртуальных функций, а так же выполнять все bound checks и возвращать через Result пользовательские enum-ы размером в сотни байт, при этом ничуть не избавляясь от цепочки if-ов, которые спрятаны за синтаксическим сахаром try! и?..
А разве кто-то против этого спорит? Иметь гарантии безопасности от Rust-а при разнице в скорости в районе процента-двух — это просто замечательно.
Вот как раз верить во что-то, без каких либо оснований, это и есть настоящий фанатизм.

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

Теперь те же самые споры будут на счет C и Rust. С ожидаемым результатом.
Странно, у меня почему-то было ощущение (явно с чьих-то слов) о том, что в Result Err может быть именно что трейтом, а не конкретной структурой.

Возможно, ошибаюсь.
Общий случай — это когда используются самые распространенные практики и приемы. Переход в unsafe в Rust-е для выжимания производительности вряд ли можно считать распространенной практикой.

Information

Rating
1,746-th
Location
Гомель, Гомельская обл., Беларусь
Registered
Activity