Pull to refresh
-4
@dllmainread⁠-⁠only

User

Send message

Ну что можно сказать. Фиксируем. Персонаж действует по обозначенной мной схеме - надеется что мне просто надоест отвечать.

Опять пошли заходы про "счастливая случайность", хотя я уже отвечал на это и оказалось вероятность "счастья" - 100%. Никакого ответа на это не последовало и не последует. Также фиксируем.

Про "нагуглил" - это очевидный факт, потому как зная хотя бы базовые принципы организации памяти рассуждать вот таким образом:

Собственно в этом и вся соль задачи, без векторизации strlen это все бесполезно, а сделать ее автоматически довольно сложно, так как нет гарантии за выход за границы null terminated string, если шагать по размерности simd регистра.

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

Что значит договоримся? Вы рассказывали про "падает" - показать не смогли, оказалось ничего и никогда не падает. Теперь начали рассказывать про какой-то стандарт и в целом пытаться съехать с темы.

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

В общем, мне эти споры на "кому раньше надоест" мало интересны. Рассказываете про "падает" - показывете. Вы это говорили прямым текстом, эксперт из "тви" также это заявлял. До тех пор пока нет падения - в сотый раз слушать "это же ub, как ты можешь отрицать!!!" - это всё мимо.

Изучите хотя бы базовые принципы работы памяти. Там про страницы и прочее(что вы успели нагуглить чуть ранее). Далее уже будете рассуждать.

Ну и да, ответ за glibc strlen и прочее - где он? Почему проигнорировали? Это другое? )

У вас индексы вылетают за пределы массива, это все не падает только по счастливой случайности, так не влезает на границу страницы.

Ога.

не влезает на границу страницы.

Суть. И да - "случайно" - покажете мне падение?

Да, и код ваш мягко говоря "переусложнен"

Это оправдания. Ваш не переусложнённый код выдаёт выдаёт перф - внимание - 5 Gib/s. Это сильно.

потому что strlen из стандартной библиотеки раскочегарен по полной.

Потому что написан знающими людьми, а не вчерашними студентами, которые узнали вчера про -fsanitize из гугла.

Я вам уже выше отвечал на ваши заблуждения про "векторизация"/"strlen"/"автоматически"/"команда sse" и прочее.

А так, вопросы для вас ниже. Расскажете мне про чтение за '\0' и что мне помешает это делать.

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

А по поводу ub, открою секрет, но ничего не мешает читать далее '\0'. Поэтому данный эксперт идёт сначала смотреть код strlen/memchr/прочих функций из glibc, а далее начинает отрывать себе руки и всё остальное(ну это его желание, что поделать).

В общем, условия просты - для обоснования ub эксперту нужно сообщить что мне помешает читать память далее '\0'. А также что мешает делать тоже самое glibc. Всё просто.

Ах да, я как-то даже не сомневался. Если кому-то интересно, мотивация данного эксперта:

  1. https://github.com/Nekrolm/aoc2022

  2. https://github.com/Nekrolm/grpc_cpp_async_examples

Это очередной адепт того самого языка программирования(который убийца c/++, да). Ладно, я не буду рассказывать здесь, что это за адепты и в чём их суть, а то меня забанят за пару минут.

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

Во вторых, я отвечал на вот это:

Автоматически? Нет даже банальный "while (*it != '\0') ++it;" не векторизуется.

Я указал на то, что это векторизуется "автоматически", будь оно не так топорно записано. О причинах не-векторизации я также сообщил. Команда sse никакого отношения к делу не имеет и просто является частным случаем, не более.

Ответил на подобный вопрос чуть ниже. В целом, да - нужно дать компилятору/железу возможность генерировать/исполнять параллельный код. if/return/ещё какие-то ветвления - в общем случае этому препятствуют.

Идея в повышении уровня параллельности кода насколько это возможно. "скалярный"(с уровнем параллельности ~0) код всегда будет медленным. И компилятор ничего с этим сделать не сможет. Вот нормальный код ни компилятору, ни железу не мешает. Не имеет лишних зависимостей по данным/интсрукциям.

128 или нет - это не важно. Если проще - нужно более одного. Даже сильно более. На самом деле я там забыл вернуть обратно на 64 и именно со 128 там будет переполнение на "sssss...". Но сути это не меняет.

Никакие pgo(как и "кэши") здесь не помогут.

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

Оно хорошо ложится на любой камень с возможностью параллельного исполнения.

По большому счёту да, автоматически. Нужно только немного помочь компилятору. while(*it) - здесь сказано "до нуля и не далее". Там нечего параллелить.

Префы здесь ничем не помогут. Пока есть return в цикле уровень параллельности околонулевой.

Читабельность здесь мимо по большому счёту, поскольку целью заявлялось "быстрее". А так, это борьба с компилятором(и отчасти с C). clang нормально работает на таком:

  while (1) {
    signed char step_r = 0;
    unsigned char zero = 0;
    for (auto n = 0; n != step_size; ++n) {
      zero |= !i[n];
      step_r += proc(i[n]);
    }
    if (zero) {
      while (*i) r += proc(*i++);
      break;
    }
    r += step_r;
    i += step_size;
  }

