Дратути Антон – Telegram
Дратути Антон
4.11K subscribers
171 photos
30 videos
215 links
Мемы и личные размышления про управление, код, ml и здравый смысл.

Сейчас руковожу командой OCR in VLM в Яндексе.

Автор: @toshiknoscript
Download Telegram
Нас обманывали, а мы и не заметили (Часть 1)

На днях я задумался, а можно ли как-то получить доступ к тому, к чему обычно нельзя просто так обратиться. Например, к private полям класса, без его модификации в С++. Оказывается, при большом желании — да. Как это сделать, я расскажу в следующих сериях, а покамест ограничимся только protected полями.

Определим простой класс с полем:

class A {
public:
int Get() const { // Чтобы выводить в консольку
return a;
}
protected:
int a = 10;
};


Шикарно. Теперь используем using declaration, чтобы в наследнике вытащить поле в public секцию примерно так:

class B : public A {
public:
using A::a;
};


Заметим, что если мы сделаем поле у класса Aprivate, то такой трюк не получится сделать, потому что приватные поля при наследовании всегда остаются приватными.

Как бы теперь превратить A в B. Первое, что приходит в голову — использовать move-семантику (т.е. будем не копировать данные, а именно перемещать). Поэтому превращение можно сделать так:

class B : public A {
public:
using A::a;

B(A&& value) : A(std::forward<A>(value)) {} // паттерн перемещения

A ConvertToA() { // функция для ко
нвертации обратно в A
A a = std::move(*this); // Перемещаем себя же обратно в A
return a; // Возвращаем новый A
}
};


Ух, сложно. Попробуем это всё дело запустить:

A a; // Создаем переменную
std::cout << a.Get() << std::endl; // выведет 10
{
B b(std::move(a));
b.a = 20;
a = b.ConvertToA();
}
std::cout << a.Get() << std::endl; // выведет 20


Как видите, всё работает. Но есть проблемки — перемещение данных и всё такое. Как-то это не серьезно, что ли. Давайте усложним задачу и запретим перемещение:

class A2 {
public:
int Get() const {
return a;
}
A2() = default; // нужно определить конструктор по умолчанию
A2(A2&&) = delete; // удаляем перемещение, т.е. std::move не воспользоваться
protected:
int a = 10;
};


Как же быть? Ну всё просто. Вернемся к первоначальному варианту класса B, только назовем его C:

class C : public A2 {
public:
using A2::a;
};


Заметим, что C не добавляет никаких данных, а поэтому практически всегда его представление в памяти совпадает с A2. Тогда можно попробовать воспользоваться reinterpret_cast:

{
A2 a2;
std::cout << a2.Get() << std::endl; // выведет 10
C& c = reinterpret_cast<C&>(a2);
c.a = 30;
std::cout << a2.Get() << std::endl; // выведет 30
}

И вот это уже магия. Здесь ничего не перемещалось, но при этом мы изменили данные, которые недоступны извне.

Это очень интересный трюк, который показывает силу языка, но я надеюсь, он никогда вам не понадобится. Все же мы сделали очень много допущений. Но это даже неважно. Необходимо понимать, что если скрыли данные — то в этом был смысл. И не нужно пытаться получить к ним доступ, если не дают.

P.S. Можете запустить это в godbolt.

#разработка
🤔2🤯2👍1🤡1
Нас обманывали, а мы и не заметили (Часть 2)

Рассказываю, как поменять приватное поле класса. Возьмем класс A из поста выше, заменим protected секцию на private:

class A {
public:
int Get() const { // Чтобы выводить в консольку
return a;
}
private:
int a = 10;
};


Давайте подменим значения поля a с помощью нехитрого кода:

A a;
std::cout << a.Get() << std::endl; // выведет 10
static_cast<int*>(static_cast<void*>(&a))[0] = 20;
std::cout << a.Get() << std::endl; // выведет 20


Что произошло 🤯? Мы представили область памяти, где лежит объект A, в виде массива int — чиселок. Внутри только одно число, а потому обращаясь к нулевому элементу типа int, мы обращаемся к содержимому объекта a и меняем его. Здесь нам повезло — мы знали, что у класса A поле типа int.

Пора раскрывать карты и поговорить немного о том, как объект класса располагается в памяти ПК. Символом # я буду означать 1 байт памяти.

Объект выше показанного класса будет располагаться в пямяти как 4 байта (почти всегда):
####

Почему так? Потому что int на современных ПК занимает 4 байта. В этом классе больше нет никакой информации (поверьте, её бывает очень много). Все вызовы функций определяются еще на этапе компиляции. Именно поэтому, по другому представив объект (в виде массива int) — мы с легкостью заменили переменную в объекте 🥲.

Давайте теперь в классе A заменим int на double, назовем этот класс B. На современных ПК double занимает 8 байт и выглядит так:
########

Запустим тот же код:

B b;
std::cout << b.Get() << std::endl; // выведет 10
static_cast<int*>(static_cast<void*>(&b))[0] = 20;
std::cout << b.Get() << std::endl; // выведет 10


ХАХ! А ничего не работает 😤! Обманул вас Антон! На самом деле нет. Давайте по порядку. Когда мы представляем объект в виде массива чисел, мы говорим компилятору: фрагментируй память по 4 байта (по размеру типа int), поэтому пямять объекта разобъется так:
#### ####

