#cpp
Инициализация 2/2.
Непонятно что делать с этой инфой. Давайте посмотрим на пару примеров, которые помогут сделать выводы. Самый простой:
Вот я хочу инициализировать переменную
Fuck. Это тоже объявление функции (оно скомпилируется и вы получите ошибку в совсем другом месте!). Оставим разбор этого выражения читателю.
Из забавного (если конечно больно можно считать за смешно) следующие кейсы:
Будет ли первое компилироваться? Конечно же нет. Наверное тогда и второе? Ошибка. Второе это оператор запятая, потому всё корректно. Третье, четвёртое и шестое (по аналогии с 3м), очевидно🎧 , тоже CE. Пятое по аналогии со вторым OK.
Учитывая все эти кейсы, вероятно стоит всегда инициализировать ваши переменные с помощью фигурных скобок. Просто чтобы избавиться от вероятных проблем.
Из интересного ещё следующий момент. В C++11 следующая запись означала
Однако в C++14 семантику подобного изменили, где это стало
А что же тут? Это без сомнений🥴 , не правда ли? Говорят, что семантику такого поправлять не будут. Может просто запретят что-то такое делать в будущем (или нет, who knows). Мб потому стоит ещё и отказаться от инициализации с помощью
Хотя есть аргумент за такой стиль. У вас будет одинаковый способ “инициализации” переменных и типов:
Для типов тогда лучше явно указывайте тип:
Вот эту
Ещё можно заметить, что такой способ работает (работал*) только для movable/copyable типов, иначе CE:
Ну и всякие другие проблемы бывают. Например с производительностью некоторых типов.
Чисто из опыта совет: помните, что значит первая буква в AAA — Almost. Потому что иногда поиск типа переменной становится неприятным муторным занятием, попросту тратящим время.
Ещё классический момент это инициализация вектора:
Ну тут всё понятно. Не будем обсуждать.
В докладе Nicolai Josuttis можно посмотреть про это всё счастье подробнее. Там ещё много маслин словить можно.
Инициализация 2/2.
Непонятно что делать с этой инфой. Давайте посмотрим на пару примеров, которые помогут сделать выводы. Самый простой:
int m();Вот я хочу инициализировать переменную
m по месту. Но, учитывая most vexing parse, это у вас не переменная, а объявление функции. Всё сломалось. Или гораздо более стрёмный кейс:vector<int> v(istream_iterator<int>(cin), istream_iterator<int>());Fuck. Это тоже объявление функции (оно скомпилируется и вы получите ошибку в совсем другом месте!). Оставим разбор этого выражения читателю.
Из забавного (если конечно больно можно считать за смешно) следующие кейсы:
int i(1, 2);
int i = (1, 2);
int i = int(1, 2);
auto i(1, 2);
auto i = (1, 2);
auto i = int(7, 9);Будет ли первое компилироваться? Конечно же нет. Наверное тогда и второе? Ошибка. Второе это оператор запятая, потому всё корректно. Третье, четвёртое и шестое (по аналогии с 3м), очевидно
Учитывая все эти кейсы, вероятно стоит всегда инициализировать ваши переменные с помощью фигурных скобок. Просто чтобы избавиться от вероятных проблем.
Из интересного ещё следующий момент. В C++11 следующая запись означала
std::initializer_list<int>{1}:auto i{1};Однако в C++14 семантику подобного изменили, где это стало
int{1}.
auto i = {1};А что же тут? Это без сомнений
init_list. Т.е. вы меняете тип объекта при инициализации с помощью =. Конечно интуитивно это вроде понятно, но кринж=. Просто на всякий случай. Хотя есть аргумент за такой стиль. У вас будет одинаковый способ “инициализации” переменных и типов:
auto x = …;
using T = …;Для типов тогда лучше явно указывайте тип:
auto i = int{1} (это всё мысли из AAA от Herb Sutter). В защиту такого подхода приводится поинт, что вы не забудете инициализировать вашу переменную. Хотя и тут есть траблы:std::string x = “1”;
auto x = “1”s; // C++14Вот эту
s в конце (литерал) очень легко забыть/не обратить на неё внимания. Ещё можно заметить, что такой способ работает (работал*) только для movable/copyable типов, иначе CE:
auto a = std::atomic<int>{9}; // CE before C++17Ну и всякие другие проблемы бывают. Например с производительностью некоторых типов.
Чисто из опыта совет: помните, что значит первая буква в AAA — Almost. Потому что иногда поиск типа переменной становится неприятным муторным занятием, попросту тратящим время.
Ещё классический момент это инициализация вектора:
std::vector<int> v(3, 1); // 1 1 1
std::vector<int> v{3, 1}; // 3 1Ну тут всё понятно. Не будем обсуждать.
В докладе Nicolai Josuttis можно посмотреть про это всё счастье подробнее. Там ещё много маслин словить можно.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11❤1 1
#cpp
1. Женя писал про попытку закоммитить в clang реализацию
2. А Саша писал у себя про self-move. Почитайте. Там ещё в комментах есть интересное.
3. Тут краткий и понятный поинт про то, почему лучше использовать
А тут можно посмотреть аналогичный ответ про
4. Небольшая статья про использование
5. Доклад Herb Sutter про видение обобщённого программирования на C++ аж из 2017ого, вполне, тем не менее, актуальный, т.к. многое из рассказанного ещё не завезли.
6. Небольшая статья с заметками про лямбды.
Такой вот минипостик получился.
1. Женя писал про попытку закоммитить в clang реализацию
assume и про то, почему не получилось. Даже как-то и несмешно уже.2. А Саша писал у себя про self-move. Почитайте. Там ещё в комментах есть интересное.
3. Тут краткий и понятный поинт про то, почему лучше использовать
std::make_unique вместо прямой передачи указателя в конструктор std::unique_ptr. В целом понятно. А тут можно посмотреть аналогичный ответ про
std::make_shared (разница гораздо более ощутимая, но не такая однозначная).4. Небольшая статья про использование
aligned new.5. Доклад Herb Sutter про видение обобщённого программирования на C++ аж из 2017ого, вполне, тем не менее, актуальный, т.к. многое из рассказанного ещё не завезли.
6. Небольшая статья с заметками про лямбды.
Такой вот минипостик получился.
👍14
#common
Базово про кодировки.
Очень важно различать два основных понятия: текст и его представление в виде байт. Есть две основные операции перевода из одной сущности в другую: из текста в байты с помощью какой-то кодировки (обычно называется encode), и наоборот (decode).
Собственно кодировка это способ/правило/функция отображения текста в байты и наоборот.
Одной из первых адекватных кодировок стала ASCII. Она в соответствие каждому символу ставит семибитовый код (т.е. хранится 128 символов). 32 символа - управляющие, остальные - стандартные английские буквы, цифры, знаки препинания и т.д. 8й бит использовался для контроля ошибок. Позже она была расширена до 256 символов (задействованы все 8 битов). Однако уже успели появиться другие однобайтовые кодировки, которые расширяли изначальные 128 символов. Например KOI-8 — кириллическая кодировка, имеющая интересную особенность: в случае, если используемый редактор не поддерживает эту кодировку, при преобразовании кириллических символов в ASCII получался транслит; соответственно русские буквы шли не по алфавиту. Или CP866 — кодировка, используемая в MSDOS, — и CP1251 — дефолтная кодировка при использовании кириллицы в windows до windows 10.
Со временем 256 символов конечно же перестало хватать. И наличие различных кодировок не является универсальным решением. Потому родился Unicode. Он также является отображением из кода в символ, только размеры чуть больше: 2^21 символов.
Unicode реализуют несколько основных кодировок:
- UTF-32. Каждый символ занимает ровно 4 байта. С ней легче всего работать, но она занимает больше всего памяти из-за фиксированного размера.
- UTF-16. Каждый символ представлен либо 2, либо 4 байтами.
- UTF-8. Символ кодируется 1, 2, 3 или 4 байтами. ASCII-часть кодируется одним байтом, причём коды символов полностью совпадают.
Устройство UTF-8 (наиболее популярная из вышеназванных).
Если говорить об ASCII-символе, то в начале идёт 0, а потом 7 бит символа.
Если символ не ASCII, тогда нужно понять, сколько байт нужно для представления символа. Для этого в самом первом байте вместо нуля пишется некоторое количество единиц. Это количество и указывает на то, сколько байт занимает символ. Т.е. если он занимает 3 байта, то пишется 111, после чего [что логично] идёт 0. Далее идут уже значащие для кода биты. Каждый последующий байт начинается с 10, после чего идут 6 значащих битов.
Зачем нужна такая избыточность🤔 ?
При разработке кодировки требовалось сохранить обратную совместимость с уже большой кодовой базой, написаной на C, в котором нулевой символ является знаком окончания строки. Как видим, нулевой символ в UTF-8 только один. Ещё тут выполняется такое свойство, что ни один символ не является префиксом другого, что позволяет использовать некоторый набор старых функций, работающих с ASCII с UTF-8 (например поиск подстроки в строке).
Собственно если посчитать, сколько символов мы можем задать подобным образом, получится что-то около 2^21.
Вот хорошая статья про это всё.
Базово про кодировки.
Очень важно различать два основных понятия: текст и его представление в виде байт. Есть две основные операции перевода из одной сущности в другую: из текста в байты с помощью какой-то кодировки (обычно называется encode), и наоборот (decode).
Собственно кодировка это способ/правило/функция отображения текста в байты и наоборот.
Одной из первых адекватных кодировок стала ASCII. Она в соответствие каждому символу ставит семибитовый код (т.е. хранится 128 символов). 32 символа - управляющие, остальные - стандартные английские буквы, цифры, знаки препинания и т.д. 8й бит использовался для контроля ошибок. Позже она была расширена до 256 символов (задействованы все 8 битов). Однако уже успели появиться другие однобайтовые кодировки, которые расширяли изначальные 128 символов. Например KOI-8 — кириллическая кодировка, имеющая интересную особенность: в случае, если используемый редактор не поддерживает эту кодировку, при преобразовании кириллических символов в ASCII получался транслит; соответственно русские буквы шли не по алфавиту. Или CP866 — кодировка, используемая в MSDOS, — и CP1251 — дефолтная кодировка при использовании кириллицы в windows до windows 10.
Со временем 256 символов конечно же перестало хватать. И наличие различных кодировок не является универсальным решением. Потому родился Unicode. Он также является отображением из кода в символ, только размеры чуть больше: 2^21 символов.
Unicode реализуют несколько основных кодировок:
- UTF-32. Каждый символ занимает ровно 4 байта. С ней легче всего работать, но она занимает больше всего памяти из-за фиксированного размера.
- UTF-16. Каждый символ представлен либо 2, либо 4 байтами.
- UTF-8. Символ кодируется 1, 2, 3 или 4 байтами. ASCII-часть кодируется одним байтом, причём коды символов полностью совпадают.
Устройство UTF-8 (наиболее популярная из вышеназванных).
Если говорить об ASCII-символе, то в начале идёт 0, а потом 7 бит символа.
Если символ не ASCII, тогда нужно понять, сколько байт нужно для представления символа. Для этого в самом первом байте вместо нуля пишется некоторое количество единиц. Это количество и указывает на то, сколько байт занимает символ. Т.е. если он занимает 3 байта, то пишется 111, после чего [что логично] идёт 0. Далее идут уже значащие для кода биты. Каждый последующий байт начинается с 10, после чего идут 6 значащих битов.
Зачем нужна такая избыточность
При разработке кодировки требовалось сохранить обратную совместимость с уже большой кодовой базой, написаной на C, в котором нулевой символ является знаком окончания строки. Как видим, нулевой символ в UTF-8 только один. Ещё тут выполняется такое свойство, что ни один символ не является префиксом другого, что позволяет использовать некоторый набор старых функций, работающих с ASCII с UTF-8 (например поиск подстроки в строке).
Собственно если посчитать, сколько символов мы можем задать подобным образом, получится что-то около 2^21.
Вот хорошая статья про это всё.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍17❤4🔥2
#cpp
Чуть-чуть из доклада Daisy Hollman (не путать с Nadin Holman) на CppCon 2022 (прошлый я упоминал тут).
Fun fact. Можно скрыть общий шаблон с помощью constraint’ов:
Теперь изначальный шаблон недостижим, потому что более частный с точки зрения компилятора предпочтителен, пусть семантически он ровно такой же.
По факту, вы можете полностью заменить определения каких-нибудь стандартных классов (на практике это вероятно уб, но теоретически да).
Правда если вы потом захотите сделать ещё одну специализацию:
то для условного
И поменять сигнатуры на
Это какие-то жоские преки. Словно про поломанность
Второй fun fact (ну это конечно чуваки крутят-вертят как хотят уже).
Помним можно писать user-defined literal templates:
И использовать их можно вот даже для чисел:
Компилятор распарсит 123 в три символа и внутри оператора можете творить что хотите.
Собственно на этом и построена возможность обращаться к элементу тупла через
Ну и всё. Файная обёртка над😁
Но помните, что это работает только для литералов.
Прикона конечно, но кринж, что такие простые хотелки решаются вот такими методами. Может на раст уйду. Хз.
Или вообще кодить перестану.
Чуть-чуть из доклада Daisy Hollman (не путать с Nadin Holman) на CppCon 2022 (прошлый я упоминал тут).
Fun fact. Можно скрыть общий шаблон с помощью constraint’ов:
template <class T>
struct B {
void print() { cout << “unreachable”; }
};
template <> requires true
struct B<T> {
void print() { cout << “new default”; }
};Теперь изначальный шаблон недостижим, потому что более частный с точки зрения компилятора предпочтителен, пусть семантически он ровно такой же.
По факту, вы можете полностью заменить определения каких-нибудь стандартных классов (на практике это вероятно уб, но теоретически да).
Правда если вы потом захотите сделать ещё одну специализацию:
template <class T> requires std::integral<T>
struct B<T> {
void print() { cout << “integral”; }
};то для условного
int получится неоднозначность, потому что по факту это выбор между двумя requires true. Но это можно обойти объявлением всегда истинного концепта:template <class T> concept True = true;И поменять сигнатуры на
template <class T> requires True<T>
// и
template <class T> requires std::integral<T> && True<T>Это какие-то жоские преки. Словно про поломанность
std::void_t вспомнил.Второй fun fact (ну это конечно чуваки крутят-вертят как хотят уже).
Помним можно писать user-defined literal templates:
template <char… Chars>
constexpr auto operator “”_i();И использовать их можно вот даже для чисел:
auto x = 123_i;Компилятор распарсит 123 в три символа и внутри оператора можете творить что хотите.
Собственно на этом и построена возможность обращаться к элементу тупла через
operator[]: template <size_t I> struct index {};// преобразовываем литерал в число
template <char… Chars>
constexpr auto operator “”_i () {
return index<[]{
size_t rv = 0;
for (auto c : {Chars…}) rv = rv * 10 + (c - ‘0’);
}()>{};
}template <class… Ts>
struct index_tuple : std::tuple<Ts…> {
using std::tuple<Ts…>::tuple;
template <size_t I>
auto operator[](index<I>) {
return std::get<I>(*this);
}
};auto t = index_tuple<int, double>{1, 2.2};
cout << t[1_i];Ну и всё. Файная обёртка над
std::get готоваНо помните, что это работает только для литералов.
Прикона конечно, но кринж, что такие простые хотелки решаются вот такими методами. Может на раст уйду. Хз.
Или вообще кодить перестану.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
#nereklama
У нас в Лавке активно развивается b2b направление (это когда мы предоставляем возможность пользоваться нашими техническими решениями партнёрам), в связи с чем ребята семимильными шагами расширяют команду. Потому есть пару вакансий на C++ и python:
- руководитель команды разработки оркестратора в b2b Лавки;
- руководитель команды разработки логистики в b2b Лавки;
- разработчик на python в команду инфраструктуры b2b Лавки;
- разработчик на C++ в команду b2b Лавки.
Я работаю в Лавке уже примерно полтора года и словил сильный буст в хард- и, что довольно важно, софтскиллах. Успел запилить несколько крупных фич, которые имеют ощутимое влияние на пользователей и внутренние процессы. А ещё менторил ребят, собесил, участвовал в яндексовых образовательных проектах и выступал с рассказами на внутренних митапах. В общем, вижу и трогаю множество возможностей для развития в разные стороны. Вы тоже можете : )
Можно подаваться напрямую по вакансиям или написать мне в лс (@vanyakhodor) за рефером.
У нас в Лавке активно развивается b2b направление (это когда мы предоставляем возможность пользоваться нашими техническими решениями партнёрам), в связи с чем ребята семимильными шагами расширяют команду. Потому есть пару вакансий на C++ и python:
- руководитель команды разработки оркестратора в b2b Лавки;
- руководитель команды разработки логистики в b2b Лавки;
- разработчик на python в команду инфраструктуры b2b Лавки;
- разработчик на C++ в команду b2b Лавки.
Я работаю в Лавке уже примерно полтора года и словил сильный буст в хард- и, что довольно важно, софтскиллах. Успел запилить несколько крупных фич, которые имеют ощутимое влияние на пользователей и внутренние процессы. А ещё менторил ребят, собесил, участвовал в яндексовых образовательных проектах и выступал с рассказами на внутренних митапах. В общем, вижу и трогаю множество возможностей для развития в разные стороны. Вы тоже можете : )
Можно подаваться напрямую по вакансиям или написать мне в лс (@vanyakhodor) за рефером.
👾9❤7👍3❤🔥1⚡1
#cpp #common
1. Прикольный доклад Andrei Alexandrescu про мысли о рефлексии в плюсах.
2. В комментах поста про кодировки упоминали про сложность устройства эмодзи. Вот хороший пост про это. У автора оригинала кстати есть канал, где он горит на всякое время от времени: @nikitonsky_pub.
3. Не самый свежий доклад, но довольно общий и широкий. Про то, что особенного в субд в оперативной памяти.
4. В дополнение к посту про инициализацию можно почитать две статьи про
1. Прикольный доклад Andrei Alexandrescu про мысли о рефлексии в плюсах.
2. В комментах поста про кодировки упоминали про сложность устройства эмодзи. Вот хороший пост про это. У автора оригинала кстати есть канал, где он горит на всякое время от времени: @nikitonsky_pub.
3. Не самый свежий доклад, но довольно общий и широкий. Про то, что особенного в субд в оперативной памяти.
4. В дополнение к посту про инициализацию можно почитать две статьи про
std::initializer_list: раз, два.👍10
#cpp #common
Коллеги выпустили YTsaurus в опенсорс. Это крутое распределённое хранилище, на котором у нас держится примерно много всего.
https://github.com/YTsaurus/YTsaurus
https://habr.com/ru/company/yandex/blog/721526/
Коллеги выпустили YTsaurus в опенсорс. Это крутое распределённое хранилище, на котором у нас держится примерно много всего.
https://github.com/YTsaurus/YTsaurus
https://habr.com/ru/company/yandex/blog/721526/
Хабр
YTsaurus: основная система для хранения и обработки данных Яндекса теперь open source
Привет! Меня зовут Максим Бабенко, я руковожу отделом технологий распределённых вычислений в Яндексе. Сегодня мы выложили в опенсорс платформу YTsaurus — одну из основных инфраструктурных...
❤15👍6🔥3🤮2
#cpp
Атрибуты в C++ 1/2.
Начнём со стандартных.
С точки зрения синтаксиса поддерживается два основных варианта:
Из примеров с cppreference ещё можно увидеть такое:
- атрибуты с аргументами:
- с неймспейсом:
Атрибуты можно применять к чему угодно: переменным, типам, функциям, классам, блокам кода и целым translation units. Но конечно это должно согласовываться с его реализацией.
В msvc игнорится (даже с С++20), потому надо юзать
Ещё примерчики можно посмотреть на cppref.
можно писать
Тут и
Атрибуты в C++ 1/2.
Начнём со стандартных.
С точки зрения синтаксиса поддерживается два основных варианта:
[[attribute_name]] // C++11
[[using attribute-namespace: attribute_list]] // C++17Из примеров с cppreference ещё можно увидеть такое:
- атрибуты с аргументами:
[[deprecated(“text”)]];- с неймспейсом:
[[gnu::unused]].Атрибуты можно применять к чему угодно: переменным, типам, функциям, классам, блокам кода и целым translation units. Но конечно это должно согласовываться с его реализацией.
[[noreturn]] говорит, что функция не возвращает control flow кода. Например она вызывает std::terminate, бросает исключение, содержит бесконечный цикл и т.д. В C можно использовать _Noreturn или noreturn для аналогичного.[[carries_dependency]] может использоваться в случаях, когда у вас memory order consume. Я вообще в этих ваших многопоточках не оч шарю, но говорят, что введение такого понятия не самое удачное решение. Мб подскажете адекватные кейсы использования?[[deprecated]], [[deprecated(“message”)]] говорит, что какой-то объект является deprecated (обычно компиляторы выдают ворнинги при использовании помеченых штук).[[fallthrough]] говорит, что провал в следующий case в switch является намеренным -> компилятор не будет подсказывать ворнингами об этом.[[nodiscard]], [[nodiscard(“message”)]] применяются к функции и означают, что не стоит игнорировать её возвращаемое значение. Если да, получите ворнинг.[[maybe_unused]] говорит, что используемый объект может быть просто объявлен, но нигде в коде не заюзан. Если атрибут есть, вы не получите ворнинга. Удобно иногда, когда пишешь код частями и хочешь проверять, компилится ли, наставить их всему, что мешает. Потом с продвижением по решению таски поубирать. Или когда у вас какой-то общий интерфейс, но не все аргументы нужны для новой реализации.[[likely]], [[unlikely]] говорят, что некоторый code flow выполняется чаще/реже. Соответственно компилятор может использовать эти знания и оптимизировать hot path. Или наоборот. Мб вы знаете кейсы, когда это прям помогает? Имхо компиляторы сейчас неплохо с такими вещами и так справляются.[[no_unique_address]] говорит, что член класса может не обладать уникальным адресом в памяти, из-за чего несколько членов класса потенциально могут пересекаться в памяти. Подробнее расскажу про него вместе с empty base class optimization чуть позже.В msvc игнорится (даже с С++20), потому надо юзать
[[msvc::no_unique_address]].[[assume]] говорит, что некоторое выражение, которое вы в него засунете, будет true c момента указания атрибута. Это может помочь компилятору провести какие-то оптимизации (опять не уверен, что это сильно поможет): int x = f();
[[assume(x > 0)]];
// далее это утверждение может использоваться для более эффективных оптимизацийint z = x;
[[assume((h(), x == z))]]; // после вызова h() переменные всё ещё будут равны[[assume((g(z), true))]]; // g(z) вернёт trueЕщё примерчики можно посмотреть на cppref.
using в атрибутах это про сокращение записи. Например это может быть полезно для нестандартных атрибутов. Вместо [[gnu::always_inline]] [[gnu::hot]] [[gnu::const]]можно писать
[[using gnu: always_inline, hot, const]]Тут и
using, и сразу несколько атрибутов в одном attribute-specifier (это [[]]).👍14
#cpp
Атрибуты в C++ 2/2.
Кроме стандартных атрибутов есть ещё всякие компилятороспецифичные.
Тут стоит сказать, что хотя конечно в целом компиляторы в основном умеют работать с атрибутами со стандартным attribute-specifier (
По самим атрибутам.
Если вы уверены, что что-то нужно сто процентов заинлайнить, в gcc можно использовать
Есть и довольно специфические атрибуты. Например в clang атрибут
Или например
Или например
Из полезного ещё есть
В msvc подобного результата (насколько я знаю) можно достичь с прагмами. Рядышком есть ещё
Из прекрасного про msvc факт, что
И много других:
Больше атрибутов можно увидеть тут: clang, gcc.
Атрибуты в C++ 2/2.
Кроме стандартных атрибутов есть ещё всякие компилятороспецифичные.
Тут стоит сказать, что хотя конечно в целом компиляторы в основном умеют работать с атрибутами со стандартным attribute-specifier (
[[]]), в зависимости от компилятора часто используются формы вроде __attribute__(()), __declspec() или, реже, прагм. По самим атрибутам.
Если вы уверены, что что-то нужно сто процентов заинлайнить, в gcc можно использовать
__attribute__((always_inline)). Он игнорирует все лимиты, по которым решается, инлайнить ли функцию/метод, и игнорирует -fno-inline. Есть вот небольшая преза с бенчмарками по этому атрибуту. Есть и довольно специфические атрибуты. Например в clang атрибут
[[clang::preferred_name(some_name)]] может быть применён к шаблону класса и указывает предпочтительный способ присвоения имени специализации шаблона. Или например
using_if_exists, который позволяет не ругаться на юзинги, если в них используются несуществующие/необъявленные сущности. Таким образом ошибки откладываются до момента использования:namespace empty_namespace {};
__attribute__((using_if_exists))
using empty_namespace::does_not_exist; // no error!
does_not_exist x; // error: use of unresolved 'using_if_exists'Или например
noescape к параметру-указателю говорит, что этот указатель никуда не “убежит”. Т.е. вы его ни во что, что может использоваться дальше, не присвоите (вызвать free можно):int* gp;
void no(__attribute__((noescape)) int* p) {
*p += 100; // OK.
}
void yes(__attribute__((noescape)) int* p) {
gp = p; // Not OK.
}Из полезного ещё есть
__attribute__((weak)) в clang (__attribute_((weak_import)) в gcc). Weak линковка это когда вы можете пометить некоторую реализацию функции подобным атрибутом, но если будет найдена другая без подобного атрибута, ей отдаётся предпочтение. Типичный пример — перегрузка operator new/operator delete. В msvc подобного результата (насколько я знаю) можно достичь с прагмами. Рядышком есть ещё
[[selectany]], которые разрешает компилятору выбрать любую реализацию. Из прекрасного про msvc факт, что
[[no_unique_address]], как уже говорилось, на нём не работает. Нужно юзать специфичный компиляторный. Но это только если у вас msvc. Если хотите под windows собирать с clang’ом, земля вам пухом.И много других:
enable_if (который делает примерно то, что вы и думаете), format (который говорит, что функция принимает printf/scanf-like format строку и аргументы для неё), malloc (говорит, что функция ведёт как функция для выделения памяти из системы, no_sanitize (отключающий проверки санитайзеров для конкретной функции или глобальной переменной), constructor (позволяет выполнить некоторый код до запуска main(), подробнее Женя писал в https://news.1rj.ru/str/cxx95/109) и destructor (после выхода из main()), call_once (утверждающий, что параметр-функтор вызывается ровно однажды). Больше атрибутов можно увидеть тут: clang, gcc.
👍10
#cpp
Пачка фактов.
0. Оч кайфовый доклад про приемлемый способ починить большинство текущих проблем в C++: link.
Так сказать, never gonna give you up.
1. Вместо того, чтобы писать
https://godbolt.org/z/9KT15jPor
Такой подход удобно обобщается на любые другие операторы. Всем советую, потому что если дать железякам свободу принимать решения, рано или поздно они нас всех захватят.
Но лучше всегда возвращать true, потому что все равны.
2. Подруга мамы назвала своего сына
3. Вы же знали про оператор стремления к нулю?
4. А про оператор головастик?
https://habr.com/ru/post/258811/
5. В Польше запретили
6. Если вам не нравится
Пачка фактов.
0. Оч кайфовый доклад про приемлемый способ починить большинство текущих проблем в C++: link.
Так сказать, never gonna give you up.
1. Вместо того, чтобы писать
operator==, гораздо лучше отдать пользователю это решение. Пусть при каждом сравнении он сам скажет, равны ли объекты! https://godbolt.org/z/9KT15jPor
Такой подход удобно обобщается на любые другие операторы. Всем советую, потому что если дать железякам свободу принимать решения, рано или поздно они нас всех захватят.
2. Подруга мамы назвала своего сына
std::victor.3. Вы же знали про оператор стремления к нулю?
while (x —-> 0);4. А про оператор головастик?
https://habr.com/ru/post/258811/
5. В Польше запретили
std::abort.6. Если вам не нравится
std::vector<bool>, можете использовать std::vector<std::optional<std::monostate>>. Или даже лучше std::vector<std::variant<std::true_type, std::false_type>>.😁30🔥7🤯6🤣5🐳2🌚2👍1
#cpp
Два факта про неочевидные (и к счастью редкие) кеки C++.
Первое про union.
Важно помнить, что у
И собственно сам кек. Т.к. по дефолту деструктор удалён, то в данном случае
Вы спросите, а почему в деструкторе ничего не делаем? Я отвечу: потому что этим должен заниматься пользователь
Ну и чтобы соблюсти формальности, уточним, что вызывать деструктор
а как
потому что это же алиас.
Второе про function-try-block.
В C++ существует альтернативный синтаксис для определения тела функции, позволяющий навесить на него целиком перехват и обработку исключений:
Эта фича позволяет нам ловить исключения из списка инициализации в конструкторах:
Правда тут есть один опасный момент. После обработки ошибки в
А что с деструкторами? Из них же крайне нежелательно бросать исключения, потому словить всё было бы удобно. Но тут пранк — поведение такое же, как и в конструкторах. Т.е.
Таким образом словили кейс, когда
На всякий напомню, что ловить исключения через
Ну а плюсы что. Кринж.
Два факта про неочевидные (и к счастью редкие) кеки C++.
Первое про union.
Важно помнить, что у
union в C++ могут быть конструкторы/деструкторы, потому что важно уметь выбирать кого вы конструируете по умолчанию, и соответственно уметь вызывать корректный деструктор в зависимости от активного члена. И вот если у вас объектам надо вызывать конструктор, и вы union’у его не реализовываете, то код просто не компилируется, потому что дефолтный по умолчанию удалён. Но это лирическое отступление. Имеем такой union:union U {
std::string s_;
std::vector<int> v_;
U(std::string s) { new (&s_) std::string(s); }
U(std::vector<int> v) { new (&v_) std::vector<int>(v); }
~U() {}
};И собственно сам кек. Т.к. по дефолту деструктор удалён, то в данном случае
= default для него означает, что деструктора нет. Т.е. поведение при пустом кастомном деструкторе и при ~U() = default отличается.Вы спросите, а почему в деструкторе ничего не делаем? Я отвечу: потому что этим должен заниматься пользователь
union снаружи. Он знает, какой член является активным. Он пусть с этим и разбирается. Ну и чтобы соблюсти формальности, уточним, что вызывать деструктор
std::string не как s_.~string()а как
s_.~basic_string<char>()потому что это же алиас.
Второе про function-try-block.
В C++ существует альтернативный синтаксис для определения тела функции, позволяющий навесить на него целиком перехват и обработку исключений:
// Стандартный способ
void f() {
try {
may_throw();
} catch (...) {
handle_error();
}
}
// Альтернативный синтаксис
void f() try {
may_throw();
} catch (...) {
handle_error();
}Эта фича позволяет нам ловить исключения из списка инициализации в конструкторах:
struct S {
A a_;
B b_;
S(A a, B b) try : a_(a), b_(b) {
} catch (…) {
// handle
}
};Правда тут есть один опасный момент. После обработки ошибки в
catch ошибка будет неявно проброшена выше. Это в целом логично: если при инициализации полей класса вылетело исключение, мы никак не можем исправить ситуацию и починить объект.А что с деструкторами? Из них же крайне нежелательно бросать исключения, потому словить всё было бы удобно. Но тут пранк — поведение такое же, как и в конструкторах. Т.е.
catch из function-try-block неявно пробрасывает исключение выше. Вы только что нарушили неявный noexcept(true). Но в отличие от конструкторов, для деструкторов есть мегакостыль: просто добавьте return!struct A {
~A() try {
throw std::runtime_error("err");
} catch (…) {
std::cout << “ERROR”;
return; // исключение не будет перевыброшено!
}
};Таким образом словили кейс, когда
return в конце void-функции меняет её поведение.На всякий напомню, что ловить исключения через
catch (…) не очень хорошо. Вообще можно почитать мою статью про обработку ошибок. Ну а плюсы что. Кринж.
👍13😱5😁3❤1
#common
Про ревью.
Что вы думаете, когда видите что-то такое:
> Это какой-то бред.
Оценочное суждение. Никакой конкретики, что же сделано не так. Никаких предложений по исправлению.
> Так лучше не делать.
Как??????????
Писать комменты в ревью адекватно — важный скилл. Как минимум чтобы коллеги не думали, что вы токсичный и неприятный. Как максимум, чтобы исправление ревью было простым и не тратило время вас двоих на выяснение, что же имелось в виду.
Примеры выше имеют несколько важных недостатков: они [вероятно] грубые и не несут никакой конкретики (ваш коллега не имеет никакого контекста о том, что было у вас в голове во время написания комментария).
Как сделать ваше ревью более полезным? Вот несколько набросов:
- цель код ревью в том, чтобы сделать код лучше. Потому при написании комментариев важно фокусироваться именно на коде и написанном, а не на авторе и других аспектах бытия;
- пишите конкретно.
Вот это не будет работать по таким-то причинам: раз, два, три. Можно сделать вот так. Вот ссылка на пример использования/функцию/метод/инструмент.
Донесите вашу мысль максимально чётко, чтобы автор кода понял вас с первого раза;
- не ленитесь и напишите (псевдо)код предлагаемого решения, если оно может показаться нетривиальным.
Вообще максимально старайтесь предлагать какие-то решения (вы же понимаете, как сделать лучше?). Это экономит время;
- по опыту, важно ещё и как вы оставляете комментарий. Если вы предлагаете что-то исправить в формулировке “Можем сделать ….?”, автор имеет полное право сказать “нет”, даже если это решаемый вопрос. Просто потому что не хочет. Напишите лучше что-то вроде “Давай сделаем …”;
- ну и в конце концов, будьте приятными и нетоксичными. Просто по-человечески добрыми. Это всегда приятно. Плюс к фидбеку охотнее прислушиваются, если он написан уважительно.
Будьте добрыми😎
Про ревью.
Что вы думаете, когда видите что-то такое:
> Это какой-то бред.
Оценочное суждение. Никакой конкретики, что же сделано не так. Никаких предложений по исправлению.
> Так лучше не делать.
Как??????????
Писать комменты в ревью адекватно — важный скилл. Как минимум чтобы коллеги не думали, что вы токсичный и неприятный. Как максимум, чтобы исправление ревью было простым и не тратило время вас двоих на выяснение, что же имелось в виду.
Примеры выше имеют несколько важных недостатков: они [вероятно] грубые и не несут никакой конкретики (ваш коллега не имеет никакого контекста о том, что было у вас в голове во время написания комментария).
Как сделать ваше ревью более полезным? Вот несколько набросов:
- цель код ревью в том, чтобы сделать код лучше. Потому при написании комментариев важно фокусироваться именно на коде и написанном, а не на авторе и других аспектах бытия;
- пишите конкретно.
Вот это не будет работать по таким-то причинам: раз, два, три. Можно сделать вот так. Вот ссылка на пример использования/функцию/метод/инструмент.
Донесите вашу мысль максимально чётко, чтобы автор кода понял вас с первого раза;
- не ленитесь и напишите (псевдо)код предлагаемого решения, если оно может показаться нетривиальным.
Вообще максимально старайтесь предлагать какие-то решения (вы же понимаете, как сделать лучше?). Это экономит время;
- по опыту, важно ещё и как вы оставляете комментарий. Если вы предлагаете что-то исправить в формулировке “Можем сделать ….?”, автор имеет полное право сказать “нет”, даже если это решаемый вопрос. Просто потому что не хочет. Напишите лучше что-то вроде “Давай сделаем …”;
- ну и в конце концов, будьте приятными и нетоксичными. Просто по-человечески добрыми. Это всегда приятно. Плюс к фидбеку охотнее прислушиваются, если он написан уважительно.
Будьте добрыми
Please open Telegram to view this post
VIEW IN TELEGRAM
👍46🥰10💔1
#cpp
Ух. Влетел с рассказом про обработку ошибок в плюсах на яндексовый Intern Meetup Week. В целом понятны точки роста, где над чем можно поработать, но опыт очень прикольный и полезный. Запись можно глянуть тут: https://www.youtube.com/live/5stJKC6UGyI?feature=share&t=532
Текстовая версия: https://habr.com/ru/articles/690038/
Ух. Влетел с рассказом про обработку ошибок в плюсах на яндексовый Intern Meetup Week. В целом понятны точки роста, где над чем можно поработать, но опыт очень прикольный и полезный. Запись можно глянуть тут: https://www.youtube.com/live/5stJKC6UGyI?feature=share&t=532
Текстовая версия: https://habr.com/ru/articles/690038/
🔥39
#highload #cpp
Подсобирал ссылочек на разные приконые статьи и доклады.
1. Про то, как discord хранит сообщения. TLDR: история про смену БД.
2. Про переезд с монолита на микросервисы в Twitch: part one, part two.
Появились записи прошлогоднего C++ Russia. Пока правда не на youtube.
3. (Core) библиотеки: идеи и примеры про то, как писать общие библиотеки и различные подходы на двух примерах.
4. Reflection TS: будущее рефлексии в C++.
5. Маленький, но довольно глубокий Type Sanitizer: способ обнаружения нарушений правил strict aliasing в C++.
Подсобирал ссылочек на разные приконые статьи и доклады.
1. Про то, как discord хранит сообщения. TLDR: история про смену БД.
2. Про переезд с монолита на микросервисы в Twitch: part one, part two.
Появились записи прошлогоднего C++ Russia. Пока правда не на youtube.
3. (Core) библиотеки: идеи и примеры про то, как писать общие библиотеки и различные подходы на двух примерах.
4. Reflection TS: будущее рефлексии в C++.
5. Маленький, но довольно глубокий Type Sanitizer: способ обнаружения нарушений правил strict aliasing в C++.
❤12👍4🤯2
#highload
Вышли записи Saint Highload++ 2022. Вот пару ссылочек из интересного. Сначала технические:
0. Какие архитектурные решения в Яндекс Go позволяют запускать десятки продуктовых экспериментов. Олег Ермаков.
Тут интересно с точки зрения того, что всем этим мы пользуемся абсолютно регулярно и постоянно. Обзорно оч хорошо даёт понимание.
Мб как-нибудь доберусь и базово про аб-тесты расскажу.
1. Векторный поиск в ClickHouse. Артур Филатенков.
2. Про историю и будущее поиска. Андрей Аксёнов.
3. Архитектура: история и будущее на примере ВКонтакте. Александр Тоболь.
Ну ВКонтакте со своими костылями это угар какой-то местами. Но доклад приконый и оч живой.
И не очень:
4. Под красным флагом: как инженер может понять, что в проекте происходит что-то не то. Даниил Подольский.
5. Как понять, что проекту плохо, если ты инженер. Юлия Белозёрова.
Тянка, кстати, жоская. Под конец доклада понял, что читал k её статей в vas3k.club. И все кайфовые.
Вышли записи Saint Highload++ 2022. Вот пару ссылочек из интересного. Сначала технические:
0. Какие архитектурные решения в Яндекс Go позволяют запускать десятки продуктовых экспериментов. Олег Ермаков.
Тут интересно с точки зрения того, что всем этим мы пользуемся абсолютно регулярно и постоянно. Обзорно оч хорошо даёт понимание.
Мб как-нибудь доберусь и базово про аб-тесты расскажу.
1. Векторный поиск в ClickHouse. Артур Филатенков.
2. Про историю и будущее поиска. Андрей Аксёнов.
3. Архитектура: история и будущее на примере ВКонтакте. Александр Тоболь.
Ну ВКонтакте со своими костылями это угар какой-то местами. Но доклад приконый и оч живой.
И не очень:
4. Под красным флагом: как инженер может понять, что в проекте происходит что-то не то. Даниил Подольский.
5. Как понять, что проекту плохо, если ты инженер. Юлия Белозёрова.
Тянка, кстати, жоская. Под конец доклада понял, что читал k её статей в vas3k.club. И все кайфовые.
🔥8❤2👍1👎1
#common
Есть желание немножко поразбираться и пописать про поиск и связанные штуки, так что время от времени что-то с этим связаное будет возникать.
Сегодня про задачи и специфику поиска в различных контекстах.
Давайте представим, как может выглядеть простейший поиск. Это примерно просто нахождение подстроки в строке (или во множестве строк). И это вполне себе полезная задача. Например, вы сидите в своей любимой ide и грепаете что-то по файлику. Тут обычно не нужно мутить чего-то чуть более сложного вроде исправления опечаток, семантики запроса и т.д. Нужно просто найти, что просят (может, проигнорировав кейс). Чтобы не делать это втупую, можно заюзать Бойера-Мура или Кнута-Морриса-Пратта. Даже можно какое-нибудь суффиксное дерево по текущему файлику построить, но имхо звучит как оверинжиниринг. И тут это почти наверняка хорошо работает, потому что файлы у вас обычно небольшие (думаю, даже несколько тысяч строк подобным спокойно закрываются). Другой вопрос конечно, как сделать подобное быстро, если вы ищете по целому проекту или по огромной монорепе, но про такой codesearch как-нибудь потом.
Примерно та же задача возникает, если вы хотите искать по логам. Чаще всего вас интересуют какие-то параметры и конкретное вхождение текста. Тут правда уже такими подходами не обойдёшься, потому что объёмы данных огромны (легко порядка единиц-десятков-сотен террабайт). Но всё ещё можно не наворачивать опечаточник, работу с семантикой, ml и прочее.
Это примеры поиска по различным неестественным текстам, что обычному пользователю редко пригождается.
Обычно пользователей интересует поиск по естественным текстам: сайтам с их страничками, статьям, форумам и прочему. Причём поиск тут может вестись разными способами: по коротким фразам целиком, по каким-то длинным фрагментам, по различным ключевым словам (которые надо уметь выделять и оценивать их ценность в запросе), иногда даже по семантике (тут уже надо какой-то ml наворачивать). Учитывая, что хочется, чтобы ваш сайтик искался и индексировался лучше, рядом возникает задача search engine optimization (SEO).
Ну и часто вы хотите искать что-то по какой-то предметной области. Например по товарам в рамках конкретного сайта. Или по твитам. Или по фильмам, если вы какой-нибудь kinogo. В некоторых таких кейсах у вас есть какое-то количество информации об искомых объектах кроме тайтла (например описания). И по-хорошему хочется уметь искать не только по названиям, но и другой информации, но мб учитывая её с меньшим весом.
Тут обычно берут какой-нибудь поисковый движок вроде sphinx, lucene или какой-нибудь другой и кастомят под себя.
Тут получилось просто, абстрактно и общо. Дальше будет чуть больше конкретики.
Есть желание немножко поразбираться и пописать про поиск и связанные штуки, так что время от времени что-то с этим связаное будет возникать.
Сегодня про задачи и специфику поиска в различных контекстах.
Давайте представим, как может выглядеть простейший поиск. Это примерно просто нахождение подстроки в строке (или во множестве строк). И это вполне себе полезная задача. Например, вы сидите в своей любимой ide и грепаете что-то по файлику. Тут обычно не нужно мутить чего-то чуть более сложного вроде исправления опечаток, семантики запроса и т.д. Нужно просто найти, что просят (может, проигнорировав кейс). Чтобы не делать это втупую, можно заюзать Бойера-Мура или Кнута-Морриса-Пратта. Даже можно какое-нибудь суффиксное дерево по текущему файлику построить, но имхо звучит как оверинжиниринг. И тут это почти наверняка хорошо работает, потому что файлы у вас обычно небольшие (думаю, даже несколько тысяч строк подобным спокойно закрываются). Другой вопрос конечно, как сделать подобное быстро, если вы ищете по целому проекту или по огромной монорепе, но про такой codesearch как-нибудь потом.
Примерно та же задача возникает, если вы хотите искать по логам. Чаще всего вас интересуют какие-то параметры и конкретное вхождение текста. Тут правда уже такими подходами не обойдёшься, потому что объёмы данных огромны (легко порядка единиц-десятков-сотен террабайт). Но всё ещё можно не наворачивать опечаточник, работу с семантикой, ml и прочее.
Это примеры поиска по различным неестественным текстам, что обычному пользователю редко пригождается.
Обычно пользователей интересует поиск по естественным текстам: сайтам с их страничками, статьям, форумам и прочему. Причём поиск тут может вестись разными способами: по коротким фразам целиком, по каким-то длинным фрагментам, по различным ключевым словам (которые надо уметь выделять и оценивать их ценность в запросе), иногда даже по семантике (тут уже надо какой-то ml наворачивать). Учитывая, что хочется, чтобы ваш сайтик искался и индексировался лучше, рядом возникает задача search engine optimization (SEO).
Ну и часто вы хотите искать что-то по какой-то предметной области. Например по товарам в рамках конкретного сайта. Или по твитам. Или по фильмам, если вы какой-нибудь kinogo. В некоторых таких кейсах у вас есть какое-то количество информации об искомых объектах кроме тайтла (например описания). И по-хорошему хочется уметь искать не только по названиям, но и другой информации, но мб учитывая её с меньшим весом.
Тут обычно берут какой-нибудь поисковый движок вроде sphinx, lucene или какой-нибудь другой и кастомят под себя.
Тут получилось просто, абстрактно и общо. Дальше будет чуть больше конкретики.
👍8🤔8❤4🐳2🗿2💔1
#cpp #highload
1. Ещё один доклад Daisy Holman про всякие необычные C++ tricks. Можно полистать презу.
Ранее упоминал её тут и тут.
2. Статья про устройство real-time messaging в Slack.
3. Статья про Tinder API Gateway.
4. Migrating Critical Traffic At Scale with No Downtime в Netflix.
5. Monoliths are not dinosaurs.
1. Ещё один доклад Daisy Holman про всякие необычные C++ tricks. Можно полистать презу.
Ранее упоминал её тут и тут.
2. Статья про устройство real-time messaging в Slack.
3. Статья про Tinder API Gateway.
4. Migrating Critical Traffic At Scale with No Downtime в Netflix.
5. Monoliths are not dinosaurs.
👍7❤🔥4
#common
Про то, что в поисковых движках (для полнотекстового поиска) обычно есть.
Интересно, что на самом деле ничего очень специфичного именно для поиска вроде как и нет. Т.е. конечно алгоритмов и подходов внутри подобных решений хватает, но чтобы прям только тут, такого ничего не видно.
Базой почти всегда служит инвертированный индекс — множество id документов для каждого ключевого слова, в которых это самое слово встречается. Выглядит это примерно вот так:
Правда вектор интов это всё-таки упрощение, т.к. чаще всего хочется хранить что-то более сложное. Например структурку
Собственно если у вас именно вектор интов в значении мапки, вы можете его очень сильно пожать. Ловите профит, правда инфы из такого индекса вы получаете мало. Хотя если научиться каким-то образом ужимать весь ваш
Тут правда миллион вопросов возникает: как должен быть устроен
Т.к. индекс обычно поддерживает очень много разной инфы, приходится очень много сжимать, потому что огромные индексы — неэффективно и больно. Из простого и понятного для последовательностей чисел (в нашем случае например для id документов) можно применять дельта-кодирование, когда вы возрастающую положительную последовательность меняете на разницу с предыдущим:
11, 15, 16, 21, 37, …
11, 4, 1, 5, 16, …
Если же у нас и отрицательные, применяют что-то около zig-zag кодирования.
Но вообще как числа жать, уже давно придумали: elias gamma, golomb, rice, huffman и другое. С ними правда тоже вопросики есть, но можно крутиться.
Можно ещё посмотреть в сторону varint (или group varint/pfor/simple9/simple16).
Но в любом случае надо что-то тут делать, потому что сжимать оч важно.
Важной частью ещё является ранжирование. Учитывая развесистые модели, которые для решения этой задачи используются, эта часть может занимать огромное количество времени. И сверху ещё (т.к. обычно там какой-никакой мль), результаты могут быть с вопросом. Тут рядом считают какие-нибудь статистики вроде TF/BM25, которые в поисковых движках уже стали классикой. Иногда ещё движки, если они разрабываются как SaaS решение, могут предоставлять возможность создавать свои факторы для ранжирования и как-то их крутить. Очень полезно в рамках различных предметных областей, т.к. непонятно, где ваша разработка будет использоваться. Хотя конечно почти наверняка делают более специализированные штуки (например в силу специфики работы, видел движки для екома, но скорее внутри, чем снаружи).
Обычно в движке у вас есть ещё какой-то матчинг (чтобы какие-то результаты по запросу получать), опечаточник, саджест, иногда какие-то атрибутивные real-time обновления, чтобы индекс каждый раз заново не варить (иногда умеют делать фулл индексы real-time, но не совсем, а иногда умеют варить только дельту). Короч очень много всего.
И есть конечно же огромная куча подходов и решений, которые часто встречаются в других областях. Например как в бд (потому что движки это по факту специфические базы данных), как в nlp (кодировки, токенизация, морфология, стемминг/лемминг, языковые модели), ну и интеграции для более гибкой настройки (иногда свой диалект SQL, различные возможности подменять этапы работы с текстом, интеграции с моделями для ранжирования, с библиотеками для векторного поиска (если вдруг надо) и чем угодно ещё).
Постик основан на докладе, но ссылку на него я вам пока не дам, потому что он в закрытом доступе. Как появится запись, обязательно поделюсь.
Про то, что в поисковых движках (для полнотекстового поиска) обычно есть.
Интересно, что на самом деле ничего очень специфичного именно для поиска вроде как и нет. Т.е. конечно алгоритмов и подходов внутри подобных решений хватает, но чтобы прям только тут, такого ничего не видно.
Базой почти всегда служит инвертированный индекс — множество id документов для каждого ключевого слова, в которых это самое слово встречается. Выглядит это примерно вот так:
using Index = Map<String, Vector<int>>;Правда вектор интов это всё-таки упрощение, т.к. чаще всего хочется хранить что-то более сложное. Например структурку
Posting (вхождение слова в документ), которая хранит всякие разные дополнительные штуки: id поля в документе (можно потом с разными весами их крутить), позиция слова в документе, различные флаги и что угодно ещё. Собственно если у вас именно вектор интов в значении мапки, вы можете его очень сильно пожать. Ловите профит, правда инфы из такого индекса вы получаете мало. Хотя если научиться каким-то образом ужимать весь ваш
Posting в одно чиселко, можно тут и пооптимизировать. Тут правда миллион вопросов возникает: как должен быть устроен
Map? а Vector<Posting>? Эти структуры могут быть примерно чем угодно в зависимости от вашей прикладной области и требований. Т.к. индекс обычно поддерживает очень много разной инфы, приходится очень много сжимать, потому что огромные индексы — неэффективно и больно. Из простого и понятного для последовательностей чисел (в нашем случае например для id документов) можно применять дельта-кодирование, когда вы возрастающую положительную последовательность меняете на разницу с предыдущим:
11, 15, 16, 21, 37, …
11, 4, 1, 5, 16, …
Если же у нас и отрицательные, применяют что-то около zig-zag кодирования.
Но вообще как числа жать, уже давно придумали: elias gamma, golomb, rice, huffman и другое. С ними правда тоже вопросики есть, но можно крутиться.
Можно ещё посмотреть в сторону varint (или group varint/pfor/simple9/simple16).
Но в любом случае надо что-то тут делать, потому что сжимать оч важно.
Важной частью ещё является ранжирование. Учитывая развесистые модели, которые для решения этой задачи используются, эта часть может занимать огромное количество времени. И сверху ещё (т.к. обычно там какой-никакой мль), результаты могут быть с вопросом. Тут рядом считают какие-нибудь статистики вроде TF/BM25, которые в поисковых движках уже стали классикой. Иногда ещё движки, если они разрабываются как SaaS решение, могут предоставлять возможность создавать свои факторы для ранжирования и как-то их крутить. Очень полезно в рамках различных предметных областей, т.к. непонятно, где ваша разработка будет использоваться. Хотя конечно почти наверняка делают более специализированные штуки (например в силу специфики работы, видел движки для екома, но скорее внутри, чем снаружи).
Обычно в движке у вас есть ещё какой-то матчинг (чтобы какие-то результаты по запросу получать), опечаточник, саджест, иногда какие-то атрибутивные real-time обновления, чтобы индекс каждый раз заново не варить (иногда умеют делать фулл индексы real-time, но не совсем, а иногда умеют варить только дельту). Короч очень много всего.
И есть конечно же огромная куча подходов и решений, которые часто встречаются в других областях. Например как в бд (потому что движки это по факту специфические базы данных), как в nlp (кодировки, токенизация, морфология, стемминг/лемминг, языковые модели), ну и интеграции для более гибкой настройки (иногда свой диалект SQL, различные возможности подменять этапы работы с текстом, интеграции с моделями для ранжирования, с библиотеками для векторного поиска (если вдруг надо) и чем угодно ещё).
Постик основан на докладе, но ссылку на него я вам пока не дам, потому что он в закрытом доступе. Как появится запись, обязательно поделюсь.
👍7❤2
#common
Узнал про такую штуку как hashcash. Он используется в различных криптоштуках в качестве proof-of-work. Но сейчас мы не будем останавливаться на битке и подобных вещах. Хочется рассказать о том, как такое можно использовать в житейских простых задачах вроде пагинации (писал немного про неё тут).
С реализацией пагинации могут быть некоторые проблемы, если выдача у вас нестабильная. То есть офсет и курсор это круто, если множество данных у вас плюс-минус фиксировано и их порядок не меняется (плюс-минус, потому что можно добрасывать новое в конец и ничего в моменте не сломать). Но такая ситуация не всегда. Например вы хотите сделать пагинацию для рекомендашек. При каждом запросе у вас происходит полный цикл процесса рекомендаций, в конце которого (в упрощённом случае) находится ранжирование. Т.к. это обычно машинки и фичей бывает очень много (а они ещё могут очень быстро меняться) сущность, которую вы отранжировали на 1ю позицию в первом запросе, при втором запросе может вполне себе отранжироваться на 21ю. И с курсором 20 во втором запросе вы получите в выдаче то, что уже показывали. Почти наверняка это плохое решение, потому что пользователь уже видел этот контент и деняк больше вы не заработаете. Да и выглядит стьюпидно.
В зависимости от реализации поискового движка может возникнуть та же проблема.
Получается, надо где-то сохранить инфу о том, что мы пользователю уже показывали. Учитывая, что эта инфа валидна только для конкретного юзера, почти наверняка придётся каким-то образом получать данные с клиента (читай фронта). Давайте тогда запихнём эту инфу в курсор, который теперь будет иметь немного более сложный вид: это будет конкатенированная строка хешей объектов, которые мы отдали. И собственно из рексистемы отдадим эту строку как курсор клиенту, после чего он может прислать этот курсор обратно, а мы уже поймём по нему, что мы показывали, а что нет.
Хешировать тут можно всё что хотите. Хорошим вариантом будет какой-нибудь уникальный для каждой сущности idшник, множество которых вы просто заджойните в одну длинную строку. Потом можно засплитить эту строку по разделителю в множество хешей и проверить, какие айтемы вы уже пользователю отдавали, а какие нет. Это конечно может быть долговато, но можно покрутить хеш-функцию.
Собственно примерно поэтому и называется hashcash: вы кешируете множество объектов с помощью кеширования каждого из них.
Ещё из проблем тут есть:
- т.к. хеширование двух различных объектов может давать один хеш, вы можете отсеять что-то, что на самом деле не показывали, но, насколько я могу понимать, это обычно не крит, потому что с таким же успехом товар мог отфильтроваться на каком-нибудь другом этапе рекомендаций (отбор кандидатов, после ранжирования оказаться слишком низко, реранкинг). Если такое возникает часто, опять же можно посмотреть в сторону других хеш-функций, которые дают более разнообразные результаты;
- т.к. с каждым запросом кол-во показанных сущностей растёт, сам курсор тоже будет расти. Тут можно выбрать какое-то ограничение для длины курсора с трейдоффом кол-во показанного юзеру контента <-> технические возможности вашей системы. Если вы понимаете, что из всех пользователей долистывает до 300ого айтема только полпроцента, может не так страшно не показывать больше 300 объектов? Ну и оч длинную строку парсить на бекенде не хочется, т.к. это потенциально таймауты (а ещё та же проблема на клиенте, а ещё нагрузка на сеть вырастает). Можно опять же выбрать хеш-функцию так, чтобы она давала более короткие хеши, но тут важно не переборщить, чтобы не получить прошлую проблему в большом объёме.
Концептуально про рекомендательные системы расскажу попозже.
Узнал про такую штуку как hashcash. Он используется в различных криптоштуках в качестве proof-of-work. Но сейчас мы не будем останавливаться на битке и подобных вещах. Хочется рассказать о том, как такое можно использовать в житейских простых задачах вроде пагинации (писал немного про неё тут).
С реализацией пагинации могут быть некоторые проблемы, если выдача у вас нестабильная. То есть офсет и курсор это круто, если множество данных у вас плюс-минус фиксировано и их порядок не меняется (плюс-минус, потому что можно добрасывать новое в конец и ничего в моменте не сломать). Но такая ситуация не всегда. Например вы хотите сделать пагинацию для рекомендашек. При каждом запросе у вас происходит полный цикл процесса рекомендаций, в конце которого (в упрощённом случае) находится ранжирование. Т.к. это обычно машинки и фичей бывает очень много (а они ещё могут очень быстро меняться) сущность, которую вы отранжировали на 1ю позицию в первом запросе, при втором запросе может вполне себе отранжироваться на 21ю. И с курсором 20 во втором запросе вы получите в выдаче то, что уже показывали. Почти наверняка это плохое решение, потому что пользователь уже видел этот контент и деняк больше вы не заработаете. Да и выглядит стьюпидно.
В зависимости от реализации поискового движка может возникнуть та же проблема.
Получается, надо где-то сохранить инфу о том, что мы пользователю уже показывали. Учитывая, что эта инфа валидна только для конкретного юзера, почти наверняка придётся каким-то образом получать данные с клиента (читай фронта). Давайте тогда запихнём эту инфу в курсор, который теперь будет иметь немного более сложный вид: это будет конкатенированная строка хешей объектов, которые мы отдали. И собственно из рексистемы отдадим эту строку как курсор клиенту, после чего он может прислать этот курсор обратно, а мы уже поймём по нему, что мы показывали, а что нет.
Хешировать тут можно всё что хотите. Хорошим вариантом будет какой-нибудь уникальный для каждой сущности idшник, множество которых вы просто заджойните в одну длинную строку. Потом можно засплитить эту строку по разделителю в множество хешей и проверить, какие айтемы вы уже пользователю отдавали, а какие нет. Это конечно может быть долговато, но можно покрутить хеш-функцию.
Собственно примерно поэтому и называется hashcash: вы кешируете множество объектов с помощью кеширования каждого из них.
Ещё из проблем тут есть:
- т.к. хеширование двух различных объектов может давать один хеш, вы можете отсеять что-то, что на самом деле не показывали, но, насколько я могу понимать, это обычно не крит, потому что с таким же успехом товар мог отфильтроваться на каком-нибудь другом этапе рекомендаций (отбор кандидатов, после ранжирования оказаться слишком низко, реранкинг). Если такое возникает часто, опять же можно посмотреть в сторону других хеш-функций, которые дают более разнообразные результаты;
- т.к. с каждым запросом кол-во показанных сущностей растёт, сам курсор тоже будет расти. Тут можно выбрать какое-то ограничение для длины курсора с трейдоффом кол-во показанного юзеру контента <-> технические возможности вашей системы. Если вы понимаете, что из всех пользователей долистывает до 300ого айтема только полпроцента, может не так страшно не показывать больше 300 объектов? Ну и оч длинную строку парсить на бекенде не хочется, т.к. это потенциально таймауты (а ещё та же проблема на клиенте, а ещё нагрузка на сеть вырастает). Можно опять же выбрать хеш-функцию так, чтобы она давала более короткие хеши, но тут важно не переборщить, чтобы не получить прошлую проблему в большом объёме.
Концептуально про рекомендательные системы расскажу попозже.
👍14❤1🤔1🤯1
#common
Рекомендательные системы 1/2.
Опустим часть с мотивацией нужности подобных штук.
Пусть у нас есть множество пользователей и айтемов, которые мы рекомендуем. И пусть у нас есть история оценок по каждому пользователю, как он оценивал те или иные айтемы. Задача — научиться предсказывать оценку пользователя по какому-то ранее неоценённому айтему.
У вас может не быть явной системы оценивания, но могут быть другие сигналы — клики на айтемы, покупки айтемов, их просмотр/прослушивание, если вы какой-то видео-/аудиохостинг. Потому фидбек от пользователя делится на explicit (когда он явно сообщает, что ему что-то (не)нравится) и implicit (когда непонятно, нравится ли пользователю что-то, но можно выдвигать гипотезы, что действие пользователя означает).
Часто в таких штуках применяется коллаборативная фильтрация — методы в рексистемах, основанные на похожести айтемов и взаимодействии пользователя с айтемами.
Из терминов рядом ещё есть User2User (user based) и Item2Item рекомендации. Думаю, тут смысл понятен.
Есть ещё контентный подход — когда рекомендуют айтемы по схожести контента в них.
Ну и гибридные есть, которые сразу обе концепции совмещают, потому что у каждого подхода есть свои pros/cons, которые при совмещении могут закрываться.
На этапе ранжирования важной проблемой является кол-во данных. Фактически не представляется возможным отранжировать все существующие айтемы и взять из них топ, просто потому что это очень трудозатратно. Потому есть отбор кандидатов — какой-то относительно дешёвый способ сузить общее кол-во айтемов к небольшому множеству, чтобы все последующие действия производить только с ними. Тут конечно важно не испортить полноту айтемов, чтобы было вообще из чего в итоге выбирать. Как это можно делать:
- эвристически: выбирать самое популярное (в целом, или по геопозиции, или среди такого же возраста, или что угодно ещё);
- коллаборативно (item2item): офлайн посчитать 100 похожих айтемов для каждого, сложить это в какое-нибудь key-value и в онлайне взять для ста интересующих нас айтемов по 100 кандидатов. Отскорить итоговые 10k;
- юзать контентные похожести (взять какое-нибудь hnsw и для каждого айтема брать 100 самых близких в пространстве объектов);
- что-то продуктовое: докинуть чего-нибудь нового, например.
После отбора кандидатов и самого ранжирования наступает этап реранкинга (берём топ айтемов после ранжирования). Тут важно не только уметь показать пользователю то, что ему понравится, но и сделать это красиво. Например, повысить разнообразие выдачи, чтобы он не смотрел всё время на одно и то же (тут не только про тематику, но и про разнообразие форматов, если такая возможность есть). Или показывать только что-то старое, а не свежий контент.
В коллаборативной фильтрации есть проблема холодного старта: если у айтема/юзера нет никакого рейтинга/оценок, непонятно, кому и стоит ли вообще это рекомендовать.
Для юзеров можно попытаться собрать как можно больше инфы (хотя бы при регистрации задать пару лишних вопросов или как-то поонбордить его и предложить сразу что-то оценить).
Для айтемов тут можно использовать контентный подход.
Рекомендательные системы 1/2.
Опустим часть с мотивацией нужности подобных штук.
Пусть у нас есть множество пользователей и айтемов, которые мы рекомендуем. И пусть у нас есть история оценок по каждому пользователю, как он оценивал те или иные айтемы. Задача — научиться предсказывать оценку пользователя по какому-то ранее неоценённому айтему.
У вас может не быть явной системы оценивания, но могут быть другие сигналы — клики на айтемы, покупки айтемов, их просмотр/прослушивание, если вы какой-то видео-/аудиохостинг. Потому фидбек от пользователя делится на explicit (когда он явно сообщает, что ему что-то (не)нравится) и implicit (когда непонятно, нравится ли пользователю что-то, но можно выдвигать гипотезы, что действие пользователя означает).
Часто в таких штуках применяется коллаборативная фильтрация — методы в рексистемах, основанные на похожести айтемов и взаимодействии пользователя с айтемами.
Из терминов рядом ещё есть User2User (user based) и Item2Item рекомендации. Думаю, тут смысл понятен.
Есть ещё контентный подход — когда рекомендуют айтемы по схожести контента в них.
Ну и гибридные есть, которые сразу обе концепции совмещают, потому что у каждого подхода есть свои pros/cons, которые при совмещении могут закрываться.
На этапе ранжирования важной проблемой является кол-во данных. Фактически не представляется возможным отранжировать все существующие айтемы и взять из них топ, просто потому что это очень трудозатратно. Потому есть отбор кандидатов — какой-то относительно дешёвый способ сузить общее кол-во айтемов к небольшому множеству, чтобы все последующие действия производить только с ними. Тут конечно важно не испортить полноту айтемов, чтобы было вообще из чего в итоге выбирать. Как это можно делать:
- эвристически: выбирать самое популярное (в целом, или по геопозиции, или среди такого же возраста, или что угодно ещё);
- коллаборативно (item2item): офлайн посчитать 100 похожих айтемов для каждого, сложить это в какое-нибудь key-value и в онлайне взять для ста интересующих нас айтемов по 100 кандидатов. Отскорить итоговые 10k;
- юзать контентные похожести (взять какое-нибудь hnsw и для каждого айтема брать 100 самых близких в пространстве объектов);
- что-то продуктовое: докинуть чего-нибудь нового, например.
После отбора кандидатов и самого ранжирования наступает этап реранкинга (берём топ айтемов после ранжирования). Тут важно не только уметь показать пользователю то, что ему понравится, но и сделать это красиво. Например, повысить разнообразие выдачи, чтобы он не смотрел всё время на одно и то же (тут не только про тематику, но и про разнообразие форматов, если такая возможность есть). Или показывать только что-то старое, а не свежий контент.
В коллаборативной фильтрации есть проблема холодного старта: если у айтема/юзера нет никакого рейтинга/оценок, непонятно, кому и стоит ли вообще это рекомендовать.
Для юзеров можно попытаться собрать как можно больше инфы (хотя бы при регистрации задать пару лишних вопросов или как-то поонбордить его и предложить сразу что-то оценить).
Для айтемов тут можно использовать контентный подход.
👍2🤔2🆒1
#common
Рекомендательные системы 2/2.
Есть ещё такой трабл как feedback loop. Это когда рексистема из-за того, что рекомендует какие-то конкретные классы айтемов, будет всё чаще рекомендовать именно эти классы айтемов, т.к. по ним всё больше и больше фидбека. Ещё система может подстроиться под большинство популярных айтемов/пользователей и каким-то конкретным, выбивыющимся из общей картины пользователям, рекомендовать не самый подходящий контент.
Простые решения тут:
- можно подмешивать случайные айтемы в выдачу;
- делить рекомендации по тегам/темам/чему-то ещё.
Из понятного тут ещё можно сказать про шардирование. Например у вас огромное количество данных. Вы раскладываете данные батчами на несколько серверов. При запросе делаете запрос на каждый шард и получаете топ-100 с каждого шарда. Потом собираете все топы в какой-то один и переранжируете все айтемы ещё раз. Получаете честный топ-100 и отдаёте клиенту. Это классический подход. Надеюсь, ничего нового я вам тут не сказал.
Можно ещё сказать, что поиск и рекомендашки похожи по задаче, но в поиске у вас обычно есть какой-то конкретный запрос, когда в рекомендациях запрос — история поведения пользователя, что немного задачу усложняет.
Но я не настоящий сварщик в мльных штучках всяких. Просто интересно стало.
Рекомендательные системы 2/2.
Есть ещё такой трабл как feedback loop. Это когда рексистема из-за того, что рекомендует какие-то конкретные классы айтемов, будет всё чаще рекомендовать именно эти классы айтемов, т.к. по ним всё больше и больше фидбека. Ещё система может подстроиться под большинство популярных айтемов/пользователей и каким-то конкретным, выбивыющимся из общей картины пользователям, рекомендовать не самый подходящий контент.
Простые решения тут:
- можно подмешивать случайные айтемы в выдачу;
- делить рекомендации по тегам/темам/чему-то ещё.
Из понятного тут ещё можно сказать про шардирование. Например у вас огромное количество данных. Вы раскладываете данные батчами на несколько серверов. При запросе делаете запрос на каждый шард и получаете топ-100 с каждого шарда. Потом собираете все топы в какой-то один и переранжируете все айтемы ещё раз. Получаете честный топ-100 и отдаёте клиенту. Это классический подход. Надеюсь, ничего нового я вам тут не сказал.
Можно ещё сказать, что поиск и рекомендашки похожи по задаче, но в поиске у вас обычно есть какой-то конкретный запрос, когда в рекомендациях запрос — история поведения пользователя, что немного задачу усложняет.
Но я не настоящий сварщик в мльных штучках всяких. Просто интересно стало.
👍6🆒1