Как стать автором
Обновить
-17
@twinklederead⁠-⁠only

Пользователь

Отправить сообщение
На cppreference есть вполне адекватные примеры.
есть, есть std::variant, которым пользуется примерно никто

Это ничего не значит, да и попросту враньё. На это я могу ответить: есть rust|haskell, которыми пользуется примерно никто.

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

Ну ваши претензии не работают в рамках логики автора, да и в моей тоже.

А можно пример?

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

Было бы здорово увидеть пример.

chromium.googlesource.com/chromium/src/+/lkgr/docs/jumbo.md

Я не то чтобы спец, но предположим у вас в реализации функции не какая-нибудь банальщина, а инстанцирование тяжелых линейных солверов из Eigen, приправленных expression templates. В этом случае раздельная компиляция просто жизненно необходима. И это не то чтобы прям редкий случай, заметная доля проектов с С++ это числодробилки, в которых постоянно возникает подобная ситуация.

Вам это никак не поможет. Это поможет только в том случае, если вы обернёте шаблонное api в c-api и уже с ним будете работать. Но будет уже не С++. Если же ваша логика будет так же шаблонной и так же использовать какую-то логику из библиотеки в cpp-файле, то это ничего не даст.

В современном С++-коде код не разбивается(в силу нежелания этого делать и в силу невозможности этого сделать). Существует тысячи ho-библиотек и тот же stdlib так же является ho-библиотекой.

Таким образом в каждом вашем cpp-файле будет 1% вашего кода и 99% заинклюженого. Таким образом компилятор будет делать лишнюю работу, собирая тысячи раз одно и те же инклюды.

Просто для понимания сделайте подобный бенчмарк:

$ cat a.cpp 
#include <iostream>

void a() {
  std::cout << __PRETTY_FUNCTION__ << std::endl;
}
$ cat b.cpp 
#include <iostream>

void b() {
  std::cout << __PRETTY_FUNCTION__ << std::endl;
}
$ cat c.cpp 
#include <iostream>

void c() {
  std::cout << __PRETTY_FUNCTION__ << std::endl;
}
$ cat main.cpp 
#ifdef HO
#include "a.cpp"
#include "b.cpp"
#include "c.cpp"
#else
void a(); void b(); void c(); 
#endif

int main() {
    a(); b(); c();
}

$ time g++ a.cpp b.cpp c.cpp main.cpp 

real    0m0,466s
user    0m0,398s
sys     0m0,068s

$ time g++  main.cpp -DHO

real    0m0,178s
user    0m0,152s
sys     0m0,026s


$ time clang++  main.cpp -DHO 

real    0m0,684s
user    0m0,589s
sys     0m0,046s

$ time clang++ a.cpp b.cpp c.cpp main.cpp

real    0m1,831s
user    0m1,721s
sys     0m0,100s


И чем сложнее будут инклюды — тем хуже ситуация будет с раздельной компиляцией. Оно будет почти всегда медленнее. И единственное для чего может потребоваться раздельная компиляция — это параллельная сборка большой кодовой базы. Но это явно не тот случай. Да и в том случае нужно будет модульное разделение, а не разделение по-файлам.
Необходимости конечно нет, но разбиение на реализацию/объявление заметно улучшает читаемость, и ускоряет компиляцию.

А чего же их не разделяют в rust? Да и много где ещё. Подобные рассуждения не работают, вообще. Вы либо последовательно применяйте это ко всем языкам, либо не применяете вообще.
заметно улучшает читаемость

Это догмат. Лично для меня это ничего и никак не улучшает, как и для пользователей тысяч других языков, где нет этого разделение. К тому же не каждый С++-код можно разделить, а современный С++ разделить вообще почти нельзя.

и ускоряет компиляцию.

Это попросту глупость разоблачённая уже давно. Как-то ускорение есть лишь в очень редких случаях, либо на очень большой кодовой базе. Вернее даже не ускорение, а просто возможность распараллелить сборку.

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

Повторю, они игнорируются в «несколько тысяч строк на rust» и проблем не вызывают, как и в тысячах других языков.

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