Получается две чиселки типа int. Ну хорошо, мы поменяли первую чиселку, должно же было что-то поменяться. Давайте чтоли попробуем вторую чиселку поменять:

static_cast<int*>(static_cast<void*>(&b))[1] = 20;
std::cout << b.Get() << std::endl; // выведет 4.24399e-313


Тэк-с...🤨 Что-то явно пошло не так. Или так?

На самом деле мы с вами столкнулись с самой большой болью C++ разработчиков — работой с памятью 😩. Программист в этом языке может напрямую менять значения в памяти, но нередко это делается неправильно 🤷. Например, как я намеренно это сделал, предствив число double в виде двух int. Тип double намного сложнее, подробнее про это можно почитать здесь. И просто так там значение не подменить.

Куда бОльшие проблемы появляются, когда путем перезатераний памяти мы теряем место, где выделили какую-то память. Происходит утечка памяти (ваши игры именно так и вылетают) — с одной стороны, место в памяти вроде как не нужно больше, с другой стороны — операционная система не дает заполнить это место, потому что мы там ничего не освободили, а сделать этого не можем, потому что место потеряли 😂.

Все эти private, protected секции в языке — это некоторые гарантии языка на уровне абстракций для классов. Но как только переходим на уровень, где происходит работа с памятью напрямую — никакая гарантия языка не поможет. Насколько я знаю, именно за счет того, что разработчики не позаботились о дополнительном сокрытии переменных, с помощью ArtMoney в играх можно было хакнуть себе ресурсов побольше — программа просто лезла в область памяти вашей программы и находила байты, в которых лежат чиселки, похожие на заданные 🥷.

Все операции выше — сугубо в учебных целях. На практике это пригождается в очень специфических кейсах и надо иметь четкие гарантии и представление, что все будет именно так, как задумывалось. Иначе поменяем int, а нужно было double. Лично я сталкивался с этим один раз в жизни, когда связывал Objective-C и C++ для алгоритмов ray casting.

В следующем посте я расскажу подробнее про то, как классы устроены в памяти. Там не всё так просто!

P.S. Ссылочка на godbolt.

#разработка
🔥3👍2🤔1😱1🤡1
Выравние или то, на что обычно забивают 🙈

Как я уже упомянул в прошлом посте: классы — штука непростая. И сейчас я постараюсь описать некоторые интересные чудеса.

Для начала интересно узнать, каков размер пустого класса? Такого как этот:

class A {};

Если вы скажите 0 — вы будете неправы. Или правы? Или нет?😅 С точки зрения теории размер правда должен быть равен 0. Но объекты класса нужно между собой различать, даже если класс пустой. Это значит, что какую-то память да выделить надо. По стандарту минимальный размер объекта — 1 байт. Это вам и выдаст sifeof(A) — команда, по которой можно узнать размер объекта/класса в C++.

Забавно то, что если мы сделаем следующий класс:

class B {
int8_t b;
}


Чиселка в int означает число бит. Например: int8_t — это 8 бит или 1 байт; int32_t — это 32 бита или 4 байта. Подробнее про такие типы здесь.

то его размер тоже будет 1 байт. Просто в случае, когда класс пустой — нам память нужна просто для того, чтобы различать объекты. В случае класса B нам уже нужно хранить данные.😇

Пойдем немного дальше. Объявим следующий класс:

class C {
int32_t a;
int8_t b;
int32_t c;
};


С точки зрения логики здесь 9 байт. Но на самом деле будет 12 байт. Это происходит из-за выравнивания по ширине максимального фундаментального типа. Выравнивание необходимо для более быстрого чтения структуры и доступа с к данным. Но что в реальной практике достаточно часто можно увидеть, так классы пишут по следующему примеру:

class D {
int32_t a;
int8_t b;
int32_t c;
int8_t d;
};

Эта структура будет занимать целых 16 байт, в то время как реальных данных тут на 10 байт 😡. Всё остальное — это выравнивание. Поменяйте c и d местами — получите 12 байт, бесплатно без регистрации и смс 🗿.

Но иногда нам всё же нужно получить максимально сжатую структуру. В C++ это можно сделать с помощью pragma pack:

#pragma pack(push, 1)
class D2 {
int32_t a;
int8_t b;
int32_t c;
int8_t d;
};
#pragma pack(pop)

В этом случае структура D2 будет точно иметь 10 байт 🥷. Но производительность системы может упасть, из-за того, что не всегда поддерживаются быстрые операции со структурурами, у которых убрали выравнивание. Так что баланс соблюден — либо побыстрее, либо поменьше.

Подробнее про объекты можно почитать здесь.

Посмотреть на выводы можно здесь.

#разработка
👍3🔥1🤯1🤡1
GIT Анимашки

В жизни каждого из нас возникает период, когда необходимо делиться своими знаниями с новыми коллегами или новичками 👶. На такие случаи я собираю отдельную библиотечку, в которой храню полезные ссылочки 🤫.

Тут я подумал, что может быть и вам тоже будет интересно, поэтому время от времени буду разбавлять свои технические посты такими ресурсами. 😇

