В качестве контраргумента достаточно посмотреть на засилие электрона вокруг,
В качестве контр-аргумента подойдёт и rust. Но это не является контр-аргументом, т.к. и одно и второе не является исполнителем и не является хардварным.
которму до эффективности как до луны.
Даже если я приму эти попытки, то они в любом случае неудачны. Исполнителем в случае с электроном является хром. Хром является самым эффективным броузером, v8 является самым эффективным исполнителем js.
Нету. лисп-машина — это интерпретатор лиспа. Причём даже не хардварный. Об этом даже в википедии написано. Зачем люди продолжают повторять эту глупость(что лисп-машины исполняли какой-то лисп)?
Так же, упускается из виду фундаментальное обстоятельство. Никому и никогда ненужно просто исполнение. Нужно эффективное исполнение. И именно этим лисп-машины и были. Просто хардварные оптимизации характерные для lisp. Такая же история есть с java-байткодом в каких-нибудь arm.
Все эти ухищрения неэффективны. Именно поэтому они и умерли, а существовали только на начальных этапах. Как в том же arm. Исполнитель откровенно слаб, частота маленькая. Проще туда впихнуть побольше всякой логики и будет быстрее. Но чем дальше — тем хуже.
Именно поэтому и умер циск. Когда-то это казалось хорошей идеей, а сейчас воспринимается за глупость.
Никто rustc не декларирует как транслятор из rust в llvm-ir. Мало того, что это попросту глупо. Причин много. Начиная из того, что ir уже llvm, его внутренние представление. И это именно задача фронтенда генерировать промежуточное представление компилятора. Получается, что компилятор компилирует то, что потом компилирует компилятор. Какой смысл вообще разделять фронтенд и компилятор, если и то и компилятор? Заканчивая тем, что на уровне языка множество ссылок на именно генерацию программ. Те же модули существуют именно на бинарном уровне.
Даже если всё это исключить и принять эту логику, то это ничего не изменит. Генератор ir в rustc зависим от llvm. Именно llvm генерирует ir, а фронтенд использует api для генерации ir. Таким образом те части, которая в rustc написаны на rust сами генерировать ir не могут.
Единственная соломинка здесь — это mir. Но это тоже тупиковый путь. Это некая форма rust. Её ничто не умеет исполнять. Она декларируется как промежуточное представление. Это не таргет. Если это таргет, то зачем компилятору что-то делать с таргетом и куда-то его преобразовывать(пусть и внешней логикой)?
Прямо как string_view, который все так долго ждали в плюсах.
Все его использовали ещё со времён. Где именно его ждали? В стандарте? Ну слайсы для раста уже 10лет ждут в стандарте, как всё остальное.
Поэтому и хвалит он раст, у которого компилятор не позволит такой баг допустить.
Как? Не даст взять ссылку на элемент и придётся(как автор делал) хранить индексы? Это неудобно, но ладно.
Проблема заключается в том, что я без проблем могу написать vector, который ссылки не теряет. Причём мне ненужно даже писать вектор, можно просто поменять ему аллокатор.
Это одна из фундаментальные проблем «безопасности» подобного рода. Эта безопасность защищает даже тогда, когда никакой защиты ненужно. Но продолжает вносить оверхед(как и по производительности, так и по удобству программиста).
Проблема подобных ответов заключается в том, что в понятия «аналог» входит то, что является сравнимым хотя-бы по каким-то основным критериям. В данном случае основном критерия аллокатора — работать быстро. Этот аллокатор неконкурентоспособен не то что каким-то лучшим аллокатора, а даже самым банальным.
Даже если устранить все различия в реализациях одного и того же кода (для этого в Rust зачастую придется обмазаться unsafe с ног до головы, нивелируя все его достоинства)
Аналогичная проблема возникла и тут. Даже наивный код в котором C++ и его stdlib очень просто победить. Не побеждается. И чем ниже уровень, тем больше проблем. Ведь rust задизайнен именно для наивногохайлевел программирования.
Да, можно говорить о том, что код на rust более безопасный. В С++ можно попробовать заменить list на vector. Но нельзя говорить о том, что эта безопасность достаётся бесплатно. Как и по части производительности, так и по части применяемых усилий. Очень много ложится на голову программиста.
Есть ещё один нюанс, о котором всё время забывают. Не бывает быстрых/медленных языков. Есть языки с большими/меньшими возможностями. Производительность/безопасность кода зависит исключительно от программиста. И именно на этом этапе проявляется различие языков. Какой-то язык одному уровню программистов даёт писать код быстрее/безопаснее, чем другим. Но для другого уровня — всё может быть наоборот.
Если мы начнём говорить про производительность, то тут прежде всего определяющим является умение программиста. А язык лишь средство выражения его умения/опыта. Язык должен дать возможность выразить программисту то, что он хочет и то как он это хочет. В этом основания проблема rust. В подобных случаях он не просто не помогает — он мешает. Причём порою мешает до невозможности.
Именно поэтому нет никакого смысла говорить о высокой(не в сравнении с питоном/swift, а объективно) производительности. Раст попросту не даст возможности программисту раскрыть свой потенциал. Именно поэтому уровень программистов будет относительно мал. Человек должен быть крайне фанатичен(в сторону rust) что-бы несмотря на все горечи и страдания пытаться писать на нём высокопроизводительный код.
компилятор rust'а может использовать менее эффективную декларацию о вызовах
Есть ещё одно обстоятельство касательно llvm. В нём вполне адекватные хайлевел-оптимизации. В ситуациях, когда дело касается числодробильного кода- он крайне слаб.
За примерами ходить ненужно. Тот же rapidjson собранный gcc в полтора раза быстрее собранного clang/llvm.
Производительность однопоточного С++ кода я привёл, чтобы была точка отсчета для связи с приведёнными результатами на других машинах.
Нет, вы именно сравнивали. Как минимум автор статьи декларировал это сравнение явно, а вы просто сравнивали. Причём сравнивали непонятно где. И почему-то 200мс сустайма у вас вопросов не вызвало как и запуск не под линуксом.
Многопоточного С++ кода тогда ещё по-моему не было.
Я уже отвечал на этот вопрос. Дело в том, что добавление многопоточности — это не оптимизация в ситуации, когда и тот и тот язык умеет в многопоточность. Тут можно долго говорить о просьбе автора, но факт остаётся фактом. Это обстоятельство нужно уточнять и сравнивать несравнимое нельзя.
Rust ориентирован на безопасность когда. Наивное решение на Rust и наивное решение на C++ (которое ещё и работает) требуют разных уровней знания языка.
Насколько я вижу в интернете, везде вся эта безопасность декларируется как «zero-cost».
Кроме того, использование вами string_view и других последних фишек языка, как-то не выглядит очень наивным.
Я уже так же отвечал на этот вопрос. Единственное место, которое я паписал «не очень наивно» — это main::next. Именно там сосредоточено почти всё использование string_view.
Какой у вас опыт разработки на C++?
Смотря как считать. Если брать именно С++, то может года 2-3 суммарно.
Подход, применяемый в rapidjson, и так отличается от того, что применяется в serde.
Например, SIMD intrinsic'и в serde пока ещё не применяются.
Я вижу этот rapidjson первый(или второй) раз в жизни, как и саму задачу парсинга json«а. Полистав код я вижу только одно, что я уже и говорил. Этой библиотеке очень и очень далеко до оптимальной.
По поводу simd. Они там мало что дают. Да и сделано это крайне наивно.
Видимо это и является причиной, почему я с трудом догнал его по производительности на своей машине даже с zero-copy.
Нет, не является. Я проверил. Они там не включены по умолчанию, а если их включить, то они только замедляют код.
Пожалуйста, объясните, каким образом это не zero-copy? x.name.GetString() возвращает указатель на буфер, если есть такая возможность. Или я не прав?
Нет, это не zero-copy. В данном случае я использую sv как обёртку над „указатель + длинна“, которую мне отдают библиотка. Использую я это для красивого сравнения строк через ==, а не через memcmp(который за меня вызовет sv).
Все строки, которые выходят из это функции — это std::string, в который копируются данные. Везде и всюду, где использует sv — он используется как удобная обёртка над указателем + длинна. Никакой zero-cost функции он не несёт.
Причины я обозначил. Я писал аналогичный rust-коду автора код. Единственное место, где я отошёл от этого правила — это main::next и чтение файла. Причина банальна — так проще.
zero-cost есть в многопоточной реализации, именно так строки отправляют через очередь на парсинг.
Это инвариант типов String и str — они содержат валидный utf-8, если не содержат — UB.
Ещё раз. Это локальные проблемы отдельной библиотеки. Реальная жизнь на строки никакого подобного инварианта не накладывает. Строки это не только utf-8.
Но есть. Платформа WSL.
Это явно не обозначенная автором платформа.
И, после общения в таком тоне, не очень хочется.
Не хочется запускать код на той платформе, которую обозначил автор?
В статье представлена достаточно наивная реализация решения.
Я предоставил такую же наивную реализацию на С++.
Производительность Rust кода теоретически не должна отличаться от C++.
Нужно сравнивать равные решения, иначе это всё не имеет смысла. Вы пытаетесь догнать наивную С++-версию. Сравнивайте с наивным rust-кодом. Вы же что делаете? Вы добавляете хаки, оптимизации и прочее, что не будет использоваться в обыденной жизни. В обыденной жизни будет использоваться базовый подход.
Далее, вы ускоряете версию в которой проблем меньше, чем у С++. Вы пытаетесь добавить zero-copy парсинг, убрать копирование, добавить более оптимальные хешмапы, cow и прочее. Но проблема никуда не делал — это сравнение глупое. Ведь всё это(и даже больше) есть в С++-коде и я там же могу этим заняться. И если вы даже наивную С++ до сих пор не догнали, то что будет в том случае? В случая, когда она будете не наивная.
Нужно всегда сравнивать что-то в равных условиях. Подобный вашему подход работает в ситуации сравнению с тем же jua. jua не может во всё это и на этом можно играть.
Я разбираюсь почему она отличается.
Ну нет же. В С++ есть zero-copy-парсинг? Нету. Там даже поточного парсинга нету. Задача «понять почему отличается» — это не пытаться найти другой подход и как-то догнать. Задача понять заключается в том, что-бы узнать. Почему в одном и том же подходе оно отличается. Почему С++ без всего этого работает быстро, причём работает быстрее даже самой оптимизированной вашей версии.
Эм, а что и многопоточная версия есть?
Да. Я специально после написания этой ветке пошел и сделал его.
У вас всё в порядке? Обычное желание выжать максимум из кода. Не надо придумывать какие-то странные мотивации.
Вы не выжимаете максимум — вы сравниваете. Сравниваете неадекватно. Сравниваете одно/много-поточные версии. Сравниваете наивные и не-наивные решения.
Выше вы писали, что хотите разобраться. Сейчас вы пишите, что вы просто пытаетесь догнать любыми методами. И в этом проблема. Все эти ухищрения, в том числи и многопоточность, можно добавить в С++. Это вам никак не поможет разобраться.
Это проблема вашего подхода. Обозначу проблему ещё раз. Нет смысла делать быстрее за счёт изменения подхода применяемого в С++. Вы этим ничего ничего не добиваетесь и причина простая. Всё это подходы можно точно так же добавить в С++ и они на него повлияют точно так же, даже больше.
Поправил баг (ну не работал я с serde никогда в zero-copy режиме), время выполнения поднялось на 50ms
Я в очередной раз спрошу. Зачем? История аналогичная потоком. Вы пытаетесь похакать, добавить jemalloc, выкинуть стандартную библиотеку, добавить «zero-copy режиме». И все эти ухищрения нужны для того, что-бы ещё даже не догнать С++-версию без всего это.
Зачем вы сравнивали однопоточную и многопоточную версию? Думали, что я не добавлю многопоточную? Зачем вы занимаетесь этим шаманством? Вы думаете, я не добавлю его в С++-код? К чему эта пыль в глаза?
Раст, как и С++, предполагает, что программист знает что делает. Если написано &str — значит программист хочет валидный utf-8, что предполагает обязательную валидацию пользовательского ввода. Догадаться, что программист хочет максимально быстрый код, Раст, естественно, не может.
Не совсем понимаю причём тут пользовательский ввод, когда читаются бинарные данные из файла. Если мне понадобиться валидация utf-8 — я вопрошу её явно. Раст ведь декларирует zero-cost-абстракции?
Так же, я говорил об ином. Для того, что-бы догнать наивный, кое-как написанный С++-код приходится что-то оптимизировать, хакать, страдать. И даже всего этого недостаточно.
serde ориентирован на десериализацию в strongly typed персистентные структуры, а не на потоковый парсинг.
У меня используется парсер в dom-режиме. В sax-режиме этот парсер куда быстрее, но до «максимально» и даже «быстрого» ему очень далеко. Не вижу смысла заниматься оптимизацией изначального тупикового подхода.
Эту задачу нетривиально распараллелить линейно. Поэтому приходится тратить время на раскидывание миллиона объектов по нескольким потокам (оверхед около 300нс на объект, но накапливается), вместо того, чтобы разбить файл на несколько частей и отдать каждую своему потоку.
Всё это не имеет отношения к проблеме. У вас базовая логика почти в 2раза медленнее. Это значит, что абсолютно неважно какой там оверхед и как сложно что-то распараллелить.
Есть базовое обстоятельство. Трупут парсинга объекта и его обработки почти в два раза ниже. В этот самый трупут всё и упирается. На сколько угодно потоков этот трупут можно множить, но он останется лишь в районе 0.5. Ведь у С++ есть столько же потоков.
Это просто фокус. Работает он следующим образом. Есть трупут на одном потоке у С++ — это 1.0. Есть раст, у него трупут на потоке 0.5. Есть некий коэффициент(какой угодно, но положительный). Из этого следует, что добавляя потоки(один, два, десять) — мы когда-нибудь дойдём до этой единице.
Если же взять одинаковое кол-во потоков, одинаковый коэффициент роста за поток, то первый случай будет всегда быстрее. И быстрее в те самые 2 раза.
К слову, то, насколько просто можно распараллелить програму, тоже играет свою роль.
Ещё важнее то, насколько просто написать оптимальную(если мы говорим про оптимальные программы) программу. В одном случае берётся стандартная библиотека и просто пишется код «как попало» без каких-то попыток что-то оптимизировать. Единственная функция, которая там написана хоть как-то адекватно — это main::next. В другом случае требуются замена стандартных функций, множество хаков, тюнинга библиотек и прочего. Но даже этого недостаточно.
На тему распараллеливания. Тут всё куда сложнее. В подавляющем большинстве случаев попытка распараллелить задачу малоэффективна, либо попросту бессмысленна. Подобные мы видим и тут.
В данном случае распараллеливание достигается за счёт запуска отдельных инстансов задачи. Но проблема никуда не уходит. Трупут каждого потока так и остался лишь чуть большим от 0.5 от С++.
Запустить задачи в параллель везде и всюду так же просто, особенно используя готовые библиотеки для организации пайпов. Но в данном случае, как и в большинстве других, всё упирается в трупут парсинг+process_object. И от этого никуда не уйти.
Если задача ~линейно параллелится, то cputime не должно расти. Если же оно растёт — что-то делается не так.
Нет.
В качестве контр-аргумента подойдёт и rust. Но это не является контр-аргументом, т.к. и одно и второе не является исполнителем и не является хардварным.
Даже если я приму эти попытки, то они в любом случае неудачны. Исполнителем в случае с электроном является хром. Хром является самым эффективным броузером, v8 является самым эффективным исполнителем js.
Это явные противоречия. Да и попросту глупость, т.к. все проекты в рамках llvm зависят от llvm и являются его частью.
И что же из этого следует? В rustc появился линкер на rust? Нет.
Откуда же в rustc линкер, если у него нет бинарных таргетов? линкера в rustc нет. Линкером там является llvm.
Нету. лисп-машина — это интерпретатор лиспа. Причём даже не хардварный. Об этом даже в википедии написано. Зачем люди продолжают повторять эту глупость(что лисп-машины исполняли какой-то лисп)?
Так же, упускается из виду фундаментальное обстоятельство. Никому и никогда ненужно просто исполнение. Нужно эффективное исполнение. И именно этим лисп-машины и были. Просто хардварные оптимизации характерные для lisp. Такая же история есть с java-байткодом в каких-нибудь arm.
Все эти ухищрения неэффективны. Именно поэтому они и умерли, а существовали только на начальных этапах. Как в том же arm. Исполнитель откровенно слаб, частота маленькая. Проще туда впихнуть побольше всякой логики и будет быстрее. Но чем дальше — тем хуже.
Именно поэтому и умер циск. Когда-то это казалось хорошей идеей, а сейчас воспринимается за глупость.
Даже если всё это исключить и принять эту логику, то это ничего не изменит. Генератор ir в rustc зависим от llvm. Именно llvm генерирует ir, а фронтенд использует api для генерации ir. Таким образом те части, которая в rustc написаны на rust сами генерировать ir не могут.
Единственная соломинка здесь — это mir. Но это тоже тупиковый путь. Это некая форма rust. Её ничто не умеет исполнять. Она декларируется как промежуточное представление. Это не таргет. Если это таргет, то зачем компилятору что-то делать с таргетом и куда-то его преобразовывать(пусть и внешней логикой)?
Вопрос. Проиграл ли С++?
Я не решился. Копировал строки. С++ не проиграл. Как вы это объясните?
Все его использовали ещё со времён. Где именно его ждали? В стандарте? Ну слайсы для раста уже 10лет ждут в стандарте, как всё остальное.
Как? Не даст взять ссылку на элемент и придётся(как автор делал) хранить индексы? Это неудобно, но ладно.
Проблема заключается в том, что я без проблем могу написать vector, который ссылки не теряет. Причём мне ненужно даже писать вектор, можно просто поменять ему аллокатор.
Это одна из фундаментальные проблем «безопасности» подобного рода. Эта безопасность защищает даже тогда, когда никакой защиты ненужно. Но продолжает вносить оверхед(как и по производительности, так и по удобству программиста).
Нужен не только unsafe, но и libc( в данном случае).
Проблема подобных ответов заключается в том, что в понятия «аналог» входит то, что является сравнимым хотя-бы по каким-то основным критериям. В данном случае основном критерия аллокатора — работать быстро. Этот аллокатор неконкурентоспособен не то что каким-то лучшим аллокатора, а даже самым банальным.
Допустим, вот так: godbolt.org/z/qmzVyN
Аналогичная проблема возникла и тут. Даже наивный код в котором C++ и его stdlib очень просто победить. Не побеждается. И чем ниже уровень, тем больше проблем. Ведь rust задизайнен именно для
наивногохайлевел программирования.Да, можно говорить о том, что код на rust более безопасный. В С++ можно попробовать заменить list на vector. Но нельзя говорить о том, что эта безопасность достаётся бесплатно. Как и по части производительности, так и по части применяемых усилий. Очень много ложится на голову программиста.
Есть ещё один нюанс, о котором всё время забывают. Не бывает быстрых/медленных языков. Есть языки с большими/меньшими возможностями. Производительность/безопасность кода зависит исключительно от программиста. И именно на этом этапе проявляется различие языков. Какой-то язык одному уровню программистов даёт писать код быстрее/безопаснее, чем другим. Но для другого уровня — всё может быть наоборот.
Если мы начнём говорить про производительность, то тут прежде всего определяющим является умение программиста. А язык лишь средство выражения его умения/опыта. Язык должен дать возможность выразить программисту то, что он хочет и то как он это хочет. В этом основания проблема rust. В подобных случаях он не просто не помогает — он мешает. Причём порою мешает до невозможности.
Именно поэтому нет никакого смысла говорить о высокой(не в сравнении с питоном/swift, а объективно) производительности. Раст попросту не даст возможности программисту раскрыть свой потенциал. Именно поэтому уровень программистов будет относительно мал. Человек должен быть крайне фанатичен(в сторону rust) что-бы несмотря на все горечи и страдания пытаться писать на нём высокопроизводительный код.
Есть ещё одно обстоятельство касательно llvm. В нём вполне адекватные хайлевел-оптимизации. В ситуациях, когда дело касается числодробильного кода- он крайне слаб.
За примерами ходить ненужно. Тот же rapidjson собранный gcc в полтора раза быстрее собранного clang/llvm.
Нет, вы именно сравнивали. Как минимум автор статьи декларировал это сравнение явно, а вы просто сравнивали. Причём сравнивали непонятно где. И почему-то 200мс сустайма у вас вопросов не вызвало как и запуск не под линуксом.
Я уже отвечал на этот вопрос. Дело в том, что добавление многопоточности — это не оптимизация в ситуации, когда и тот и тот язык умеет в многопоточность. Тут можно долго говорить о просьбе автора, но факт остаётся фактом. Это обстоятельство нужно уточнять и сравнивать несравнимое нельзя.
Насколько я вижу в интернете, везде вся эта безопасность декларируется как «zero-cost».
Я уже так же отвечал на этот вопрос. Единственное место, которое я паписал «не очень наивно» — это main::next. Именно там сосредоточено почти всё использование string_view.
Смотря как считать. Если брать именно С++, то может года 2-3 суммарно.
Я вижу этот rapidjson первый(или второй) раз в жизни, как и саму задачу парсинга json«а. Полистав код я вижу только одно, что я уже и говорил. Этой библиотеке очень и очень далеко до оптимальной.
По поводу simd. Они там мало что дают. Да и сделано это крайне наивно.
Нет, не является. Я проверил. Они там не включены по умолчанию, а если их включить, то они только замедляют код.
Нет, это не zero-copy. В данном случае я использую sv как обёртку над „указатель + длинна“, которую мне отдают библиотка. Использую я это для красивого сравнения строк через ==, а не через memcmp(который за меня вызовет sv).
Все строки, которые выходят из это функции — это std::string, в который копируются данные. Везде и всюду, где использует sv — он используется как удобная обёртка над указателем + длинна. Никакой zero-cost функции он не несёт.
Причины я обозначил. Я писал аналогичный rust-коду автора код. Единственное место, где я отошёл от этого правила — это main::next и чтение файла. Причина банальна — так проще.
zero-cost есть в многопоточной реализации, именно так строки отправляют через очередь на парсинг.
Ещё раз. Это локальные проблемы отдельной библиотеки. Реальная жизнь на строки никакого подобного инварианта не накладывает. Строки это не только utf-8.
Это явно не обозначенная автором платформа.
Не хочется запускать код на той платформе, которую обозначил автор?
Я предоставил такую же наивную реализацию на С++.
Нужно сравнивать равные решения, иначе это всё не имеет смысла. Вы пытаетесь догнать наивную С++-версию. Сравнивайте с наивным rust-кодом. Вы же что делаете? Вы добавляете хаки, оптимизации и прочее, что не будет использоваться в обыденной жизни. В обыденной жизни будет использоваться базовый подход.
Далее, вы ускоряете версию в которой проблем меньше, чем у С++. Вы пытаетесь добавить zero-copy парсинг, убрать копирование, добавить более оптимальные хешмапы, cow и прочее. Но проблема никуда не делал — это сравнение глупое. Ведь всё это(и даже больше) есть в С++-коде и я там же могу этим заняться. И если вы даже наивную С++ до сих пор не догнали, то что будет в том случае? В случая, когда она будете не наивная.
Нужно всегда сравнивать что-то в равных условиях. Подобный вашему подход работает в ситуации сравнению с тем же jua. jua не может во всё это и на этом можно играть.
Ну нет же. В С++ есть zero-copy-парсинг? Нету. Там даже поточного парсинга нету. Задача «понять почему отличается» — это не пытаться найти другой подход и как-то догнать. Задача понять заключается в том, что-бы узнать. Почему в одном и том же подходе оно отличается. Почему С++ без всего этого работает быстро, причём работает быстрее даже самой оптимизированной вашей версии.
Да. Я специально после написания этой ветке пошел и сделал его.
Вы не выжимаете максимум — вы сравниваете. Сравниваете неадекватно. Сравниваете одно/много-поточные версии. Сравниваете наивные и не-наивные решения.
Выше вы писали, что хотите разобраться. Сейчас вы пишите, что вы просто пытаетесь догнать любыми методами. И в этом проблема. Все эти ухищрения, в том числи и многопоточность, можно добавить в С++. Это вам никак не поможет разобраться.
Это проблема вашего подхода. Обозначу проблему ещё раз. Нет смысла делать быстрее за счёт изменения подхода применяемого в С++. Вы этим ничего ничего не добиваетесь и причина простая. Всё это подходы можно точно так же добавить в С++ и они на него повлияют точно так же, даже больше.
Я в очередной раз спрошу. Зачем? История аналогичная потоком. Вы пытаетесь похакать, добавить jemalloc, выкинуть стандартную библиотеку, добавить «zero-copy режиме». И все эти ухищрения нужны для того, что-бы ещё даже не догнать С++-версию без всего это.
Зачем вы сравнивали однопоточную и многопоточную версию? Думали, что я не добавлю многопоточную? Зачем вы занимаетесь этим шаманством? Вы думаете, я не добавлю его в С++-код? К чему эта пыль в глаза?
Как строка связана с utf8? Как минимум существуют строки без юникода, а даже если с юникодом — существуют разные форматы юникода.
Это базовая логика. Почему именно она у вас медленнее неважно. Факт есть факт — она медленнее, со всеми вытекающими.
1) уберите. 2) у меня в С++ коде всё копируется и нет проблем. 3) у меня никого оврехеда нет. Почему?
Зачем вы добавили С++-зависимость к коду на rust? Ваши биндинги не могут во флаги?
Нет. Я уже говорил, что у вас 180ms systime. Такого быть не должно.
Не совсем понимаю причём тут пользовательский ввод, когда читаются бинарные данные из файла. Если мне понадобиться валидация utf-8 — я вопрошу её явно. Раст ведь декларирует zero-cost-абстракции?
Так же, я говорил об ином. Для того, что-бы догнать наивный, кое-как написанный С++-код приходится что-то оптимизировать, хакать, страдать. И даже всего этого недостаточно.
У меня используется парсер в dom-режиме. В sax-режиме этот парсер куда быстрее, но до «максимально» и даже «быстрого» ему очень далеко. Не вижу смысла заниматься оптимизацией изначального тупикового подхода.
Всё это не имеет отношения к проблеме. У вас базовая логика почти в 2раза медленнее. Это значит, что абсолютно неважно какой там оверхед и как сложно что-то распараллелить.
Есть базовое обстоятельство. Трупут парсинга объекта и его обработки почти в два раза ниже. В этот самый трупут всё и упирается. На сколько угодно потоков этот трупут можно множить, но он останется лишь в районе 0.5. Ведь у С++ есть столько же потоков.
Это просто фокус. Работает он следующим образом. Есть трупут на одном потоке у С++ — это 1.0. Есть раст, у него трупут на потоке 0.5. Есть некий коэффициент(какой угодно, но положительный). Из этого следует, что добавляя потоки(один, два, десять) — мы когда-нибудь дойдём до этой единице.
Если же взять одинаковое кол-во потоков, одинаковый коэффициент роста за поток, то первый случай будет всегда быстрее. И быстрее в те самые 2 раза.
Только сейчас увидел:
$ time ../habr2_cpp/a.out 1.json
[...]
real 0m0.653s
user 0m0.453s
sys 0m0.188s
Судя по sys — с системой явно что-то не то.
Результат был понятен изначально:
//C++ 4 потока
real 0m0,137s
user 0m0,557s
sys 0m0,012s
//C++ 1 поток + taskset на 1 ядро
real 0m0,526s
user 0m0,510s
sys 0m0,016s
//далее 400 мегабайт json
//C++ 4 потока + taskset на 4 ядра
real 0m0,547s
user 0m2,057s
sys 0m0,042s
//C++ 1 поток + taskset на 1 ядро
real 0m2,074s
user 0m2,034s
sys 0m0,040s
//rust + taskset на 4 ядра
real 0m0,956s
user 0m3,696s
sys 0m0,070s
Правильный рост, никакого оверхеда, никакого изменения кода. Написано максимально наивно.
Ещё важнее то, насколько просто написать оптимальную(если мы говорим про оптимальные программы) программу. В одном случае берётся стандартная библиотека и просто пишется код «как попало» без каких-то попыток что-то оптимизировать. Единственная функция, которая там написана хоть как-то адекватно — это main::next. В другом случае требуются замена стандартных функций, множество хаков, тюнинга библиотек и прочего. Но даже этого недостаточно.
На тему распараллеливания. Тут всё куда сложнее. В подавляющем большинстве случаев попытка распараллелить задачу малоэффективна, либо попросту бессмысленна. Подобные мы видим и тут.
В данном случае распараллеливание достигается за счёт запуска отдельных инстансов задачи. Но проблема никуда не уходит. Трупут каждого потока так и остался лишь чуть большим от 0.5 от С++.
Запустить задачи в параллель везде и всюду так же просто, особенно используя готовые библиотеки для организации пайпов. Но в данном случае, как и в большинстве других, всё упирается в трупут парсинг+process_object. И от этого никуда не уйти.
Если задача ~линейно параллелится, то cputime не должно расти. Если же оно растёт — что-то делается не так.