Ой, оказывается те другие попросту использовали «сопоставлять шаблон» и это было плохо. Но это ведь была проблема С++, а теперь оказывается rust-код отказывается от pm в пользу визиторов, которое в С++ попросту нативны(в отличии от rust, где это делается через тысячи костылей и боейлерплейта).

Каждая новая строчка пробивает очередной дно.
Затем сравнил с компилятором на C++, и там вполне ожидаемо компилятор был примерно на 30% больше

Что это за ангажированные намёки ничем не обоснованные? Это сравнение, либо пропаганда?

Rust (основа для сравнения)

На каком основании это основа, а не это:

Rust (другая группа): трёхкратный размер из-за иного дизайна!


Почему эта разница в одном случае описывается дизайном, а в другом непонятным набором слов?

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

А раньше же было «ожидаемо», а теперь «не копался», «не знаю»? Зачем что-то утверждать, если при этом ничего не знать?

Отсутствие типов sum и сопоставлений с образцом в C++, которые мы широко использовали и которые были очень полезны.

Никакое «сопоставлений с образцом» не является чем-то необходимым и в 95% случаев сливает более мощной концепции визиторов. В С++ есть исключения и ненужно матчить каждую функцию. К тому же, с чего вдруг в С++ нету сумтипов?

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

Необходимость дублировать все сигнатуры в заголовочных файлах, чего нет в Rust.

То, что это попросту обман я уже писал. Необходимость что-то там дублировать обусловлена разделением на h/cpp-файлы. Никак не обусловлена С++ и это разделение может не использоваться.

с оговоркой, что они помещают реализации множества небольших функций в заголовочные файлы

Какая разница какие функции? К тому же, тут явно видно взаимные противоречия.

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

Это просто глупость несусветная. Это мифология, которую была опровергнута уже тысячи раз. Особенно хромом. Уплотнение кода способствует ускорению компиляции т.к. инклюды в С++ тяжелые и компилятор делает ненужную работу каждый раз обрабатывая один и тот же код в каждом cpp-файле.

К тому же, уровень владения языком представителей С++-лагеря крайне скудны(судя по тексту). В С++ непросто так появилось ho и причины тому просты — шаблонный код попросту нельзя разделить. Попытки были и все они не имели успеха.

Таким образом из этого можно сделать вывод, что там не было никакого С++, а был типичный студенческий С/С++ начального уровня.

8733 строк

Никак не могут компилироваться секунды. К тому же сравнение скорости компиляции на разном железе меня так же удивляют. А так же меня удивляет отсутствие информации об компиляторе и какой-то конкретики в целом.

Опять же. Кода мы не видим. Никаких измерений нет. Есть «одна бабка сказала» и какие-то ничем не обоснованные заявления. Прослеживается явная ничем не обоснованная ангажированность и двойные стандарты.

Необходимость дублировать все сигнатуры в заголовочных файлах, чего нет в Rust.

Это неверно. Такой необходимости нет.
C++ имеет возможность переопределения операторов, а у ребят из комитета есть жгучее желание эту возможность использовать

И в чём же проблема? Какая разница как это сделано? Оператором или каким-нибудь кейвордом/конструкциями?
в стиле «ыыы, смотрите как можно».

В стиле «ыыы, мы можем использовать возможности языка и делать нормальные api»? Как это глупо, как это странно.

Лично я бы допилил uniform function call syntax

Это такая же перегрузка не очевидная. В чём разница? Да и что будем делать с коллизиями имён?

без извращенств с операторами

В чём «извращенств» заключается?

хочу видеть так:

У этого подхода все аналогичные проблемы(хотя я наличия проблем не вижу ни там, ни там). Допустим, если я хочу сохранить операцию — можно сделать auto op = filter(is_even); Если же я захочу сделать это с функций — мне нужно описывать функцию с достаточно не очевидным параметром. Какой концепт мы напишем у op? Какой он у filter? Если мы просто напишем auto — мы получим плохую ошибку. Нам нужно будет писать какой-нибудь кастомный bind.

