Comments 21
Я понимаю, что перевод, но в статье прям не хватает краткого описания, как собственно string_view устроен. Что он просто хранит, условно, адреса первого и последнего байта исходных строковых данных. Большинство итоговых выводов статьи стали бы очевидны уже после этой информации (как и подводные камни такой реализации, вроде UB при изменении содержимого исходной строки).
Шёл 2023 год, а в статьях GCC 6.3. Не удивительно при оригинале статьи из 2017 года. При этом ни про устройство, ни про гарантии и потенциальный UB от повисших указателей. `string_view` - замечательная вещь, но хоть какие-нибудь размышления и сравнения с char* не помешали бы.
Ну и воспользуюсь случаем и порекламмирую panda::string. CopyOnWrite(CoW), все те же substr тоже без копирований и аллокаций, но с полной гарантией безопасности. Тот же SSO на 23 байта. Цена полной безопасности дешёвого subbstr - CoW не лучшим образом дружит с потоками. Но если не шарить стейт и передавать копии данных аккуратно, ну или как мы вообще не использовать потоки, то это совсем не цена.
Просто любопытно, sizeof(panda::string) vs sizeof(std::string) - одинаковые или нет?
Сложно утвержать сразу про все реализации std, но скорее нет, чем да. На 64-битной архитектуре sizeof(panda::string) == 40. Стандартная у меня 32 (Debian 11, GCC 10.2). То есть просто так передавать по значению тоже может быть чуть-чуть дороговато, лучше бы по ссылке. Но скопировать себе для хранения - ни о чём на фоне аллокации и копии в std.
CopyOnWrite(CoW)
А разве это не запрещено последними стандартами?
Strong Proposal
This change disallows copy-on-write implementations. For those implementations using copy-on-write implementations, this change would also change the Application Binary Interface (ABI).
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2534.html
Запрещено. В силу требований потоковой безопасности в первую очередь. Вот только не всем она нужна, особенно с учётом её цены. Такой же пример - std::shared_ptr, его атомарный счётчик слишком уж дорог для многих применений. Имхо, это нарушение базового принципа "не платить за то, что не используешь". Поэтому мы отказались от этих довольно слабых гарантий (класс в целом всё равно не thread-safe) в пользу производительности в одном потоке.
P.S. Струкутуру проекта поправили, ссылка изменилась panda::string
вот интересно, автор проводит какие то исследования производительности и не пишет в какой конфигурации он этот тестовый пример компилировал в Release или в Debug. И если это Release то с какими ключами оптимизации?
Может я отстал от жизни и эти настройки какие-то стандартные что ли теперь? Или эти настройки теперь (для новых стандартов С++) не влияют на производительность?
Может кто-то пояснить?
Из своего опыта я помню что в Release компилятор не то что вызовы к переопределенной функции может выкинуть, он может и целые переменные проигнорировать в коде.
Назовите три причины почему надо передавать std::string_view в функции по значению? :)
Из-за этого так бы задан вопрос?
Развёрнутые ответы с ассемблером: https://quuxplusone.github.io/blog/2021/11/09/pass-string-view-by-value/
Там в тексте есть такой пассаж:
In the
byvalue
case, thestring_view
is passed in the register pair(%rdi, %rsi)
, so returning its “size” member is just a register-to-register move.
Стал искать, что это за регистровая пара %rdi, %rsi
? В других процессорах бывает, что два регистра объединяются в регистровую пару, представляя как бы один регистр в два раза большего размера. Если действительно в x86 существует регистровая пара из 64-х битных rdi и rsi, то эта пара должна иметь размер 128 бит.
Но команда movq %rsi, %rax
перебрасывает значение из 64-битного rsi в 64-х битный rax. При чем тут регистровая пара?
Кроме того, я нигде не нашел информации что rdi и rsi способны объединяться в пару. Да, у них есть связь, потому что rsi - это регистр-источник (source), а rdi - это регистр-приемник (destination), и, видимо они индексные (i) потому что используются в командах типа repe movsb. Но это же не регистровая пара.
В общем, автор меня запутал. Что он хотел сказать?
Блин, неужели было сложно поставить номера строчек в примерах кода?
Давно не писал на С++, но там все такой же древний, архаичный вывод сообщений в std::cout, как этот?
std::cout << "std::string_view::substr: " << durStringView.count() << " seconds" << std::endl;
Нормального, удобного форматирования до сих пор нет?
Вообще, стандартному С++ давно не хватает строкового пула. Как в Lua. Всё настолько сурово, что я уже всерьез хочу прикрутить рантайм Luajit к С/С++ проекту просто ради ссылочной системы на иммутабельные строки.
Так если есть список строковых литералов, то оно и так оптимизировалось и складировалось в .rodata. Или речь про динамически считываемые строки которые потом не меняются?
std::string совершенно плевать на строковые литералы в чистом виде
сравнение строк все равно через memcmp идет
std::string в поле класса все равно хранит копию данных
строка может прийти из внешнего документа (json, например) Суть строкового пула, что память на "ещё одну" строку выделяться не будет (кроме ссылки). А когда 95% времени выполнения процесса занимает malloc() - это становится существенно
Ближайшее, я так понимаю, boost::pool. А так согласен, с иммутабельными вещами в плюсах довольно плохо, но если очень хочется, то можете написать некоторый пропозал в стандарт.
ух ты!
оказывается, что если вместо данных хранить указатель на них, то копирование будет работать быстрее!
C++17 — std::string_view и никакого копирования