Одна из больших болей у начинающих в разработке — это работа с VCS (version control system). Если кратко — это инструмент, который позволяет хранить не только сами файлы, но и историю их изменений. Это очень удобно для текстовых форматов данных — можно понять, кто, когда и зачем добавил ту или иную строчку. Для больших файлов (3D модели, видео, аудио, картинки) существует GIT-LFS. У себя на работе мы используем GIT и сервис для удобного взаимодействия с ним — GitLab.

На днях я наткнулся на интересный пост с очень красивыми анимациями того, как работают основные и полезные команды Git.

Добавляйте себе в закладки!💫

#полезное #управление
👍3🔥3🤡1
Инициализация весов в нейронных сетях

Один из самых очевидных и недооцененных моментов во время подготовки нейронной сети к обучению — это инициализация её весов. 🤷

Для справки: можете понимать нейронные сети как набор функций и матриц. Вот эти матрицы обычно называются весами. Т.к. слоёв в нейронной сети много — то и матриц тоже много (по крайней мере одна на слой). Тогда если X — это какой-то вход (картинка или что-то другое), а w — это веса на одном слое, то простейшая нейронная сеть — это fun(Xw), где fun — это какая-то функция.

Дело всё в том, что если в жизни для того, чтобы начать дело, нужно просто начать, то в машинном обучении нужно еще подготовиться к этому всему.🗿 Я опускаю моменты, связанные с подговкой данных, которые являются наиболее важными.

Допустим мы все сделали, написали весь код, подготовили данные, продебажили и поняли что всё в целом работает. И даже что-то учится и нейронная сеть выдает адекватные результаты☺️. Но не такие, какие хотелось бы. А еще учится просто ужасно долго (относительно 🥲). До первых адекватных результатов — 20 часов. 😢

Начинаем разбираться, как так, а что случилось? Или всё ок?🤔 И самая первая классика — забили/забыли/не подумали про инициализацию весов. Исправляем, запускаем и ого — теперь сеточка показывает первые результаты через 5 часов, а метрики уже показывают новые рекорды 💫. А казалось бы — какая-то инициализация весов, кто бы мог подумать.

В этом замечательном посте описано с формулами и картинками, почему важна инициализация весов.

А вам желаю я сходимости весов и градиентов без затуханий и взрывов!🤗

#разработка
🔥2🤯1🤡1
C++ Zero Cost Conf 2022

Начиная с прошлого года я обязательно посещаю конференцию от Яндекса C++ Zero Cost Conf. Это возможность послушать бесплатно доклады от ребят, которые делают один из самых сложных и больших 🤯 highload на рынке России и, наверное, даже в СНГ. В этом году она будет 30 июля.

Я бы не стал выделять какие-то отдельные доклады, потому что сейчас программа выглядит очень сбалансировано и практически на все доклады можно попасть без перекрытий. Но лично я с удовольствием посмотрю на доклад от ClickHouse (просто люблю доклады от них). 😇

Регистрируйтесь на конференцию. Даже если вы не практикующий плюсовик, я думаю, вам будет интересно и полезно, как устроены сложные системы и с чем разработчики сталкиваются в большом highload! 🥷

#разработка
👍3🔥1😱1🤡1
Addressof

Иногда, когда мы люди пишут библиотеки на C++ (можно сказать отдельный самодостаточный модуль приложения), важно строго следить за тем, что ты делаешь в коде 😅. Можно просто на раз два выстрелить себе в ногу 🗿: мы это уже видели в множестве постов выше (тык, тык). Вернемся к нашему любимому примеру:

class A {
public:
int *operator&() { return &b_; }
void Print() const { std::cout << a_ << ' ' << b_ << std::endl; }

private:
int a_ = 100;
int b_ = 200;
};


В нём я добавил один метод, о котором я расскажу позже. Как я уже рассказывал в посте, мы можем изменить приватные поля класса. Давайте изменим поле a_:

A a{};
a.Print(); // выведет 100 200
static_cast<int *>(static_cast<void *>(&a))[0] = 20;
a.Print(); // выведет 100 20


Странно, скажете вы 🤨. И правда — вроде обратились к первому элементу и хотели поменять его. А в итоге поменяли второй. Ну чушь какая-то. В чем же дело?

А проблема как раз в новом методе. Он переопределяет оператор взятия указателя &. Когда разработчик его переопределяет, вместо возврата указателя на область памяти, где лежит объект, будет вызван метод. А он возвращает указатель на b_. Поэтому и меняется значение b_ 😢.

Но как же избежать такого поведения? Неужели ребята из комитета C++ не продумали и оставили такую дыру? На самом деле нет — есть std::addressof — этот метод как раз и помогает нам избежать проблемы 🥷. Смотрите сами:

A b{};
b.Print(); // выведет 100 200
static_cast<int *>(static_cast<void *>(std::addressof(b)))[0] = 20;
b.Print(); // выведет 20 200


Это что, магия 💫??? Почему этот метод работает, а оператор & нет? Давайте подумаем, как можно такое написать. Для начала вспомним, что мы не можем писать оператор & перед переменной — это приведет к вызову переопределенного метода 🤓.

Можно было попробовать static_cast и const_cast, но там нужно как-то привести всё к void*. А этого мы сделать не можем, потому что не можем получить указатель. Остается reinterpret_cast. Предлагаю сделать тогда приведение к char &:

reinterpret_cast<char &>(b)