Даже как memcpy в заmmapленный файл.

Трупут memcpy десятки гагабайт на ведро. Трупут современной файловой подсистемы не далеко от этого ушел. 99% программ даже сотен мегабайт трейса не генерируют. Такое кол-во программ даже близко не являются оптимальными, причём эта неоптимальность не исчисляется процентами, а исчисляется разами.

И что самое интересное, эти рассуждения вообще не имеют смысла. Если для отладки чего-то необходим даже мегабайт трейса — отладить это вручную попросту невозможно. Здесь нет какого-либо выбора «трейс или не трейс» и какие-то претензии к трейсу попросту нестоятельны.

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

Это не моя мотивация и я ничего не знаю об этом. Для меня так же все минусы -O0 перекрывают плюсы.

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

Смотря как написать трейс.

Ошибки выражения логики на языке лучше отлаживать asan'ом, tsan'ом и тому подобным. Ошибки самой логики — тесты там всякие.

Трейс является источником данных. Интерпретировать их может не только человек, а интерпретация человек уже по-сути тестирование.

Не хватает в сравнении хоть каких-то замеров по скорости работы

Замерять нужно уже реализации из stdlib. Но в целом там нечему тормозить. Лично я разницы не заметил, но мой опыт ограничен прототипированием. И то недолгим т.к. они тогда собирались крайне медленно.

скорости компиляции

Текущая реализация очень тормозная, в том числе и по причине эмуляции концептов. Но реализация и не претендует на оптимальность.

2) Как себя ведут рэнджи относительно исключений? Бросают ли сами? как реагируют на то, если итератор бросит?

А что именно они там бросать должны и на что реагировать? Реагируют так же как и какой-нибудь std::transform()|range based for.

3) Хотелось бы еще пример того, что будет показывать компилятор при опечатке и имени ренджовой функции. Боюсь что-то очень страшное.


main.cpp:14:51: ошибка: «splite» is not a member of «rv»; did you mean «split»?
   14 |   auto count = rs::distance(rv::c_str(text) | rv::splite(' '));
      |                                                   ^~~~~~
      |                                                   split


main.cpp:17:62: ошибка: no match for «operator|» (operand types are «ranges::split_view<ranges::delimit_view<ranges::subrange<const char*, ranges::unreachable_sentinel_t, ranges::subrange_kind::unsized>, char>, ranges::single_view<char> >» and «int»)
   17 |   auto count = rs::distance(rv::c_str(text) | rv::split(' ') | x);
      |                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^ ~
      |                                             |                  |
      |                                             |                  int
      |                                             ranges::split_view<ranges::delimit_view<ranges::subrange<const char*, ranges::unreachable_sentinel_t, ranges::subrange_kind::unsized>, char>, ranges::single_view<char> >


Вот пример: godbolt.org/z/VSykII

4) Работают ли в constexpr контексте?

Смотря что. Много работает, а много нет. В стандарте, думаю, будет работать всё.

Где почитать?

Везде. Тема поднималась тысячи раз. Я ссылался на это Где почитать — не знаю, меня эта тема не интересует.

А если серьезно, есть впечатление, что комитет давно забил на скорость работы в сборках без оптимизаций.

С++ до «забил» ещё очень далеко. Недавно кто-то пытался дождаться выполнения дебажной сборки раста — не дождался.

сборках без оптимизаций.

Лучше её называть дебажной. Мотивация там не столько вырубить оптимизатор, а сколько обеспечить прямое соответствие написанному и сгенерированному коду.

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

Почему вредна? Всё очень просто. Опять же, многопоточная среда. O0 по-сути предполагает volatile для всех переменных. Т.е. поведение разных сборок будет различным.

В ситуации же с более мощной и универсальной отладкой трейсом — таких проблем нет. Как и нет проблем с отладкой подобных пайпов. Без проблем пишется операция «трейс» и вставляет куда угодно в пайп. А трейс — это просто подвид тестирования.

Из этого следует то, что подобные решения(даже если они имеют упомянутые вами проблемы) не приводят к проблемам отладки. Они приводят к проблемам лишь в одной методике отладки. А проблемы одного из подходов не являются проблемой в целом, а являются проблемой локальной для подхода.