Но gcc не смогает и там всего 20Gib/s. Поэтому приходится хаками "расслаблять" некоторые правила языка.

return в цикле очевидно всё сломает и выдаст пару Gib/s, как в начале статьи.

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

#define step_size 128
#define auto __auto_type
#define proc(v) ({ auto _v = (v); (_v == 's') - (_v == 'p'); })
#define aligned(ptr, align) ({ \
  auto _ptr = (ptr); \
  auto _align = (align); \
  auto _r = (long long)_ptr & _align - 1; \
  _r ? _ptr + (_align - _r) : _ptr; \
})

int run_switches(const unsigned char* i) {
  auto r = 0;
  for (auto head_end = aligned(i, step_size); i != head_end && *i; ++i) r += proc(*i);
  while (1) {
    signed char step_r = 0;
    unsigned char rs[step_size];
    for (auto n = 0; n != sizeof(rs); ++n) {
      rs[n] = i[n] ? 0 : ~0;
      step_r += proc(i[n]);
    }
    long long* _ = rs;
    if (_[0] | _[1] | _[2] | _[3] | _[4] | _[5] | _[6] | _[7] | _[8] | _[9] | _[10] | _[11] | _[12] | _[13] | _[14] | _[15]) {
      while (*i) r += proc(*i++);
      break;
    }
    r += step_r;
    i += sizeof(rs);
  }
  return r;
}

Собирать так: gcc -std=gnu2x -Ofast -march=native -fwhole-program -obench bench.c. Код автора с to_add выдаёт на моём мк(целерон какой-то) ~4.5 Gib/s. Моя версия выдаёт 26.6 Gib/s. Это овер 5 раз. Заморачиваться на каких-то крополях мне лень.

А вот safe rust - даёт.

Повторяемся. Я уже показал эквиваленты в C++, предоставляющие те же гарантии.

Чужие безопасные абстракции, написанные на unsafe rust - это то самое свойство Раста (быстрые безопасные абстракции). В предположении, что unsafe часть написана корректно, остальной код уже не может привести к широкому классу ошибок. Safe rust поверх стандартной библиотеки, кажется, концептуально не отличается от питона: где-то внизу есть "опасный" код, но любая программа, которая компилируется не может содержать, например, двойного освобождения памяти.

Вы опять повторяетесь. Это везде так. И ещё, по поводу double-free: http://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=rust

  • An issue was discovered in the algorithmica crate through 2021-03-07 for Rust. There is a double free in merge_sort::merge().

  • In the standard library in Rust before 1.52.0, a double free can occur in the Vec::from_iter function if freeing the element panics.

  • An issue was discovered in the id-map crate through 2021-02-26 for Rust. A double free can occur in remove_set upon a panic in a Drop impl.

И так далее. Поэтому не только может, но и содержит. В том числе в std.

Подождите, если я делаю std:mem:swap, то выражается ровно тот swap, который нужен. И выражается он в safe rust (потому что эта функция стандартной библиотеки помечена безопасной).

Нет. Выразить - значит реализовать в данном случае. Использовать проблем никаких, это понятно. Но реализуется на safe Rust только неэффективный аналог, поэтому снова unsafe.

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

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

Кажется, мы в этом месте ходим кругами.

Не кажется - точно. Об этом я так же говорил. Но да ладно - переписывать третий раз лень.

Можем, как нет. Даже если бы .value() не было, обёртка пишется в одну строку.

Вопрос уровня гарантий. Для соблюдения гарантий safe Rust, весь unsafe код должен быть написан аккуратно.

Как было показано выше, Rust не даёт более сильных гарантий относительно других ЯП. C++ предоставляет те же гарантии - optional::value, vector::at, unique_ptr(raw pointer в safe rust использоваться не могут), нетривиальные лайфтаймы в обоих случаях руками пишутся. Более явное разделение - да, какой-то иной уровень - нет.

Например, safe Rust заприщает алиасинг (т.е. после завершения unsafe кода не должно получаться две мутабельные ссылки на одну область памяти).

Это проблема, я уже говорил о ней. Нужны потоки - опять пишем unsafe(или берём библиотеку где оно уже написано). Та же история с двусвязным списком, деревьями с дополнительными связями(ссылка на родителя - простейший пример), мультииндексами и прочими подобными данными.

В safe rust эта операция элементарно выражается: std:mem:swap(...).
Собственно, пример безопасной абстракции, написанной на опасном rust. Она один раз написана, теперь ей можно пользоваться.

В safe Rust выражается не swap, а вызов unsafe кода. Опять же, оно везде так. Точнее, на Rust выражается даже swap, но в варианте с временной копией объекта - большие объекты так свопнуть не получится(места на стеке не хватит).

То, что безопасные абстракции там на низком уровне написаны на unsafe - не удивительно. Для питона или явы низкоуровневая реализация тоже есть, что не отменяет гарантии языка по памяти.

