All streams
Search
Write a publication
Pull to refresh
82
0
Евгений Охотников @eao197

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

Send message
Вопрос по коду примеров. А почему вы создаете объект 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-е для выжимания производительности вряд ли можно считать распространенной практикой.
То мы говорили о скорости исполнения кода, а теперь уже о размере.

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

Ну вот в реальности C++, например, хоть чуть-чуть, но медленее C. Как раз потому, что абстракции на практике не бесплатны (чтобы там не говорили евангелисты). Нравится верить, что в Rust-е, не смотря на более высокий уровень абстракции, будет что-то по-другому, ну OK, нет проблем.
Интересное определение общего случая. Тогда понятно, почему у вас в общем случае Rust имеет производительность C.
Вы похоже не понимаете, что такое zero cost abstraction.

Да куда уж мне.
Вы говорите про общий случай, но упоминаете почему-то частности.

А вы подумайте, как эти частности скажутся в сумме.
Опять мимо кассы.

У табличного способа поиска исключений есть своя цена, даже если исключения не бросаются. Хотя бы в необходимости хранения этих самых таблиц.
> Так что мы всегда увидим или конкретный тип или Box<Error>

И какой тип получается вот в этом примере из стандартной документации? Неужели Box<Error>?
Тогда забываем «в общем случае». В общем случае в Rust-е не будут использоваться unsafe-блоки и mem::forget.
Там, где не может компилятор, но очень хочется, то можно сделать руками через get_unchecked.

Это будет не общий случай.
что в общем случае код на расте будет безопаснее

Это подразумевается по умолчанию. Если намеренно игнорировать безопасность Rust-а, смысла в его использовании нет.
как в С++ нулевой стоимости

Абстракции в C++ далеко не нулевой стоимости.
Пары значений ни при каком раскладе к динамически создаваемому объекту не приведут.

Я этого и не утверждал. Только вот тот, кто вызывает метод f() не может знать, положит ли метод f() в результат простой Err или же это будет созданный динамически объект, который реализует нужный трайт.
Дропы тоже будут использоваться только там где они реально нужны.

Наличие паник подразумевает, что должен быть какой-то механизм автоматического раскручивания Drop-ов при выбросе паники по аналогии с плюсовыми деструкторами и исключениями. Это не бесплатно, даже если паники не бросаются.
Если уж сравнивать числодробительные возможности, то тогда надо смотреть на бенчмарки.

Речь шла про общий случай, а не про числодробилки.
rust в общем случае не должен уступать по скорости C

Это почему это? В Rust используется bound checking при обращении к массивам по индексам, тогда как в C — нет. В ряде случаев Rust-овый компилятор способен избавиться от таких проверок (например, при итерациях), но не всегда.

Плюс в Rust-е есть Drop-ы, которые аналоги плюсовых деструкторов, и Drop-ы должны вызываться при выходе из скоупа. Что так же не бесплатно.

Плюс в Rust-е практикуется возврат Result-ов, т.е. пар значений. И в Result-е запросто может оказаться динамически созданный объект на месте Err. Что так же не дешево.

Плюс в Rust-е иногда может применяться динамический диспатчинг вызовов методов трайтов, косвенный вызов дороже прямого.

Скорее в общем случае Rust должен хоть немного, но отставать от C.
На эту тему было много разговоров в Интернетах (например). Конкретно разыменование нулевого указателя для получения ссылки — это UB. Поскольку результирующая ссылка будет содержать не пойми что, то и операция взятия адреса от нее так же может давать не пойми что. Пока это все работает (т.к., по сути ссылка и указатель на низком уровне это одно и то же), но когда оптимизаторы в компиляторах станут еще умнее, то всякое может быть.

Статическая переменная внутри inline-функции в header-only библиотеках, емнип, стабильно работает пока речь идет о статической линковке всего кода в один исполняемый файл. Если же мы начинаем работать с dll/so, то в каждой из них может оказаться свой экземпляр статической переменной.
Разыменовать нулевой указатель дабы получить гарантированно несуществующую ссылку, а потом взять адрес от этой ссылки и сравнить его с чем-то… UB попахивает и не удивлюсь, если в каком-то из компиляторов такое не сработает рано или поздно. Может безопаснее было сделать обертку вокруг std::error_code*?
class optional_error_code {
  std::error_code * v_;
  optional_error_code() : v_{nullptr} {}
public:
  optional_error_code(std::error_code & ec) : v_{&ec} {}

  static optional_error_code make_null() { return optional_error_code{}; }

  operator bool() const { return v_ != nullptr; }

  std::error_code & operator*() { return *v_; }
}

template<typename Category, typename Exception = av::Exception>
void throws_if(optional_error_code opt_ec, int errcode, const Category &cat)
{
    if (opt_ec)
        *opt_ec = std::error_code(errcode, cat);
    else
        throw Exception(std::error_code(errcode, cat));
}

void some_your_api_proc(..., optional_error_code opt_ec = optional_error_code::make_null())
{
  auto sts = some_ext_api();
  if (sts < 0) {
    throws_if(opt_ec, sts, some_ext_api_category());
    return;
  }
}
Никогда не доводилось применять MPI, так что у меня только поверхносные впечатления об этом инструменте. Но простые очереди сообщений использовать приходилось.

На мой взгляд, Модель Акторов — это следующий логический шаг в степени использования message-passing. Т.е. сперва вы решаете, что вам нужны независимые потоки управления, у каждого из которых свои собственные данные. И для общения между потоками вам нужен обмен сообщениями.

Затем вы понимаете, что у ваших потоков появляется какое-то сложное поведение, которое зависит от того, какие сообщения поток получает. Потом вы обнаруживаете, что можно как бы разделить логические потоки и физические. И что логических потоков вам нужно больше, чем физических. Вам теперь нужно отобразить N логических потоков на M физических.

И вот эти самые логические потоки оказываются вполне себе акторами. Которые могут выглядеть по разному. Как об'екты или как сопрограммы. Просто к вам в помощь появляется еще и некоторый шедулер, который акторами управляет.

Ну и еще замечу, что MPI предназначен для решения проблем parallel computing, тогда как акторы — для concurrent computing. Специфика немного разная.

Information

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