Игры и Люди

… одетый только в халат из холщовой ткани, ходил в кабачки и к певичкам. Когда его спрашивали, почему он таков, он каждый раз открывал рот, засовывал туда кулак и не говорил. Император Лян-цзун призвал его и спросил: «Каков принцип Вашего Пути?» Гуйчжэнь ответил: «Одежда тонка — поэтому люблю вино, выпью вина и защищусь от холода, напишу картину — и расплачусь за вино. Кроме этого, ничего не умею». Лян-цзун не нашелся, что сказать…

От игрушек детства мы движемся к другим. Здесь — об этом.

Алхимия игры включает несколько ингредиентов.

Рецептура состоит из Миров, по которым можно путешествовать; не все из них достаточно хорошо населены. Дело — это Игрушка одного из миров.

Объединяя видимые и сокрытые элементы, Алхимия выступает и как самостоятельный Игрок.

Виртуальные функции C++. Следствие ведут Колобки

Двигаемся поэтапно, с самого начала.

1. Никаких виртуальных функций (пока что)

Используем struct вместо class чтобы каждый раз не писать public. В нашем примере не будет никаких приватных членов класса, а в остальном все тоже самое:

Функция f()  переопределена в дочернем классе Child (overriding, не путать с overloading). Вызов функций:

Пока никаких неожиданностей нет — все работает как должно.

Замечу, что если в дочернем классе не будет функции (2), то будет вызвана функция (1) родительского класса. Это происходит потому, что Child хранит гены родителя Parent и в силу наследования имеет доступ ко всем полям родительского класса. Но это так, к слову.

2. Все еще никаких виртуальных функций

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

Указатель на Parent, а объект Child… нет ли здесь несоответствия или противоречия?

Рассуждаем так. Указатель на объект класса — это не просто адрес в памяти. Когда мы имеем дело с классами, фактически имеем набор указателей на все поля и функции класса — это если упростить (а упрощать мы любим). Выше я говорил, что Child хранит «гены» родителя Parent, в нашем случае — функцию (1) родительского класса. Поэтому указатель на Parent находит в Child то, про что он знает — отсылку на свою собственную функцию (1).

При этом родительский класс не знает абсолютно ничего про остальные поля и функции, которые эксклюзивно находятся в Child. И если последний содержит например функцию g(), то компиляция p->g() даст ошибку.

В ситуации наоборот

Указатель захочет сослаться на поля, которые есть только в Child, но в объекте Parent их быть не может!

3. Подозрительная конструкция

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

когда все прекрасно работает и без нее? Следствие, которое ведут Колобки, знает: встретили такую запись — значит где-то поблизости виртуальная функция (и может быть не одна).

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

Поэтому смотрим пока на то что «нет смысла», а потом переходим к заключительному полноценному примеру.

4. Бессмысленная виртуальная функция

Теперь функция f() стала виртуальной. Заведите хорошую привычку дописывать override в дочерних классах, чтобы во-первых было понятно что функция виртуальная (это здесь все видно, а если вы копаетесь в многочисленных листингах?), и во-вторых это страхует вас от ошибки в сигнатуре функции — без override компилятор все пропустит, но будет считать f() в дочернем классе уже не виртуальной, а с override — предупредит.

Повторяем наш эксперимент с самого начала.

Вы заметили какую — нибудь разницу по сравнению с первым сценарием без виртуальных функций? И я тоже нет.

Давайте быстро забудем про это бессмысленное применение virtual и перейдем к нашей сакраментальной конструкции, и тут она уже заиграет в полную силу.

5. Складываем все вместе

Объявление класса с виртуальными функциями:

Действие:

У меня для вас новость: объект родительского класса каким-то образом узнал про функцию в дочернем классе и вызвал именно ее. Как он это сделал?