Ну да, высокоуровневая реализация в части случаев невозможна в принципе, а в другой части неэффективна. О чём и речь.

Довольно часто достаточно пользоваться safe-абстракциями. Например, от stdlib. И даже в stdlib подавляющая часть кода - safe.

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

Вот так делать нехорошо - возникает проблема с границей unsafe: написав unsafe вы гарантируете инвариант.

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

К сожалению, формального описания "какие же инварианты на самом деле нужно поддерживать, выходя из safe", нет.

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

Не просто безопасные, а безопасные и дешевые.

Это так же не идея Rust. И даже не единственная реализация. И даже не первая - всё это уже есть в том же C++.

https://benchmarksgame-team.pages.debian.net/benchmarksgame/performance/spectralnorm.html - игрушечная задача, в который лучший код на раст так же хорош, как и лучший на c++ . При этом не содержит ни одной unsafe строчки.

С benchmarksgame интересная история. Там кто-то засылал код на C++, который оказывался быстрее всего остального, но его удалили, поменяв требования. Насчёт unsafe - оно там есть, конечно же - в библиотечном коде. Без unsafe на Rust писать всё же нельзя.

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

Не дочитал вопрос. Я привёл примерный аналог тому swap на Rust. restrict там не нужен не потому, что это будет работать и с перекрывающимися областями памяти, а потому как компилятор сам всё поймёт. Очевидно, если области перекрываются нужно проверять.

Я не для этого писал про unsafe в swap. Я говорил о невозможности выразить на safe Rust элементарную операцию.

Программа на safe rust во многих случая работает on par с аналогичной программой на C/C++.

За счёт unsafe - да, сравнимый уровень получается. Но о безопасности/надёжности ПО на Rust в таком случае речи идти не может.

Нет, не нужны никакие копирования, зачем?

Да, я неверно сказал.

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

Да, небесплатная проверка границ. Выкинет оно только в простых случаях(примерно в тех же самых, в которых в C++ получим предупреждение о выходе за границу). Хотим убрать тормоза - пишем unsafe.

Если вы в рамках unsafe Rust сделали две мутабельные ссылки на одну и ту же память и вернули их в safe - комилятор дальше уже ничего не гарантирует, к сожалению.

Вот эта ситуация, да. Снова всё нужно руками проверять. Как и с optional без предварительного if.

При переписывании функции с входного типа &T на Optional<T>, например, можно случайно оставить звёздочку. А в расте нельзя - не скомпилируется.

Всё равно не пройдёт. * от T(или T& - неважно) даст другой тип(условно, U&, U != T), * на optional<T> даст T&.

это не что-то, что изобрёл Раст, это подход, который существует давно, но конкретно в нём позволяет на (практически) одном и том же языке писать что safe, что unsafe код, и при этом safe подмножество не несёт за собой оверхеда, характерного для управляемых языков.

Формулировка более правдивая, но напомню исходный тезис из статьи: "надёжное и эффективное программное обеспечение". Управляемые языки с оверхэдом такого создавать не позволяют. То, что Rust эффективнее чем что-либо неэффективное не делает его эффективным в глобальном контексте.

Доказывался другой тезис, в именно то, что производительная низкоуровневая реализация (swap_nonoverlapping_one реализован, в общем, подобно тому, что вы отправили) не мешает быть точно такому же эффективному безопасному интерфейсу.

Это вывод на основе частного случая(одного из самых простых), и он неверен в общем. Как я уже говорил, здесь просто не может быть оверхэда.

Если речь об std::swap, то почему вы в этом и предыдущем сообщении говорите о том, что кто-то будет вызывать его из unsafe, если это safe функция?

Этим вы сводите на нет всю концепцию безопасности Rust. Взять тот же C++ - зачем кому-то нужно, допустим, разыменовывать nullptr, неправильно алиасить типы или обращаться к уже уничтоженному объекту? Или, в более общей форме, зачем кому-то совершать ошибку в коде? Ответы на данный вопрос как правило имеют форму "сделали непреднамеренно/по невнимательности, а язык это позволяет". Так же и здесь - та же невнимательность/"завтрашний день" и т. п. И язык здесь точно так же позволяет сделать ошибку. Даже unsafe лишний раз писать не всегда нужно будет - оно уже есть в коде, и в довольно большом количестве.

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

Я мысль понял. В точности та же ситуация с C++. Условно говоря, .value() - это safe, operator* - unsafe. Никаких уникальных свойств Rust здесь нет.

Да, если у вас реально есть какая-то причина полагать, что разыменования возможно без проверки, то вы можете положиться сделать его без проверки, но тогда будьте добры взять ответственность на себя, сделав это через уже unsafe функцию (насколько я помню, таковая реализована даже как отдельный крейт), потому что тут вы берёте на себя ответственность за то, что значение есть.

Именно так. На других ЯП пишется "небезопасно" не потому, что там нет "безопасных" средств, а по разным другим причинам.

Information

Rating
Does not participate
Registered
Activity