Наверное потому что пропаганда(там же всё обвешано банерами)? У меня играет это без всяких firefox.
Нет. Это какая-то библиотека для написания бенчмарков.

У нас есть Си-версия.

Есть файл params:
512
512
100000000
1.0e-1
1.0e-1


$ clang main.c -Ofast -march=native -o main
$ time ./main < params


Мы получаем: outputSeq.dat и время от time(либо от Wall Clock, которое вывод си-версия).

С вашей(julia) стороны нужно предоставить тоже самое. Т.е. какую-то программу(либо исходник с мануалом «как собрать?»), которая будет по таким же параметрам генерировать такой же outputSeq.dat.

Далее, при идентичных outputSeq.dat файлах, мы уже можем сравнивать time для julia и для С. Это будет адекватное сравнение и каждый наблюдатель сможет воспроизвести результаты(повторив наш алгоритм проверки. Сам, либо посредством предоставленных скриптов).

Решил я тут пойти по ссылкам. И увидел это:

CC = gcc-mp-7 -Wall

На этом уже можно закончить. Но я не закончил.

Я взял однопоточную версию. gcc7 у меня нет, поэтому будет gcc9. Собрал через make и получил в районе 80 секунд.

Далее я собрал всё в файл и собрал -O3 -march=native -lm. Результат уже ~10 секунд(gcc9.1/clang9). Файлы .dat идентичны. -Ofast + clang9(т.е. llvm, который является компилятором и в julia) уже 7 секунд.

О качестве кода я говорить ничего не буду.
Автоматическая векторизация в Julia

Автоматическая векторизация для fp небезопасна и я бы таким не гордился. Но в любом случае, если он включена, то сравнивать нужно с -ffast-math для С.

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

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

Да и вообще: она быстрее, чем эквивалентные коды Fortran и C! (подробнее об этом позже)

Как воспроизвести эти результаты?
Вообще говоря, никто не мешает программисту действовать в два прохода: скомпилировать со сборщиком профиля, запустить на тестовых данных и тем самым собрать профиль, и скомпилировать с использованием профиля. Пруф для gcc, показывающий как минимум возможность таких оптимизаций.

Это не новость для меня.

Имхо там больше использующих профиль оптимизаций.

Проблема не в этом.

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

Что касается «эффективней» и «наобум». И то и то верно лишь частично. Не всегда можно собрать с программы адекватный трейс. И не все обладают пониманием уровня «ставим наобум». Но я не об этом говорил.

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

В моём понимании задачей pgo будет не (о)птимизация, ведь оптимизация это такое же тыканье «наобум». Я уже приводил автору достаточно очевидный пример. У нас есть строка и sso. Исходя из статического анализа/профиля мы можем получить длины используемых строк.

А ещё лучше и генерацию профиля тоже вынести на уровень языка. Мы сможем добавлять свои метрики, расширять/переопределять существующие. Тогда ненужно будет неуправляемых костылей в каждом из компиляторов, компиляторы не будут проводить оптимизация через одно место. Они всегда будут хуже в сравнении с хайлевел.

Но, существует фундаментальная проблема у pgo. Трейс никак не может ограничить входящие данные. Даже если у нас 100% строк в программе — 5 байт, то мы никак не можем заменить их на 5/6-байтные массивы. Просто потому, что в любом момент может прийти что угодно.

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

С метриками на уровне языка мы всегда сможет показать программисту при сборке сообщения вида уровня «строки 5-байт. Ограничьте контрактом/поменяйте тип». Это ещё более частный случай оптимизации, которую делает автор.

Точно так же нам очень сложно выявить источники данных без тех же контрактов/любой другой метаинформации. Если у нас будут нормальные opaque-тимы, а не пытки их закостылить — мы всегда сможем узнать источники данных.

Ничего из этого работать не будет(вообще или нормально) с оптимизациями где-то там в кишках.

Информация

В рейтинге
Не участвует
Зарегистрирован
Активность