Почему здесь &? Потому что без него компилятор будет ругаться и говорить, что привести типы не может. Так устроен reinterpret_cast 😅. После преобразования мы получили ссылочный тип char& и вот от него можно получить уже указатель:

&reinterpret_cast<char &>(b)

Теперь нам осталось просто 🤗 превратить это в указатель на A. Делаем:

reinterpret_cast<A *>(&reinterpret_cast<char &>(b))

Поздравляю нас! Мы это сделали! Нооо.. Есть одна проблема ☺️. Допустим если переменная b — константная? Или volatile? reinterpret_cast в этом случае не будет работать. Чтобы обеспечить поддержку двух этих ключевых слов — нам нужно включить их в тип, т.е. сделать так:

reinterpret_cast<const volatile char &>(b)

Как вы поняли, мы тут не можем просто взять и превратить всё в A *. Нужно будет прокидывать ключевые слова const и volatile. А нам этого точно не нужно. К счастью, const_cast умеет их убирать. Тогда используя его, получим следующую реализацию addressof:

reinterpret_cast<A *>(const_cast<char *>(&reinterpret_cast<const volatile char &>(b)))

Вот теперь это будет работать в любом случае!☺️ Так эта функция примерно устроена в стандартной библиотеке. Я был очень сильно удивлен этому костылю 🤯!

В конце я хочу лишь сказать, что переопределять оператор & не стоит 🤨. Любой компилятор или статический анализатор подчеркнет и выведет предупреждение. Но порой необходимо делать такие вещи. А поэтому при разработке библиотек нужно учитывать даже такой, очень неявный момент.😓

P.S. Ссылочка на Godbolt

#разработка
👍1🔥1🤯1🤡1
C++ для самых маленьких 😄 и С++ для тех, кому за 300 😢

В последнее время нет сил что-либо писать. Да что уж там говорить — нет сил что-либо делать. Какое-то эмоциональное выгорание, которое я никак понять не могу 🥺. Но между делом я стараюсь уделять время своему любимому хобби — программированию 👨‍💻.

Совсем недавно я наткнулся на конспекты курса по C++ кафедры КТ (компьютерные технологии) Университета ИТМО 🧑‍🎓. Они в целом хороши — дают первичное понимание языка, того, как это работает и что с этим можно делать. Жаль, что мемы не вставили — тогда был бы лучший курс для самых маленьких 🍷, имхо.

Сейчас я погружаюсь детальнее в то, как работают приложеньки на Unix-подобных системах. В частности, как они загружаются в оперативку, как находят сторонние зависимости и т.д. Казалось бы — при чем тут C++, а на самом деле есть повод задуматься😢. Мы можем оптимизировать поиск сторонних зависимостей (как во время компиляции, так и во время исполнения), можем оптимизировать загрузку в оперативку. А это все сказывается на пользовательское ощущение от использования наших приложений (вас же тоже бесит то, как долго грузится spotify или slack??? 😡).

Куда важнее ситуация, когда мы делаем приложение на highload backend 😆, где важно не только время на ответ, но и также, как быстро приложение может восстановиться после падения. Поэтому понимание того, как работает наше приложение от момента нажатия на enter — очень важно, и классно, когда мы можем управлять этим процессом на своем языке программирования.

"Я как будто бы уже давно глубокий старец, бессмертный, ну или там уже почти бессмертный" стараюсь изучать эти процессы, чтобы делать мои приложение быстрее, лучше. Я "ищу только одного — покоя, умиротворения и вот этой гармонии, от слияния с бесконечно вечным, от созерцания великого фрактального подобия и от вот этого замечательного всеединства существа" 😄.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥1🤯1🤡1
Новая эпоха 👨‍💻

С сегодняшнего дня я сотрудник компании Яндекс. Буду помогать команде улучшать качество сервисов, применяя свои знания в области CV DL и C++.

Думаю, будет много интересного, чем можно будет в том числе и поделиться с вами! А пока буду вливаться в процессы :)
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13🔥5💩4😱1😢1🤡1
Первая неделя 😆

Как и ожидалось — прям каких-то крутых вещей я за эту неделю не делал своими руками 😭. Не удивительно, ведь в большой компании огромный спектр технологий, которые нужно изучить, прежде чем взять задачки.

Но на удивление, я уже почти втянулся 😳. Понятное дело, что всё равно еще кучу всего узнавать, но скажем так, я немного освоился. Уже ясно, какие основные сервисы, куда зачем и почему писать. Это, конечно, не только моя заслуга, но и целой команды Яндекса, которая построила процесс onboading с одной стороны, достаточно емким по знаниям, с другой стороны достаточно легким для понимания 🙏 (если что, это не реклама, просто мне понравилось).

Удивительно, но за эту неделю я также успел уже полазить в коде, посидеть на проде 👨‍💻. Каждый раз волосы встают дыбом, когда понимаешь, какие мощности сосредоточены в этой компании. Я думаю, это пройдет, но пока я прям очень восхищаюсь 🤨.

С такими мощностями также приходит и отвественность: под большими нагрузками нужно делать надежные сервисы. При этом стараться делать решение как можно быстрее и без потерь качества 🔼.