Сейчас самое главное — не погрузиться в дебри новых понятий и не потерять прозрачность повествования. Новое понятие — это таблица виртуальных функций vtable класса Parent, которая содержит указатели на виртуальные функции. Отныне все вызовы виртуальных функций будут происходить не через указатель класса Parent* p, а через посредника — таблицу vtable.

Принципиально важным является то, что содержимое этой таблицы не задается жестко во время компиляции, а может меняться по ходу выполнения программы (это то, что вас на собеседовании спрашивали про позднее связывание или dynamic binding). Если мы поймем, как и когда эта таблица заполняется и меняется, мы поймем все про виртуальные функции. Поехали!

6. Gdb, vtbl

Ехать будем с помощью отладчика gdb — это наиболее удобный способ посмотреть расположение vtable и виртуальных функций в памяти. Поменяем наш пример, чтобы с отладчиком было удобнее работать:

Компилируемся, запускаем отладку:

Замечу что b = d еще не выполнялось: мы остановились до этой строчки.

Для успокоения души проверим адреса, которые нам выдал отладчик, например для b:

Все в порядке, vtable содержит адреса Base::base_and_derived() и Base::base() (доверяй, но проверяй!)

Теперь посмотрим на таблицы, на какие виртуальные функции они указывают. С классом Base все понятно, это пара собственных виртуальных функций base_and_derived(), base(). С дочерним классом Derived немного интереснее: его таблица содержит переопределенную виртуальную функцию базового класса base_and_derived(), собственно виртуальную функцию base() которая принадлежит базовому классу (родитель!) и собственная виртуальная derived().

Само собой, адреса виртуальных таблиц 0x555555557d50 и 0x555555557d28 различаются, поскольку у каждого класса (не экземпляра класса!) своя виртуальная таблица. Это замечание пригодится нам прямо сейчас.

Продолжаем программу и выполняем наше легендарное присвоение указателя производного класса указателю базового класса:

 

Произошли существенные изменения, и главное из них — мы фактически потеряли виртуальную таблицу базового класса (что еще можно было ожидать от присваивания?) Точнее, базовый и производный класс сейчас содержат только одну виртуальную таблицу расположенную по адресу 0x555555557d28, которая по сути таблица класса Derived оставленная без изменений —.

Поскольку класс Base теперь пользуется виртуальной таблицей класса Derived, предыдущая запись
0x5555555552a0 <Base::base_and_derived()>

Поменялась на запись
0x5555555552a0 <Base::base_and_derived()>

какой она и была для Derived раньше.

Это означает: все вызовы виртуальной функции base_and_derived выполненные с помощью указателя b->base_and_derived() приведут не к вызову виртуальной функции базового класса (как можно было ожидать, ведь b — указатель на Base), а к вызову виртуальной функции произвольного класса.

Что и требовалось доказать.

7. Послесловие

Вот и все. Приведу пример, когда это бывает нужно в реальной жизни. Например, клиент знает только про класс Interface и пользуется им:

У нас есть возможность подсовывать различные варианты реализации интерфейса, оставляя клиента в неведении:

Этот пример можно воспринимать по другому: например, Implementation_1 это боевая реализация, а Implementation_2 это mock заглушка для тестирования. Или Interface это класс прокси или адаптера. В общем все шаблоны C++ которые только существуют базируются на этой записи:

и это все, что вам нужно знать про полиморфизм в C++.

Микрофонный усилитель на LM358

Понимаю, тема несколько выбивается из ряда… но разнообразие прекрасно, полезно и бодрит, поэтому продолжаем. В последние два с половиной года я был сильно погружен коммерческую разработку на  C++ , мой стол набитый комплектацией простаивал без дела, и вот пришло время размяться на аналоговом поле.

На самом деле микрофонный усилитель — это не просто так, а часть моего проекта, который я завершу — когда нибудь, надеюсь. Задача усилителя — завести голос с микрофона гарнитуры на АЦП STM32. Определенное время я развлекался, знакомясь с ужасными схемотехническими решениями, которые кочуют с сайта на сайт (высокоимпедансный микрофон на вход схемы с общим эмиттером), и решил дать простое решение с подробными разъяснениями (фокус с последующим разоблачением).

