Закралась мысль, что у Сбера стратегия информатизации такая.
Есть у Сбера деньги и нет ИТ технологий. Делаешь СП с ИТ компанией. Разводишься. Получаешь ИТ технологии в актив.
Для меня ценность vi/vim'а — в стиле навигации и набора текста (hjkl, модальный ввод и пр. что есть из коробки). Сам vim использовать в качестве IDE — увольте. Во-первых, terminal обладает далеко не богатыми UI возможностями. Во-вторых, настраивать плагины и оттачивать vim.config — то еще удовольствие (и не продуктивно).
Я всегдаиспользую «vim mode» в Visual Code, Visual Studio, CLion,… Но сам vim запускаю очень редко, когда надо глянуть файл не выходя из терминала.
По привычке начал искать vim mode. Не нашел.
А так — выглядит и работает хорошо! Жалко, что применение скорее всего ограничится только онлайн кодинг интервью.
Сам был участником подобных собеседований в FAANG. Поэтому было интересно узнать, почему собеседования проходят именно таким образом: 3 кодинг сессий, 1 дизайн системы, 1 поведенческая.
Для себя сделал такой вывод:
1. Большой поток кандидатов, сложно объективно и индивидуально подходить к каждому.
2. Решая алгоритмические задачи, кандидат показывает свои когнитивные способности. Способность понимать подсказки интервьюера, способность находить и обрабатывать крайние случаи. Отбираются кандидаты с хорошей соображалкой, а уж по скиллам место внутри многотысячной корпорации найдется.
3. Подготовленный кандидат (over 50-100 решенных задач на leetcode, например) также показывает свою мотивацию получить работу в компании.
Следующим шагом нужно применить все синхронные транзакции, которые не успел применить старый лидер. Нужно ждать их репликации на кворум узлов, а затем рассылать подтверждения этих транзакций.
Как новый лидер узнает о незавершенных транзакциях? Не может ли возникнуть ситуации, когда в слейве будет «потеряшка» — старая транзакция без подтверждения или отмены. Классическая проблема двухфазного коммита.
Полиморфные аллокаторы интересны для оптимизаций выделения памяти «на куче». Например, через них можно сделать аллокации в арене (как protobuf arena) и очистить всю память разом, освободив арену.
Один момент, который смущает — это хранение указателя на аллокатор в каждом объекте. Для контейнеров такой overhead может быть незаметен, а вот для строк — это плюс одна треть к размеру объекта. Вот думаю, можно ли провести оптимизация: хранить указатель на аллокатор в статической области памяти (thread_local). И перед работой с контейнером назначать этот указатель на необходимый аллокатор. Есть ли у кого подобный опыт?
Я описал как в железа на x86-x64. На других архитектурах механизмы другие, но будут соблюдены гарантии модели памяти C++. Компилятор не в праве компилировать корректный с точки зрения стандарта языка код в некорректный машинный код.
Поэтому — да, так всегда by design языка.
Кстати, насчет счетчика ссылок. Для инкремента можно использовать relaxed, а для декремента нужен acquire/release, т.к. декремент счетчика ссылок до 0 должен быть синхронизирован для однократного вызова деструктора объекта и деаллокации памяти.
Может ли случиться, что результат будет, как будто инкремент случился лишь единожды?
Нет, инкремент случится всегда дважды. Об этом пример 1, как раз. godbolt.org/z/jx85P9
Код:
counter.fetch_add(1, std::memory_order_relaxed);
Транслируется в команду на x86-64:
lock add QWORD PTR counter[rip], 1
На уровне ЦПУ это работает так: выполнить оператор add, захватив эксклюзивный доступ к кэш линии. Это соответсвует состоянию Exclusive в протоколе синхронизации кешей MESI ( en.wikipedia.org/wiki/MESI_protocol ). Состояние exclusive означает, что в линии кэша ядра лежит актуальное значение (соотвествующее main memory) и в других ядрах кэш линии инвалидируется (состояние invalid).
Когда два ядра захотят выполнить fetch_add(1, std::memory_order_relaxed), то им придется по-очереди захватить exclusive лок на кэш линию и каждое ядро (поток) прибавит свою единичку правильно.
Ядре (архитектура x86-64), выполняющем print_metrics(), кэш линия с counter будет в состоянии invalid, ядро запросит чтение из этой кэш линии, и она перейдет в состояние shared на всех ядрах. В этот момент в кэш линии уже будет актуальное значение, совпадающее с количеством fetch_add(1), которые успели выполнится в других потоках.
По 1. Полностью с вами согласен. Я подправлю текст статьи.
По 2. Все таки не соглашусь с вами. Атомарные операции (инструкции процессора) — более фундаментальное понятие, чем мьютекс в операционной системе. Реализация мьютексов в OS реализованы на атомиках (по крайней мере в linux).
Вот кусок из mutex.c из исходных кодов линукс:
/*
* Optimistic trylock that only works in the uncontended case. Make sure to
* follow with a __mutex_trylock() before failing.
*/
static __always_inline bool __mutex_trylock_fast(struct mutex *lock)
{
unsigned long curr = (unsigned long)current;
unsigned long zero = 0UL;
if (atomic_long_try_cmpxchg_acquire(&lock->owner, &zero, curr))
return true;
return false;
}
static __always_inline bool __mutex_unlock_fast(struct mutex *lock)
{
unsigned long curr = (unsigned long)current;
if (atomic_long_cmpxchg_release(&lock->owner, curr, 0UL) == curr)
return true;
return false;
}
Вы правы, что мьютекс в OS более «умный», чем спин лок и взаимодействует с планировщиком потоков, но это не убирает с него обязанностей атомарно «включить» или «выключить» блокировку, что реализована на атомиках, как показывает исходный код выше.
3. Про compare_exchange_weak vs strong. Я с вами согласен. Но этот вопрос достаточно внятно описан в документации этих методов. Мне не хотелось тратить время читателя на «простые вещи», описанные в документации. В том числе на такие, как std::atomic::is_lock_free и std::atomic_flag.
Да, C++ разработчики любят написать свой std::string, не спорю =) Думаю, это связано с любовью копанием в регистрах низкоуровневых мелочах, и отсутствием нормального менеджера зависимостей.
Насчет вашего выпада про готовые решения: библиотеки, которые вы указали — это парсеры json/ini и прочего. Условно, из json строки в map<string, any>. Это первая часть балета. Вторая часть — маппинг map<string, any> в конкретную составную структуру C++.
Именно вторую задачу решает автор статьи. Можно было бы хотя бы прочитать заголовок: «Отображение данных в формате json на структуру C++». По сути, это элементы рефлексии полей структуры с различными аттрибутами (имя поля json, дефолт и пр.).
Если знаете библиотеку, которая это делает — дайте ссылку.
EDIT: danielaparker.github.io/jsoncons имеет возможность маппинга json в структуру, но отсутствуют фичи, которые полезны для работы с конфигами (дефолтные значения, валидация параметров, логирование прочитанного конфига и пр.).
Спасибо за статью и за то, что показали свое решение. Совсем недавно я искал решение для парсинга и дампинга иерархических конфигов (вложенные классы, вектора, мапки, ...) из/в json. К моему удивлению, а не смог найти готового решения. Поэтому написал свой, как и вы.
Ваше решение «do the job», но имеет несколько недостатков, на мой взгляд:
магия макросов. Это значит возможный конфликт имен (или очень длинные имена), трудность понимания этого кода и сложности для IDE
определение структуры и ее парсера смешано в одном коде. Это значит, что мы не можем сделать парсер для уже готовой структуры, не можем дать значения по-умолчанию
не поддерживаются std контейнеры (как я понял), сложность расширения парсера на свои типы (например std::chrono::duration)
Основная идея — мы описываем парсер структуры (в том числе с вложенными полями) обычным C++ кодом. Далее использую объект парсера можно читать/писать в/из json, валидировать параметры, логировать поля, которые были обновлены.
Небольшой пример:
// Destination config
struct Config
{
std::string dir;
uint64_t severity;
bool feature;
std::string name;
double velocity;
std::string password;
};
// Initialize parser with rules
auto GetParser()
{
rcfg::ClassParser<Config> p;
p.member(&Config::dir, "Dir", rcfg::NotEmpty);
p.member(&Config::severity, "Severity", rcfg::Bounds{0, 6}, rcfg::Default{4});
p.member(&Config::feature, "Feature");
p.member(&Config::name, "Name", rcfg::NotEmpty, rcfg::Default("MyName"s), rcfg::Updatable);
p.member(&Config::velocity, "Vel", rcfg::Bounds{0.0, 100.0});
// secret means that the field value won't be revealed after reading
p.member(&Config::password, "Password", rcfg::NotEmpty, rcfg::Secret);
return p;
}
Странный тест на C++.
В одном из примеров присходит присваивание по неинициализированному указателю `*b = a`. После этого бессмысленно спрашивать, что выведет программа — да хоть розовых пони!
Спасибо за ссылку, я не пробовал database_exporter, но выглядит так, что он делает похожие вещи.
Чем nerve отличается:
1. Плагинами можно расширять типы задач. В частности у нас есть задачи, которые достают метрики из HTTP-запросов. А тип задачи CompareQueries делает запросы в разные базы и сравнивает результаты (only in left, only in right, ...).
2. nerve выполняет запросы по расписанию — у каждого запроса своя периодичность. Это позволяет не грузить БД и накапливать необходимую статистику.
3. Сам запрос может быть шаблонизирован (используются jinja2 шаблоны), например конструкция {{scheduled_time | delta("-2h") | toDateTime}} будет заменена на валидный для базы данных формат даты и времени
Есть у Сбера деньги и нет ИТ технологий. Делаешь СП с ИТ компанией. Разводишься. Получаешь ИТ технологии в актив.
Я всегдаиспользую «vim mode» в Visual Code, Visual Studio, CLion,… Но сам vim запускаю очень редко, когда надо глянуть файл не выходя из терминала.
А так — выглядит и работает хорошо! Жалко, что применение скорее всего ограничится только онлайн кодинг интервью.
Для себя сделал такой вывод:
1. Большой поток кандидатов, сложно объективно и индивидуально подходить к каждому.
2. Решая алгоритмические задачи, кандидат показывает свои когнитивные способности. Способность понимать подсказки интервьюера, способность находить и обрабатывать крайние случаи. Отбираются кандидаты с хорошей соображалкой, а уж по скиллам место внутри многотысячной корпорации найдется.
3. Подготовленный кандидат (over 50-100 решенных задач на leetcode, например) также показывает свою мотивацию получить работу в компании.
Как новый лидер узнает о незавершенных транзакциях? Не может ли возникнуть ситуации, когда в слейве будет «потеряшка» — старая транзакция без подтверждения или отмены. Классическая проблема двухфазного коммита.
Один момент, который смущает — это хранение указателя на аллокатор в каждом объекте. Для контейнеров такой overhead может быть незаметен, а вот для строк — это плюс одна треть к размеру объекта. Вот думаю, можно ли провести оптимизация: хранить указатель на аллокатор в статической области памяти (thread_local). И перед работой с контейнером назначать этот указатель на необходимый аллокатор. Есть ли у кого подобный опыт?
Поэтому — да, так всегда by design языка.
Кстати, насчет счетчика ссылок. Для инкремента можно использовать relaxed, а для декремента нужен acquire/release, т.к. декремент счетчика ссылок до 0 должен быть синхронизирован для однократного вызова деструктора объекта и деаллокации памяти.
Нет, инкремент случится всегда дважды. Об этом пример 1, как раз.
godbolt.org/z/jx85P9
Код:
Транслируется в команду на x86-64:
На уровне ЦПУ это работает так: выполнить оператор add, захватив эксклюзивный доступ к кэш линии. Это соответсвует состоянию Exclusive в протоколе синхронизации кешей MESI ( en.wikipedia.org/wiki/MESI_protocol ). Состояние exclusive означает, что в линии кэша ядра лежит актуальное значение (соотвествующее main memory) и в других ядрах кэш линии инвалидируется (состояние invalid).
Когда два ядра захотят выполнить
fetch_add(1, std::memory_order_relaxed)
, то им придется по-очереди захватить exclusive лок на кэш линию и каждое ядро (поток) прибавит свою единичку правильно.Ядре (архитектура x86-64), выполняющем
print_metrics()
, кэш линия сcounter
будет в состоянии invalid, ядро запросит чтение из этой кэш линии, и она перейдет в состояние shared на всех ядрах. В этот момент в кэш линии уже будет актуальное значение, совпадающее с количествомfetch_add(1)
, которые успели выполнится в других потоках.По 1. Полностью с вами согласен. Я подправлю текст статьи.
По 2. Все таки не соглашусь с вами. Атомарные операции (инструкции процессора) — более фундаментальное понятие, чем мьютекс в операционной системе. Реализация мьютексов в OS реализованы на атомиках (по крайней мере в linux).
Вот кусок из mutex.c из исходных кодов линукс:
Вы правы, что мьютекс в OS более «умный», чем спин лок и взаимодействует с планировщиком потоков, но это не убирает с него обязанностей атомарно «включить» или «выключить» блокировку, что реализована на атомиках, как показывает исходный код выше.
3. Про
compare_exchange_weak vs strong
. Я с вами согласен. Но этот вопрос достаточно внятно описан в документации этих методов. Мне не хотелось тратить время читателя на «простые вещи», описанные в документации. В том числе на такие, какstd::atomic::is_lock_free
иstd::atomic_flag
.регистрахнизкоуровневых мелочах, и отсутствием нормального менеджера зависимостей.Насчет вашего выпада про готовые решения: библиотеки, которые вы указали — это парсеры json/ini и прочего. Условно, из json строки в
map<string, any>
. Это первая часть балета. Вторая часть — маппингmap<string, any>
в конкретную составную структуру C++.Именно вторую задачу решает автор статьи. Можно было бы хотя бы прочитать заголовок: «Отображение данных в формате json на структуру C++». По сути, это элементы рефлексии полей структуры с различными аттрибутами (имя поля json, дефолт и пр.).
Если знаете библиотеку, которая это делает — дайте ссылку.
EDIT: danielaparker.github.io/jsoncons имеет возможность маппинга json в структуру, но отсутствуют фичи, которые полезны для работы с конфигами (дефолтные значения, валидация параметров, логирование прочитанного конфига и пр.).
Ваше решение «do the job», но имеет несколько недостатков, на мой взгляд:
Позволю тут оставить ссылку на свое решение: github.com/dmitryikh/rcfg
Основная идея — мы описываем парсер структуры (в том числе с вложенными полями) обычным C++ кодом. Далее использую объект парсера можно читать/писать в/из json, валидировать параметры, логировать поля, которые были обновлены.
Небольшой пример:
Указанный способ — хороший пример того, как сделать типобезопасный type erasure в C++ без интерфейсов и наследования.
В одном из примеров присходит присваивание по неинициализированному указателю `*b = a`. После этого бессмысленно спрашивать, что выведет программа — да хоть розовых пони!
Пример с reinpreted_cast — тоже UB. Посмотрите www.youtube.com/watch?v=_qzMpk-22cc
Чем nerve отличается:
1. Плагинами можно расширять типы задач. В частности у нас есть задачи, которые достают метрики из HTTP-запросов. А тип задачи CompareQueries делает запросы в разные базы и сравнивает результаты (only in left, only in right, ...).
2. nerve выполняет запросы по расписанию — у каждого запроса своя периодичность. Это позволяет не грузить БД и накапливать необходимую статистику.
3. Сам запрос может быть шаблонизирован (используются jinja2 шаблоны), например конструкция
{{scheduled_time | delta("-2h") | toDateTime}}
будет заменена на валидный для базы данных формат даты и времени