Давайте договоримся, если за недельку ничего WOW не произойдет, я расскажу в следующем посте про Modules — относительно новая фича из C++20, которая в некотором роде "упитонячивает" C++. Честно говоря, я еще особо не разбирался — но кажется настало время! 👵
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4👍2🤔1🤡1
(E)Sim

Вы мне тут скажите — Антоха, подожди, ты же вроде про модули хотел написать 🤔. Это правда — хотел, но пока я с ними разбираюсь. Там такие костыли приходится городить, а вам хочется донести самую мякотку 😇.

Итак, о чем тут это мы. Я тут недавно задумался, что у меня на телефоне есть esim — погуглил и вроде даже в РФ можно её сделать очень просто (по крайней мере у МТС). Начал читать чего и как и наткнулся на сайте МТС на фразу:

SIM (Subscriber Identity Module) — это не просто идентификационный модуль абонента. В нём есть 10 МГц процессор, постоянная энергонезависимая память (а питание для работы SIM берёт от смартфона), контроллеры и даже собственная операционная система.

И тут я прифигел. В смысле, симка — это миникомпьютер 🤯? А что, так можно было???

Оказывается это правда. SIM в нашем понимании — это симка для телефона. В реальности, эту технологию применяют еще много где (даже на наших банковских картах). Естественно, что софт у банковских и мобильных симок отличается. Как минимум приложения (вы вдумайтесь, приложение на симке!!!) для идентификации у каждого провайдера свои. 😏

Но я не скажу больше, чем написано в этой статье на хабре. Поэтому приглашаю почитать! 🙈
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6🤯1🤡1
Качество кода

Кто меня давно знает, тот понимает, какой я фанатик по качеству кода 💃. Я смотрю на качество не только с точки зрения "красивости", но и с точки зрения менеджмента: что это дает мне и моим командам 😇.

Я бы сказал, что хороший код дает вам возможность развивать проект, а не стоять на месте и фиксить баги. Естественно, код не будет идеальным никода, а правила будут сменяться на новые. Но следить за качеством важно и нужно.👨‍💻

Под хорошим качеством я обычно подразумеваю не только стиль кода и покрытие тестами, но и:
наличие документации о нём. Это поможет новым сотрудникам быстро адаптироваться, а уже бывалым не забывать, что происходит;
разделение зон отвественности за каждый кусок кода между членами команды. Причем не так, чтобы Вася отвечал за А, а Коля за Б. Лучше чтобы и Вася, и Коля знали и А, и Б. Но кто-то из них был именно отвественным.
наличие всевозможных инструментов для анализа кода: статические анализаторы, санитайзеры и т.д.
наличие и мотивация опытных членов команды, которые могут много рассказать про решение;
публичность наработок: когда ты делаешь что-то крутое, тебе обычно хочется поделиться об этом. Но что-то крутое не будет получаться сделать, если в вашем проекте будет плохое качество: вы будете тонуть в кусках кода, вместо развития продукта.


На CppConf 2021 я наткнулся на прекрасное видео от Анастасии Казаковой о том, какие инструменты есть для анализа С++ кода и как с этим вообще обстоят дела. Взгляните — не пожалеете ☺️. Но я всё же считаю, что весь этот тулинг — это лишь верхушка айсберга. И для обеспечения качества надо копать глубже 🤓.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥2🤡1
Нейросети в мобилках

На дворе 2022, а мы еще даже понятия не имеем, как хорошо и классно запускать нейронные сети на мобильных девайсах 🤔. К сожалению, на текущий момент нет готового рецепта, как готовить нейронные сети под мобильные девайсы. Оптимизация и подготовка нейронных сетей под мобильные девайсы — одно из трендовых направлений в индустрии 🔼, потому что:

1. С одной стороны, хочется доставить пользователю удовольствие от того, как всё работает быстро, а еще и без интернета 😮;
2. С другой стороны, хочется поменьше тратить своего железа на пользователей — очень много из них ходят с девайсами, мощнее ноутбуков наших бабуль и дедуль, а железо компании денег стоит 😉.

Чтобы погрузиться в эту область, можно начать со всеобъемлющего гайда, который по поверхности проходится по существующим техникам. Но к сожалению, в реальной практике везде одни грабли и костыли 😔.

Например, возьмем хотя бы iOS и Android.

— На iOS полностью свои графические ускорители, а API так называемого Neural Engine, который так любят рекламировать в Apple, вообще скрыт. По факту, ты, как разработчик, просто сидишь и гадаешь, почему нейронка, которую ты запускаешь через их библиотеку, не хочет запускаться на нейронных движках, и запускается на обычной GPU, или вообще кидает EXEC_BAD_ADDRESS. 😡
— На Android просто тьма тьмущая устройств, всяких разных на вкус и цвет. Более того, одно и то же железо, но установленное разными вендорами телефонов, может показывать разные результаты🤪. На Android в большинстве случаев можно не ждать графических ускорителей (большая часть моих близких ходит с Android-телефонами и слабым железом) и прочих плюшек, как у Apple. Как и полагается, для Android написано куча софта для запуска нейронок, но никакой из них не решает задачу по всему зоопарку устройств так, чтобы это вызывало восторог 😔.