Итак, смотрим чертеж.

Microphone amplifier LM358

Микрофонный усилитель на LM358 / Microphone amplifier LM358

Слева подключаем микрофон, справа подаем выход усилителя на АЦП микроконтроллера. Пусть обозначения цветов вас не смущают — это маркировка проводов в моем монтаже.

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

Поэтому пусть будет универсальное решение — вход нашего усилителя будет высокоомным, поэтому подаем сигнал с микрофона на (+) вход операционного усилителя — ОУ. И давайте примем, что сопротивление входа (+) ОУ будет бесконечным, поэтому входную нагрузку будут определять параллельно включенные R2 и R3 (да — да, вы не ослышались —  для сигнала что питание что земля все едино, поэтому микрофон получает нагрузку 50 кОм).

Лирическое отступление — почему на (+), а не на (-)? Ведь входы ОУ равноценны и образуют дифференциальный транзисторный каскад. Все дело в отрицательной обратной связи через R6, благодаря которой вход (+) становится высокоомным. Как это работает — рассказал на примере эмиттерного повторителя .

Давайте сразу про обратную связь. Она весьма двулична и ведет себя по-разному по постоянному и переменному току. По постоянному току играет R6 (и в этом варианте он вообще не нужен — мы могли бы накоротко замкнуть выход ОУ и вход (-)). Обратная связь получается 100 — процентной, поэтому коэффициент усиления каскада по постоянному току — единица, и средняя точка, которую устанавливает делитель R2/R3, будет такой же на выходе операционника, то есть — половина напряжения питания.

Теперь про обратную связь по переменному току. Здесь играет конденсатор C3, который закорачивает нижний конец резистора R4 на землю, в результате чего в цепи обратной связи появляется делитель R6/R4. Тут уже резистор R6 выполняет свою роль и совместно с R4 устанавливает коэффициент усиления каскада по переменному току, равный 100.

Конденсатор C4 давит усиление на высоких частотах, что лишает широкополосный LM358 возможности самовозбудиться на высоких частотах (вполне реальная перспектива, если фазовый сдвиг в цепи обратной связи получится другим нежели чем 180).

Поскольку режимы по постоянному току для микрофона и входа (+) ОУ — разные, изолируем эти цепи конденсатором C1. Надеюсь, не надо объяснять, что он проницаем для входного сигнала, и неожиданно маленькая емкость — 0.1 мкф совсем не большое препятствие, потому что опять таки — вход высокоомный.

Как вы наверное догадались, линия в верхней части, уходящая вдаль направо — это питание 3.3 В. Столько я рассчитываю получить с USB смартфона (ну вот, проговорился про еще про один кусок будущего проекта).

Для LM358 такое напряжение — нормально. Заметим, что выходной сигнал будет меняться не относительно земли (разделительного конденсатора на выходе ОУ нет), а между землей и 3.3 В — что АЦП и надо.

Для чего нужна цепочка R5C2? Практикующие инженеры знают, что мусор в цепях питания — обычное дело. На чувствительном входе каскада ОУ он нам совсем не нужен, и поэтому фильтр R5C2 будет прибивать все что по частоте выше постоянного тока (в идеале).

Пара электролит — керамика для сброса мусора на землю также стоит в правой части, которая не видна. Зачем керамический конденсатор маленькой емкости в параллель с электролитическим? Последние имеют неприятное свойство — паразитную индуктивность. Поэтому с ростом частоты они будут не очень хорошим конденсатором.

На макетке операционник в левой части. Особо зоркие могут заприметить транзистор в правой части — не обращайте на нее внимание, это очередной кусок проекта (который надо полностью поменять).

В результате экспериментов выяснилось, что усилитель держит неплохую полосу от 100 Гц до 10 кГц и негромких песен вполне достаточно до раскачки выхода от 0.5 В до 2.5 В.

