Я не то чтобы спец, но предположим у вас в реализации функции не какая-нибудь банальщина, а инстанцирование тяжелых линейных солверов из 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 строк
Никак не могут компилироваться секунды. К тому же сравнение скорости компиляции на разном железе меня так же удивляют. А так же меня удивляет отсутствие информации об компиляторе и какой-то конкретики в целом.
Опять же. Кода мы не видим. Никаких измерений нет. Есть «одна бабка сказала» и какие-то ничем не обоснованные заявления. Прослеживается явная ничем не обоснованная ангажированность и двойные стандарты.
C++ имеет возможность переопределения операторов, а у ребят из комитета есть жгучее желание эту возможность использовать
И в чём же проблема? Какая разница как это сделано? Оператором или каким-нибудь кейвордом/конструкциями?
в стиле «ыыы, смотрите как можно».
В стиле «ыыы, мы можем использовать возможности языка и делать нормальные api»? Как это глупо, как это странно.
Лично я бы допилил uniform function call syntax
Это такая же перегрузка не очевидная. В чём разница? Да и что будем делать с коллизиями имён?
без извращенств с операторами
В чём «извращенств» заключается?
хочу видеть так:
У этого подхода все аналогичные проблемы(хотя я наличия проблем не вижу ни там, ни там). Допустим, если я хочу сохранить операцию — можно сделать auto op = filter(is_even); Если же я захочу сделать это с функций — мне нужно описывать функцию с достаточно не очевидным параметром. Какой концепт мы напишем у op? Какой он у filter? Если мы просто напишем auto — мы получим плохую ошибку. Нам нужно будет писать какой-нибудь кастомный bind.
Трупут 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> >
А если серьезно, есть впечатление, что комитет давно забил на скорость работы в сборках без оптимизаций.
С++ до «забил» ещё очень далеко. Недавно кто-то пытался дождаться выполнения дебажной сборки раста — не дождался.
сборках без оптимизаций.
Лучше её называть дебажной. Мотивация там не столько вырубить оптимизатор, а сколько обеспечить прямое соответствие написанному и сгенерированному коду.
Отладка подобной сборки не всегда возможна и зачастую попросту вредна. Не все программы могут работать с подобных замедлением + многопоточна среда(и не обязательно эти потоки уровня ОС).
Почему вредна? Всё очень просто. Опять же, многопоточная среда. O0 по-сути предполагает volatile для всех переменных. Т.е. поведение разных сборок будет различным.
В ситуации же с более мощной и универсальной отладкой трейсом — таких проблем нет. Как и нет проблем с отладкой подобных пайпов. Без проблем пишется операция «трейс» и вставляет куда угодно в пайп. А трейс — это просто подвид тестирования.
Из этого следует то, что подобные решения(даже если они имеют упомянутые вами проблемы) не приводят к проблемам отладки. Они приводят к проблемам лишь в одной методике отладки. А проблемы одного из подходов не являются проблемой в целом, а являются проблемой локальной для подхода.
$ clang main.c -Ofast -march=native -o main
$ time ./main < params
Мы получаем: outputSeq.dat и время от time(либо от Wall Clock, которое вывод си-версия).
С вашей(julia) стороны нужно предоставить тоже самое. Т.е. какую-то программу(либо исходник с мануалом «как собрать?»), которая будет по таким же параметрам генерировать такой же outputSeq.dat.
Далее, при идентичных outputSeq.dat файлах, мы уже можем сравнивать time для julia и для С. Это будет адекватное сравнение и каждый наблюдатель сможет воспроизвести результаты(повторив наш алгоритм проверки. Сам, либо посредством предоставленных скриптов).
Я взял однопоточную версию. gcc7 у меня нет, поэтому будет gcc9. Собрал через make и получил в районе 80 секунд.
Далее я собрал всё в файл и собрал -O3 -march=native -lm. Результат уже ~10 секунд(gcc9.1/clang9). Файлы .dat идентичны. -Ofast + clang9(т.е. llvm, который является компилятором и в julia) уже 7 секунд.
О качестве кода я говорить ничего не буду.
Автоматическая векторизация в Julia
Автоматическая векторизация для fp небезопасна и я бы таким не гордился. Но в любом случае, если он включена, то сравнивать нужно с -ffast-math для С.
Собственно, в очередной раз можно убедиться в адекватности всех этих сравнений и громких заявлений.
Это не то. В статье были показаны графики, а значит автор должен был иметь бенчмарк(он же не из головы результаты взял?). Вот я хочу увидеть этот бенчмарк, который я смогу запустить и воспроизвести результаты?
Бенчмарк это такая штука, которая запускает несколько реализаций одного и того же, сравнивает результаты выполнения(они должны быть идентичны) и показывает нам время для каждой реализации.
Вообще говоря, никто не мешает программисту действовать в два прохода: скомпилировать со сборщиком профиля, запустить на тестовых данных и тем самым собрать профиль, и скомпилировать с использованием профиля. Пруф для gcc, показывающий как минимум возможность таких оптимизаций.
Это не новость для меня.
Имхо там больше использующих профиль оптимизаций.
Проблема не в этом.
Такой вариант имхо эффективнее, чем пытаться наобум что-то заменять. Поэтому с ипользованием профиля оптимизация смысл тоже имеет.
Что касается «эффективней» и «наобум». И то и то верно лишь частично. Не всегда можно собрать с программы адекватный трейс. И не все обладают пониманием уровня «ставим наобум». Но я не об этом говорил.
Абсолютно неважно кто расставит эту мета-информацию. Она без проблем может быть экспортирована из профиля. Как и может быть указана руками. Ключевым тут являет то(и именно об этом я говорил), что эта(и любая другая) информация должна быть доступна на уровне языка.
В моём понимании задачей pgo будет не (о)птимизация, ведь оптимизация это такое же тыканье «наобум». Я уже приводил автору достаточно очевидный пример. У нас есть строка и sso. Исходя из статического анализа/профиля мы можем получить длины используемых строк.
А ещё лучше и генерацию профиля тоже вынести на уровень языка. Мы сможем добавлять свои метрики, расширять/переопределять существующие. Тогда ненужно будет неуправляемых костылей в каждом из компиляторов, компиляторы не будут проводить оптимизация через одно место. Они всегда будут хуже в сравнении с хайлевел.
Но, существует фундаментальная проблема у pgo. Трейс никак не может ограничить входящие данные. Даже если у нас 100% строк в программе — 5 байт, то мы никак не можем заменить их на 5/6-байтные массивы. Просто потому, что в любом момент может прийти что угодно.
И решение существует только на уровне языка. Это контракты, это система типов. Мы никогда на научим тупые лоулевел оптимизации понимать семантику наших типов. Да даже понимать семантику контрактов вряд ли научим. И учить лоулевел понимать хайлевел абстракции — куда сложнее.
С метриками на уровне языка мы всегда сможет показать программисту при сборке сообщения вида уровня «строки 5-байт. Ограничьте контрактом/поменяйте тип». Это ещё более частный случай оптимизации, которую делает автор.
Точно так же нам очень сложно выявить источники данных без тех же контрактов/любой другой метаинформации. Если у нас будут нормальные opaque-тимы, а не пытки их закостылить — мы всегда сможем узнать источники данных.
Ничего из этого работать не будет(вообще или нормально) с оптимизациями где-то там в кишках.
Это ничего не значит, да и попросту враньё. На это я могу ответить: есть rust|haskell, которыми пользуется примерно никто.
PS Опять табун раст-адептов набежал и начал гадить в карму.
Ну ваши претензии не работают в рамках логики автора, да и в моей тоже.
Всё что связано с шаблонами. Разделять то же auto не имеет смысла, т.к. типы по-сути неизвестен. Много примеров.
chromium.googlesource.com/chromium/src/+/lkgr/docs/jumbo.md
Вам это никак не поможет. Это поможет только в том случае, если вы обернёте шаблонное api в c-api и уже с ним будете работать. Но будет уже не С++. Если же ваша логика будет так же шаблонной и так же использовать какую-то логику из библиотеки в cpp-файле, то это ничего не даст.
В современном С++-коде код не разбивается(в силу нежелания этого делать и в силу невозможности этого сделать). Существует тысячи ho-библиотек и тот же stdlib так же является ho-библиотекой.
Таким образом в каждом вашем cpp-файле будет 1% вашего кода и 99% заинклюженого. Таким образом компилятор будет делать лишнюю работу, собирая тысячи раз одно и те же инклюды.
Просто для понимания сделайте подобный бенчмарк:
И чем сложнее будут инклюды — тем хуже ситуация будет с раздельной компиляцией. Оно будет почти всегда медленнее. И единственное для чего может потребоваться раздельная компиляция — это параллельная сборка большой кодовой базы. Но это явно не тот случай. Да и в том случае нужно будет модульное разделение, а не разделение по-файлам.
А чего же их не разделяют в rust? Да и много где ещё. Подобные рассуждения не работают, вообще. Вы либо последовательно применяйте это ко всем языкам, либо не применяете вообще.
Это догмат. Лично для меня это ничего и никак не улучшает, как и для пользователей тысяч других языков, где нет этого разделение. К тому же не каждый С++-код можно разделить, а современный С++ разделить вообще почти нельзя.
Это попросту глупость разоблачённая уже давно. Как-то ускорение есть лишь в очень редких случаях, либо на очень большой кодовой базе. Вернее даже не ускорение, а просто возможность распараллелить сборку.
К тому же, такого разделение в rust нет и там собирается всё модульно. Опять же, либо будьте последовательны и применяйте это ко всем языкам, либо не применяйте вообще.
Повторю, они игнорируются в «несколько тысяч строк на rust» и проблем не вызывают, как и в тысячах других языков.
Ой, оказывается те другие попросту использовали «сопоставлять шаблон» и это было плохо. Но это ведь была проблема С++, а теперь оказывается rust-код отказывается от pm в пользу визиторов, которое в С++ попросту нативны(в отличии от rust, где это делается через тысячи костылей и боейлерплейта).
Каждая новая строчка пробивает очередной дно.
Что это за ангажированные намёки ничем не обоснованные? Это сравнение, либо пропаганда?
На каком основании это основа, а не это:
Почему эта разница в одном случае описывается дизайном, а в другом непонятным набором слов?
А раньше же было «ожидаемо», а теперь «не копался», «не знаю»? Зачем что-то утверждать, если при этом ничего не знать?
Никакое «сопоставлений с образцом» не является чем-то необходимым и в 95% случаев сливает более мощной концепции визиторов. В С++ есть исключения и ненужно матчить каждую функцию. К тому же, с чего вдруг в С++ нету сумтипов?
В рамках той же концепции визиторов С++ обладаем куда большими преимуществами. В rust нету перегрузки функций, нету шаблонов. Для реализации чего-то уровня С++-перегрузки требуются генерик трейты, тонна бойлерплейта и даже тогда это не будет чем-то равным.
То, что это попросту обман я уже писал. Необходимость что-то там дублировать обусловлена разделением на h/cpp-файлы. Никак не обусловлена С++ и это разделение может не использоваться.
Какая разница какие функции? К тому же, тут явно видно взаимные противоречия.
Это просто глупость несусветная. Это мифология, которую была опровергнута уже тысячи раз. Особенно хромом. Уплотнение кода способствует ускорению компиляции т.к. инклюды в С++ тяжелые и компилятор делает ненужную работу каждый раз обрабатывая один и тот же код в каждом cpp-файле.
К тому же, уровень владения языком представителей С++-лагеря крайне скудны(судя по тексту). В С++ непросто так появилось ho и причины тому просты — шаблонный код попросту нельзя разделить. Попытки были и все они не имели успеха.
Таким образом из этого можно сделать вывод, что там не было никакого С++, а был типичный студенческий С/С++ начального уровня.
Никак не могут компилироваться секунды. К тому же сравнение скорости компиляции на разном железе меня так же удивляют. А так же меня удивляет отсутствие информации об компиляторе и какой-то конкретики в целом.
Опять же. Кода мы не видим. Никаких измерений нет. Есть «одна бабка сказала» и какие-то ничем не обоснованные заявления. Прослеживается явная ничем не обоснованная ангажированность и двойные стандарты.
Это неверно. Такой необходимости нет.
И в чём же проблема? Какая разница как это сделано? Оператором или каким-нибудь кейвордом/конструкциями?
В стиле «ыыы, мы можем использовать возможности языка и делать нормальные api»? Как это глупо, как это странно.
Это такая же перегрузка не очевидная. В чём разница? Да и что будем делать с коллизиями имён?
В чём «извращенств» заключается?
У этого подхода все аналогичные проблемы(хотя я наличия проблем не вижу ни там, ни там). Допустим, если я хочу сохранить операцию — можно сделать auto op = filter(is_even); Если же я захочу сделать это с функций — мне нужно описывать функцию с достаточно не очевидным параметром. Какой концепт мы напишем у op? Какой он у filter? Если мы просто напишем auto — мы получим плохую ошибку. Нам нужно будет писать какой-нибудь кастомный bind.
Трупут memcpy десятки гагабайт на ведро. Трупут современной файловой подсистемы не далеко от этого ушел. 99% программ даже сотен мегабайт трейса не генерируют. Такое кол-во программ даже близко не являются оптимальными, причём эта неоптимальность не исчисляется процентами, а исчисляется разами.
И что самое интересное, эти рассуждения вообще не имеют смысла. Если для отладки чего-то необходим даже мегабайт трейса — отладить это вручную попросту невозможно. Здесь нет какого-либо выбора «трейс или не трейс» и какие-то претензии к трейсу попросту нестоятельны.
Это не моя мотивация и я ничего не знаю об этом. Для меня так же все минусы -O0 перекрывают плюсы.
Смотря как написать трейс.
Трейс является источником данных. Интерпретировать их может не только человек, а интерпретация человек уже по-сути тестирование.
Замерять нужно уже реализации из stdlib. Но в целом там нечему тормозить. Лично я разницы не заметил, но мой опыт ограничен прототипированием. И то недолгим т.к. они тогда собирались крайне медленно.
Текущая реализация очень тормозная, в том числе и по причине эмуляции концептов. Но реализация и не претендует на оптимальность.
А что именно они там бросать должны и на что реагировать? Реагируют так же как и какой-нибудь std::transform()|range based for.
Вот пример: godbolt.org/z/VSykII
Смотря что. Много работает, а много нет. В стандарте, думаю, будет работать всё.
Везде. Тема поднималась тысячи раз. Я ссылался на это Где почитать — не знаю, меня эта тема не интересует.
С++ до «забил» ещё очень далеко. Недавно кто-то пытался дождаться выполнения дебажной сборки раста — не дождался.
Лучше её называть дебажной. Мотивация там не столько вырубить оптимизатор, а сколько обеспечить прямое соответствие написанному и сгенерированному коду.
Отладка подобной сборки не всегда возможна и зачастую попросту вредна. Не все программы могут работать с подобных замедлением + многопоточна среда(и не обязательно эти потоки уровня ОС).
Почему вредна? Всё очень просто. Опять же, многопоточная среда. O0 по-сути предполагает volatile для всех переменных. Т.е. поведение разных сборок будет различным.
В ситуации же с более мощной и универсальной отладкой трейсом — таких проблем нет. Как и нет проблем с отладкой подобных пайпов. Без проблем пишется операция «трейс» и вставляет куда угодно в пайп. А трейс — это просто подвид тестирования.
Из этого следует то, что подобные решения(даже если они имеют упомянутые вами проблемы) не приводят к проблемам отладки. Они приводят к проблемам лишь в одной методике отладки. А проблемы одного из подходов не являются проблемой в целом, а являются проблемой локальной для подхода.
У нас есть Си-версия.
Есть файл 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 и для С. Это будет адекватное сравнение и каждый наблюдатель сможет воспроизвести результаты(повторив наш алгоритм проверки. Сам, либо посредством предоставленных скриптов).
На этом уже можно закончить. Но я не закончил.
Я взял однопоточную версию. gcc7 у меня нет, поэтому будет gcc9. Собрал через make и получил в районе 80 секунд.
Далее я собрал всё в файл и собрал -O3 -march=native -lm. Результат уже ~10 секунд(gcc9.1/clang9). Файлы .dat идентичны. -Ofast + clang9(т.е. llvm, который является компилятором и в julia) уже 7 секунд.
О качестве кода я говорить ничего не буду.
Автоматическая векторизация для fp небезопасна и я бы таким не гордился. Но в любом случае, если он включена, то сравнивать нужно с -ffast-math для С.
Собственно, в очередной раз можно убедиться в адекватности всех этих сравнений и громких заявлений.
Бенчмарк это такая штука, которая запускает несколько реализаций одного и того же, сравнивает результаты выполнения(они должны быть идентичны) и показывает нам время для каждой реализации.
Как воспроизвести эти результаты?
Это не новость для меня.
Проблема не в этом.
Что касается «эффективней» и «наобум». И то и то верно лишь частично. Не всегда можно собрать с программы адекватный трейс. И не все обладают пониманием уровня «ставим наобум». Но я не об этом говорил.
Абсолютно неважно кто расставит эту мета-информацию. Она без проблем может быть экспортирована из профиля. Как и может быть указана руками. Ключевым тут являет то(и именно об этом я говорил), что эта(и любая другая) информация должна быть доступна на уровне языка.
В моём понимании задачей pgo будет не (о)птимизация, ведь оптимизация это такое же тыканье «наобум». Я уже приводил автору достаточно очевидный пример. У нас есть строка и sso. Исходя из статического анализа/профиля мы можем получить длины используемых строк.
А ещё лучше и генерацию профиля тоже вынести на уровень языка. Мы сможем добавлять свои метрики, расширять/переопределять существующие. Тогда ненужно будет неуправляемых костылей в каждом из компиляторов, компиляторы не будут проводить оптимизация через одно место. Они всегда будут хуже в сравнении с хайлевел.
Но, существует фундаментальная проблема у pgo. Трейс никак не может ограничить входящие данные. Даже если у нас 100% строк в программе — 5 байт, то мы никак не можем заменить их на 5/6-байтные массивы. Просто потому, что в любом момент может прийти что угодно.
И решение существует только на уровне языка. Это контракты, это система типов. Мы никогда на научим тупые лоулевел оптимизации понимать семантику наших типов. Да даже понимать семантику контрактов вряд ли научим. И учить лоулевел понимать хайлевел абстракции — куда сложнее.
С метриками на уровне языка мы всегда сможет показать программисту при сборке сообщения вида уровня «строки 5-байт. Ограничьте контрактом/поменяйте тип». Это ещё более частный случай оптимизации, которую делает автор.
Точно так же нам очень сложно выявить источники данных без тех же контрактов/любой другой метаинформации. Если у нас будут нормальные opaque-тимы, а не пытки их закостылить — мы всегда сможем узнать источники данных.
Ничего из этого работать не будет(вообще или нормально) с оптимизациями где-то там в кишках.