Как вы понимаете, уже только от зоопарка поддерживаемых девайсов можно немного встревожиться. А это я еще даже софтовые проблемы несовместимости не начал описывать. Также нужно подумать:
— На чем же запускать нейронку на конкретном девайсе (CPU, GPU, Neural Engine, etc)? А если троттлинг? А если места нет?
— Блин, а чего там по точности? Скорости? Памяти? Поеданию батареи?
— Чорт, а самая лучшая у нас нейронка очень большая, как её ужимать, чтобы сохранить качество?
— Господи, а самая лучшая у нас нейронка очень медленная, как её ускорять?
— ...

И нужно задачу запуска нейронной решить хотя бы на уровне кластера пользователей (по типу железа), а если можно на уровне отдельного пользователя — это вообще красота (но пока мне кажется, это нереально) ⛔️.

Мне кажется, что именно из-за огромного числа факторов и типов железа, мало кто делает запуск нейронных сетей на устройствах. Все отправляют запросы в сеть и получают ответ обратно. Все AI приложения на вашем телефоне обычно сделаны вендором этого телефона, который потратил много денег только на свой девайс, чтобы сделать классное решение (например, почти у всех свои умные камеры, режимы съемки и прочее).

В следующих постах я постараюсь описать то, каким образом можно решать некоторые из вопросов выше. В целом, это нереально интересная область!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6🤡1
Вы знаете, я не могу не поделиться этой новостью!
🤡1
Forwarded from AI для Всех
Нейрокомпрессия звука

Сжатие является важной частью современного Интернета, поскольку оно позволяет людям легко обмениваться высококачественными фотографиями, прослушивать аудиосообщения, транслировать любимые передачи и многое другое.

Сегодня, исследователи из Мета совершили прорыв в области гиперкомпрессии аудио с помощью ИИ. Представьте себе, что вы слушаете аудиосообщение друга в районе с низким качеством связи, а оно не тормозит и не глючит.

Они создали трехкомпонентную систему и обучили ее сжимать аудиоданные до заданного размера. Затем эти данные могут быть декодированы с помощью нейронной сети.

Они добились примерно 10-кратной степени сжатия по сравнению с MP3 при 64 кбит/с без потери качества. Хотя подобные методы уже применялись для речи, они первыми применили их для стереофонического аудио с частотой дискретизации 48 кГц (т.е. качество CD), которое является стандартом для распространения музыки.

Pied Piper только что стал реальностью, более того - опенсорсной.

🦦 Блог-пост
📖 Статья
🤤 Код
🔥7👍2🤡2
endbr64 🤔

Однажды я игрался в godbolt с виртуальными функциями по работе. Проставил необходимые флаги для сборки, читаю ассемблерный код и вижу инструкцию endbr64. "Это что за покемон такой?" — подумал я и начал копать. 👨‍💻

Запустим godbolt с опцией -O2 на следующем примере:

int main() {
return 0;
}

Он нам вернёт примерно следующее:

main:
xor eax, eax
ret


Теперь добавить опцию -fcf-protection и получим уже следующее:

main:
endbr64
xor eax, eax
ret


Что происходит? Остановите землю!!! 😮

Как известно, функция main в C++ — это входная точка запуска приложения в операционной системе. Но до вызова этой функции происходит еще множество вещей под капотом, по типу чтения бинаря, инициализации ресурсов и т.д. Затем некоторая функция __libc_start_main всё же вызывает main 🤓.

Но вот допустим, что у нас где-то появилось вредоносное ПО. Ну и по каким-то причинам ему нужно в runtime быстренько подменить функцию main (пока она еще не вызвалась из __libc_start_main), на другую, которая также находится в нашем бинарнике 😡. Хорошо, ПО нашло в памяти кусок, подменило адрес функции main, вызывается другая функция и происходит... завершение программы. Почему?

Потому что у нас есть инструкция endbr64 😇. И когда мы вызывали другую функцию, там не было endbr64 — процессор понял, что что-то не так, кинул клич и произошло экстренное завершение программы. Эта инструкция является некоторым сигналом о том, что вызов произошел успешно и можно двигаться дальше 🔼.

Насколько я знаю, эта инструкция есть у семейства процессоров Intel в рамках технологии CET, которая позволяет избегать некоторые атаки на приложения. Но она не панацея: если бы у той функции была эта инструкция, могло бы произойти ужасное 🥺!

На самом деле процессоры сейчас настолько сложны, что в них очень много сделано для безопасности и это не бесплатно. В идеальном мире наши процессоры работали бы быстрее — просто потому что не нужно было дополнительную логику для обеспечения безопасности 😐.

Подробнее прочитать про то, как же всё-таки запускается приложения на C++ можно здесь.
Интересное объяснение, зачем нужна инструкция endbr64здесь.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🤯1🤡1💯1
Процессоры и их кэш 😐

В школе, когда мне рассказывали про кэш процессора я задавался вопросом: "А почему бы не сделать кэш процесса в кучу гигабайт?" Ну на самом деле, была бы жизнь слаще и веселее. Обычно ответ на этот вопрос: "Дорого". А что такое дорого?

Давай посмотрим на схему процессора, где расположены кэши процессора 🤓. L1 кэш максимально близко к ядрам. L2 — подальше, но при этом побольше. L3 — вообще далеко и супербольшой. И это не просто так.

Дело в том, что:
1. Нам нужно проводить сигнал как можно скорее. Сам электрический ток. Через все логические элементы.
2. Нам нужно делать дополнительные проверки: чтобы быть безопаснее, чтобы поменьше было ошибок от случайных битов.