PyTorch: fast array computations on GPU

When using numpy at the beginning of a project we don’t think about performance. This is because it is more important for us to get solution that works. And only then, with real data, observing how our computer begins to slow down, we have to find answer on how to avoid this. And then we remember the GPU inside our computer and think: can it help us?

Yes, sure. PyTorch can utilize GPU and perform all computations on it. PyTorch uses Tensor primitives like numpy arrays. Unlike numpy, once declared tensors reside on GPU, and are calculated on it. Due to a lot of the parallel working GPU inits, computing performance arises dramatically.

We will take the matrix multiplication algorithm for the example.

First, let’s make sure the multiplication computations are performed correctly.

 

Output:

 

Everything is as expected.

Now, tensor comes into play. At this point, all the necessary modules and drivers have already been installed:

 

Output:

 

Let’s allow PyTorch to multiply matricies:

 

Output:

 

The result is the same as for the numpy.

Take a larger matrices and see how much time it takes to calculate in each of the three modes:

1) PyTorch in «cpu» (numpy — like) CPU only mode
2) PyTorch in «cuda» (GPU) mode
3) Numpy CPU mode

 

Output for M=100

 

Output for M=1000

 

Output for M=10000

 

For small matrices (M=100) numpy looks like a performance champion. Even torch in cpu mode loses to it. The worst results are observed for the GPU.

For a medium-sized matrices (M=1000) the results are leveled out. Numpy and torch in cpu mode are the same and only torch in gpu mode is far behind.

At the moment, everything looks sad for GPU. It makes a real breakthrough for large-sized (M=10000) matrices. Here the gap in execution speed is huge: 37 milliseconds for GPU versus 12 seconds for numpy.

Simultaneous computing is good

 

Дружище C.Elegans и его нейрокомпьютер

Когда люди начали создавать компьютеры, сравнение с аналогичной продукцией живой природы не заставило себя ждать. Все (за редким исключением) сошлись во мнении, что нейросистемы животных слишком сложны и медленны, чтобы быть достойными более глубокого изучения. Здесь под изучением я подразумеваю не биохимический анализ нейронов, а понимание уровня системы: по каким алгоритмам она работает и самое

Читать дальше

Автоматическое обнаружение радиолокационных станций визуальной подсистемой БЛА

По сравнению с такими шустрыми средствами, как противорадиолокационные ракеты (Anti Radar Missile) беспилотник выглядит сущим недоразумением. Однако, это всего лишь следствие инерции мышления, когда на самом деле главный недостаток БЛА по сравнению с ракетой — низкая скорость, становится преимуществом в следующих случаях:

нужно хорошенько рассмотреть цель; можно позволить себе прогуляться для того чтобы выбрать что-либо

Читать дальше

Техника образов. Сейчас и раньше

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

Читать дальше

Обработка потока с видеокамеры средствами ПЛИС/FPGA. OV7670 + Zedboard

В статье маркеры визуальной системы автоматической посадки БЛА была описана обработка видео-данных реального времени для системы оптического типа — VBLS. Это была хоть и шустрая, сделанная на C++, но модель. Теперь, как было замечено в конце этой статьи, пришло время показать систему в боевом варианте, где обработка видеопотока параллелится в ПЛИС.

В данной системе для

Читать дальше

Маркеры визуальной системы автоматической посадки БЛА

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

Читать дальше

Каким быть военному радиопеленгатору

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

Читать дальше

STM32: добавляем Ethernet

В предыдущей статье я рассказывал о том, как хорошо работать с микроконтроллерами STM32 без операционной системы. Однако может наступить момент, когда вам понадобится наладить взаимодействие с вашим МК по сети. Например, что-нибудь типа умного дома или передача отладочной и диагностической информации на web страницу. Наличие Ethernet интерфейса — это конечно сильный аргумент для того, чтобы

Читать дальше