Таким образом, чтобы гарантировать время работы кэша, сигнал должен проходить супербыстро в случае L1, чуть медленнее L2 и вообще медленно (в относительных единицах) — L3. Например, по информации здесь, доступ к кэшам работает за:
L1 — 0.5 ns
L2 — 2.8 ns
L3 — 12.9 ns


И собственно, в данном случае, "дорого" — это разработка. Придумать такую схему, чтобы L1 был на несколько гигабайт и доступ до него был бы примерно 0.5 ns по всей области покрытия — это было бы очень круто. Но пока, наверное, не получается. Но интересно, получится ли? 🤔
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4👍1🤔1🤡1🌭1
Восставший из мертвых

Мне иногда нравится поведение приложений, которые написаны на C++. И тут нравится скорее всего == “о, еще одно место, где можно прострелить ногу”.😂

Вспомним классику. В C++ мы можем создавать объекты через оператор new:

A *a = new A(); // Здесь A — это какой-то класс, неважно какой

После того, как мы закончили пользоваться этим объектам, правила хорошего тона гласят — уберись за собой:

delete a; // Уничтожение объекта

На самом деле за операторами new и delete стоит множество магии: аллокация памяти, создание/удаление объекта 🤪. Но не об этом сейчас.

Сделаем простой класс:

class A {
public:
void fun() {
++x;
std::cout << x << '\n'; // Просто выводим результат
};

private:
int x = 3;
};


Этот класс хранит в себе переменную со стандартным значением 3. При вызове функции fun, значение в переменной увеличивается на единичку, а потом полученной значение выводится в командную строку.

Теперь давайте создадим объект и вызовем у него fun:

A *a = new A();
a->fun(); // выведется 4


Никаких проблем. Теперь создадим ссылку на этот объект (ссылка по сути аналог ярлыка):

A& b = *a;

Удалим объект и вызовем fun у b:

delete a;
b.fun(); // может быть вывод каким угодно, но у меня 1


Тут давайте остановимся. Ссылка она ж ярлык. Мы когда файл удаляем — у нас ярлык становится невалидным. А тут почему-то работает. Что за магия 😳?

А теперь создадим еще один объект и снова вызовем fun у b:

A *c = new A();
b.fun(); // может быть вывод каким угодно, но у меня 4


Во-первых, почему b всё еще работает? Во-вторых, как мы перескочили с 1 до 4 😑?

На самом деле тут произошла вот какая магия:

1. Мы создали реальный объект в памяти;
2. Затем сказали программе — смотри, вот тебе ссылка на область памяти, там 100% лежит объект типа A;
3. Удалили объект, но программа всё еще думает, что по ссылке лежит реальный объект, и при этом знает, какую функцию вызвать — поэтому вызывает её, при этом она сама работает на невалидном участке памяти;
4. Потом снова создали объект — но времени так мало прошло, что программа просто в целях оптимизации положила объект в то же место, что и старый объект;
5. Теперь ссылка по сути указывает на область памяти, где реально лежит объект типа А, поэтому срабатывает валидный вызов функции.

Но на самом деле всё это — undefined behavior (UB) 🤓. Т.е. поведение, которое никак не определено стандартом и то, что у меня так получилось — мне повезло. UB — очень тяжело отлавливаются и являются сложными багами в коде 🥹.

Под конец вопросик на сообразительность. Мы тут с вами обсуждали классы с виртуальными методами. Так вот если в класс А добавить виртуальный метод — то программа вылетит с ошибкой. Почему?

UPD: А ошибочки не будет — всё же происходит UB. Подробности в комментариях.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥1🤡1
Small talks

У нас в Яндексе куча мероприятий, чтобы понимать что где как и зачем происходит ☺️. Естественно, что большая часть из них — это NDA. Мне они очень сильно нравятся, потому что я всегда хочу всё знать 🙂 Я надеюсь, что когда-нибудь и я выйду и расскажу всему Яндексу, что мы крутого сделали в команде! Но даже после них создается какое-то ощущение, что не хватает личных историй 😔.

Я долго думал да гадал, как бы заполонить эту пропасть. Оказалось всё просто — пока ждешь, что приготовится твой кофе, можно поговорить с человеком, который следующий в очереди. И неважно, что вы возможно никогда не встретитесь в будущем (в Яндексе больше 18к человек), вы поделитесь из первых уст что у вас там интересненького🤔.

Так вот на днях я познакомился с проджектом проекта из внутренней инфраструктуры Яндекса. И мало того, что мы обменялись текущими настроениями, я еще узнал о полезном инструменте, который ранее не знал, что вообще ВАУ 😍.

И так постоянно: кто-то делает проекты в web3 , кто-то оптимизировал в кучу раз продакшн и т.д. И вот тогда ты понимаешь: вокруг тебя мир всё-таки развивается 😇!

Друзья, small talks — наше всё!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🤡1🐳1
Ох мои бенчмарки (Часть 1)😔

Сейчас на работе я занимаюсь оптимизацией нейронных сеток для мобилок 🔼. Оптимизация под них подразумевает под собой замеры:
— По метрикам качества;
— По скорости работы;
— По кушанию батарейки;
— По кушанию оперативки.

В большинстве случаев с первым пунктом проблем нет: написал скрипты для прогона, посчитал по результатам метрики, сделал выводы. С третьим и четвертым достаточно инструментов от вендоров: на android это профилировщик Android Studio, на iOS — профилировщик Xcode. А вот со вторым проблема. Много проблем. 😮

🤔 Первое: под девайс надо собрать бенчмарк. Это, казалось бы простое дельце, только кажется таким. Приходится убивать много времени для того, чтобы совместить все версии библиотек на всех девайсах.

🤔 Второе: некоторые версии бенчмарка не поддерживают какие-то операции с сетью, а некоторые работают отлично. Тут варианта два: думать как-то над архитектурой сети или над версией бенчмарка. Архитектуру менять очень не хочется (это повлечет много работы по обучению сети), тем более она работает на более свежих версиях. Приходится менять версию. Но это влечет за собой то, что придется менять версию всей библиотеки в продакшн-коде. А учитывая, что у нас монорепа, это сделать ой как не просто: то, что у меня чего-то там не запускается, так себе причина менять в монорепе — у всех же работает.

🤔 Третье: троттлинг (механизм защиты процессора от перегрева путем занижения производительности). Если запустить наши нейронки на среднестатистическом ПК на CPU — я уверяю вас, они будут летать и вы даже этого не заметите. Но на телефонах всё не так просто. Там другие процессоры и компоненты, они больше подвержены перегреву. А для бенчмарков важно иметь +- стабильную производительность. Я уже не говорю о том, что чиселки должны из раза в раз повторяться по одному и тому же тесту.

🤔 Четвертое: тестов ОЧЕНЬ много. Больше 500 на один девайс. Нужно думать о том, как их провести и как потом интерпретировать. 500 чиселок очень сложно просматривать (поверьте мне). Более того, сами по себе тесты разные и некоторые между собой (внутри одного девайса) не сравнимы. Например, есть замеры на CPU, GPU, NPU и т.д.

🤔 Пятое: девайсы могут оключаться и включаться, перезагружаться, обновляться и прочие радости жизни. Нужно уметь несмотря на всё это восстанавливать проведение замеров, а именно продолжать с того места, откуда всё закончилось. И при этом сохранить себе нервы.

Первая проблема решается вручную. По второй проблеме я надеюсь доказать, что замена всё же необходима.👨‍💻 А по всем остальным проблемам решение я расскажу в следующем посте.☺️
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7😱1🤡1
Ох мои бенчмарки (Часть 2)😔

Как вы могли догадаться из предыдущего поста — вручную запускать я это собираюсь 😂. У меня несколько девайсов, на каждом более 600 тестов (да-да, за выходные еще добавилось). И кажется не для того меня нанимали, чтобы я такими вещами занимался. Конечно, нужно как-то автоматизировать 💃.

Замечу сразу — я писал обёртку для запуска готового инструмента из либы. И это по нескольким причинам:
🤔Только авторы либы обладают полным пониманием, как она работает, и способны написать тесты, которые учитывают много нюансов работы;
🤔 Даже если бы я собрался писать — это месяц, а то и больше времени работы, что естественно бессмысленно при наличии готового инструмента;
🤔 Многопоточные CPU приложения замерять сложно, GPU приложения замерять многократно сложнее;
🤔 Готовые инструменты как правило представляют подробную статистику что и как исполнялось, а не только общее число выдаёт — это очень полезно;
🤔 Если у либы нет такого инструмента, то возникает большой вопрос об эффективности этой библиотеки.

Утилита, которую я использую имеет множество параметров и определенное подмножество — это один эксперимент. Естественно, мне не нужны все подмножества данного множества: только определенные эксперименты были выбраны вручную 💪. Каждый эксперимент должен исполняться для каждой модели и для каждого окружения.

В целом подстановку всех этих параметров можно сделать путем написания нескольких вложенных циклов (хотя на деле лучше использовать hydra, что я и сделал), но есть проблема — если упадет, то придется начинать всё сначала 😮. На помощь приходить генерация плана тестов.

В моём случае план тестов — это определенная структура папок, где для каждого девайса есть папки каждого окружения, в которых для каждой модели тоже есть папки и т.д. В самых глубоких папках лежат результаты запуска каждого теста. Теперь проверка того, выполнялся ли тест становится проще — мы просто проверяем, лежат ли файлы с результатами по заданному адресу теста для девайса 🔼. Поэтому если что-то упадет, всегда можно просто перезапустить команду, она продолжит выполнять только те тесты, которые еще не были исполнены.

Маленькая ремарка: отслеживание прерывания тестов можно было бы как-то автоматически отслеживать, но в этом нет смысла: если что-то упало, то надо смотреть вручную, мб там телефон сгорел или перегрелся 🤪.

Проблема с тротлингом решается запуском тестов через какой-то промежуток времени. Мои эксперименты показали, что этого достаточно, чтобы не нагружать девайс от слова совсем 😇.

После всего многообразия тестов нужно как-то представить результаты. Я быстренько накидал скрипт на Python, который строит графики по необходимым срезам, а также генерирует таблички по окружениям в markdown стиле. Графики помогают оценить тренд в общем, а таблички уже для более детального анализа спорных моментов.

На деле оказалось куда проще, чем казалось 😃
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2🤡1