Устанавливаем OpenCV 3 для Python 3 на macOS

Конфигурация OpenCV 3 + Python 3 на macOS внезапно оказалась немного неожиданной в настройке. Изначально я ожидал что надо будет пару раз вызвать Brew, но оказалось несколько сложнее. Последовательность:

Смотрим где находятся site-packages для интерпретатора, которым собираемся пользоваться, понадобится ниже для создания симлинки на OpenCV:

>>> import site; site.getsitepackages()
['/usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages', '/Library/Python/3.6/site-packages']
>>>

Ставим, собираем, делаем линку (если нет TBB и/или Qt5, ий стоит либо поставить заранее, либо убрать соответствующие флаги):

brew install numpy --with-python3
brew install opencv3 --with-tbb --with-qt5 --with-python3 --with-examples --with-contrib --c++11
sudo mkdir -p /Library/Python/3.6/site-packages
sudo ln -s /usr/local/opt/opencv3/lib/python3.6/site-packages/cv2.cpython-36m-darwin.so /Library/Python/3.6/site-packages/cv2.so

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

>>> import cv2; cv2.__version__
'3.2.0'
>>>

Vim и проверка орфографии

Так как мой основной рабочий инструмент – Vim (в комбинации с ZSH) я давно хотел прикрутить к нему проверку орфографии. Да и что там, собственно, прикручивать – все идет “из коробки” и остается только подключить то, что нужно. Оказалось всё и вправду проще не придумать.

Во-первых, нужно слегка подправить .vimrc для автоматического включения проверки орфографии на определенных типах файлов/буфферов:

autocmd BufRead,BufNewFile *.md setlocal spell
autocmd BufRead,BufNewFile *.txt setlocal spell
autocmd FileType gitcommit setlocal spell

Ну и потом не забыть о паре комбинаций:
z= – отобразить список замен;
zg – добавить слово в список корректных слов.

И работать как-то приятнее стало

Позиция ведущего разработчика в Сингапуре

В нашей команде в Autodesk Singapore есть вакансия ведущего разработчика на C++. Требований совсем чуть-чуть: хорошее знание C++ с навыком работы как с C++98, так и C++11/14, опыт в кроссплатформенной разработке (Win/Lin/OSX) и английский (разговорный + письменный) достаточный для работы в англоязычном коллективе. График работы гибкий, в разумных пределах. Удаленная работа невозможна. Оплата 85-120K SGD в год до налогов (коих тут будет около 7%).

С учетом того, что у нас довольно большой зоопарк технологий, знание Python, Java и C# будут плюсом (перечислены в порядке важности).

Немного о нас: пишем кроссплатформенную систему лицензирования для всех продуктов компании, в основном клиентскую и немного серверные части.

Контактное лицо: Vinod Babu, vinod.babu@autodesk.com с копией мне: alex@sysdev.me.

CppCon 2016

Наконец-то сбылась мечта и я побывал на CppCon. Очень надеюсь что теперь это будет на регулярной основе

Крайне рекомендую к просмотру следующие выступления:

  • High Performance Code 201: Hybrid Data Structures;
  • Rich Code For Tiny Machines: A Simple Commodore 64 Game In C++17;
  • Using weakly ordered C++ atomics correctly;
  • Lifetime Safety… By Default: Making Code Leak-Free by Construction.

Просто рекомендую. Время зря потерянным однозначно не будет:

  • C++ Coroutines: Under the covers;
  • The Guideline Support Library: One Year Later;
  • Deploying C++ modules to 100s of millions of lines of code;
  • Keynote: Developing Blockchain Software.

async && GCD

std::async из C++11 хорош практически всем: прост, удобен, универсален. И только одна особенность стандарта несколько портит этот праздник – нет четкой регламентации того, где и как должна выполниться асинхронная задача; задача может быть выполнена как в отдельном потоке, так и в пуле потоков. В итоге это приводит к тому, что разработчики STL не утруждают себя пулом потоков (даже при наличии оного в ОС по-умолчанию) и плодят по протоку на каждый std::async вызванный с флагом std::launch::async. В случае с macOS, как мне кажется, это довольно большая оплошность, так как в системе уже есть готовый пул потоков, которым остается только воспользоваться!
В итоге я немного поковырялся в стандарте, доступных реализациях и вышло у меня следующее: Continue reading

Перенаправление временных файлов CMake

CMake, конечно, прекрасен, но мне совершенно не нравится его особенность “гадить” в директорию из которой он был запущен. Какого-либо явного способа сказать CMake – положи все свои промежуточные файлы в директорию XYZ нет, кроме не документированного ключа с кривоватым поведением. В итоге, почти все виденные мной основанные на CMake проекты просто не парятся и мирятся с тем мусором, что образуется у них в корне. Можно, конечно, мириться, но есть и варианты

По большому счету варианта два и оба они не кроссплатформенные: написать скрипты – запускалки (sh и bat) или написать Makefile. Вариант со скриптами на мой взгляд более кривой, так как требует реализации того функционала, который уже предлагается Make. Так что я остановился на Makefile следующего содержания:

SHELL := /bin/bash
RM    := rm -rf
MKDIR := mkdir -p

all: ./build/Makefile
    @ $(MAKE) -C build

./build/Makefile:
    @  ($(MKDIR) build > /dev/null)
    @  (cd build > /dev/null 2>&1 && cmake ..)

test:
    @  (cd $(BUILD_DIR) > /dev/null && ctest -L unit --verbose)

clean:
    @ $(MAKE) -C $(BUILD_DIR) clean

distclean:
    @  ($(MKDIR) build > /dev/null)
    @  (cd build > /dev/null 2>&1 && cmake .. > /dev/null 2>&1)
    @- $(MAKE) --silent -C build clean || true
    @- $(RM) ./build/Makefile
    @- $(RM) ./build/src
    @- $(RM) ./build/test
    @- $(RM) ./build/CMake*
    @- $(RM) ./build/cmake.*
    @- $(RM) ./build/*.cmake
    @- $(RM) ./build/*.txt

И теперь мне ни мусор не досаждает, ни проблем с вызовом тестов/перегенерацией основного рабочего Makefile нет. Ну и заодно добавил генерацию запускалки в свой gen-cmake.

Да, еще полезно будет поправить сам CMake файл, запретив генерацию временных файлов в корневую директорию следующим образом (добалять в самое начало CMakeLists.txt):

if ( ${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR} )
    message( FATAL_ERROR "In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there. You may need to remove CMakeCache.txt." )
endif()

Форматирование C++ кода

Одна из особенностей языка Go, которая мне очень нравится – стандартизация практические всего и вся с предоставлением инструментов для валидации и максимальной автоматизации применения. Так все программы на Go выглядят более-менее одинаково как за счет единого стандарта к разработке (да,я не люблю кучу соплей с проверкой результатов возврата, но тем не менее это единообразие) так и за счет единого форматирования. Благодаря этому не приходится испытывать какого-то серьезного дискомфорта разбирая новый кусок кода – каким бы (не)качественным он ни был, выглядеть и как следствие восприниматься он будет как родной. Кроме того, основная масса редакторов Go поддерживает переформатирование текста при сохранении, так как за формат отвечает косольное приложение, то появляется возможность поставить триггеры в VCS и отклонять не удовлетворяющие условиям коммиты. С одной стороны, все это может казаться мелочами. Но только до тех пор, пока ты не работаешь в довольно сильно распределенной команде с крайне разными уровнями у разработчиков.
Continue reading

FFI и Rust

Продолжаю бороться с типами. Есть надежда, что FFI (Foreign Function Interface) – это самая сложная и последняя часть, где система типов в Rust будет доставлять серьезные неудобства. Пока что, главное выстраданное правило гласит: если тебе Rust не дает написать какую-то конструкцию, то эта конструкция зло. То есть не надо пытаться обмануть систему типов и писать “как привык в C++”. Довольно простой, если верить документации на сайте Rust, интерфейс FFI оказался с заковырками. Даже пришлось создать маленькую песочницу для игр именно с FFI.

Наверное самая поразившая меня фича в этой области Rust-а – преобразование типов, особенно при работе с массивами. Простейший пример (type_of из предыдущего поста):

let array: &[u8] = unsafe { mem::transmute("Rust") };       // (1)
println!("type: {}, ptr: 0x{:x}, len {}",
    type_of(&array), array.as_ptr() as u64, array.len());

let new_array: &[u32] = unsafe { mem::transmute(array) };   // (2)
println!("type: {}, ptr: 0x{:x}, len {}",
    type_of(&new_array), new_array.as_ptr() as u64, new_array.len());

Классический вопрос из разряда “а что оно напечатает?”. Кажется что всё невероятно просто, создаем массив 1 из uchar, размер которого будет 4. Конвертируем 2 массив uchar в массив uint32 с размером 1. В итоге лично я ожидал чего-то такого:

type: &'static [u8], ptr: 0x1037a1414, len 4
type: &'static [u32], ptr: 0x1037a1414, len 4

Но был сильно удивлен. Дело в том, что по мнению компилятора Rust второй массив хоть и является массивом uint32, но по прежнему содержит 4 элемента, т.е. конверсия делается в лоб и только для типа, но не размера и физический размер “вырос” в 4 раза без перераспределения памяти.

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

let new_array_2 = unsafe {
    slice::from_raw_parts_mut(array.as_ptr() as *mut u32,
        array.len() / mem::size_of::<u32>())
};
println!("type: {}, ptr: 0x{:x}, len {}",
    type_of(new_array_2), new_array_2.as_ptr() as u64, new_array_2.len());

Хотя меня гложут сомнения на тему того, что я правильно всё делаю, так как вывести новый размер массива вроде очень просто из чего следует что я вызвал какую-то неправильную функцию, или правильную, но криво…

Мелкие пакости: время жизни переменной в Rust

Допустим, хочется получить текстовое представление типа переменной в Rust. При этом в язык входит такая замечательная функция как type_name() -> &’static str принимающая тип выдающая его тектовое обозначение. Само собой, хочет применить его не только для типа (название типа не так уж и полезно в диагностических целях), а к переменной. Логичным для C++ разработчика выглядит приблизительно следующее решение:

fn type_of<'a, T>(_: T) -> &'a str {
    unsafe { std::intrinsics::type_name::<T>() }
}

Но тут возникнет довольно занятная проблема, так как переменная становится недоступной после (с некоторыми ньюансами в зависимости от типа) получения её имени:

error: use of moved value: `*variable_name` [E0382]

После небольшой фрустрации понимаешь, что в принципе это ж фича и компилятор не должен догадываться о моих намерениях только лишь получить тип, а не реально использовать значение. Но делать что-то нужно. Единственным подходящим решением оказывается передача по ссылке (ссылке в понимании Rust, а не C++), что ожидаемо, но немного странно для C++ разработчика.

fn type_of<'a, T>(_: &T) -> &'a str {
    unsafe { std::intrinsics::type_name::<T>() }
}

Вообще, все эти мелкие пакости модели памяти постоянно преследуют при программировании на Rust. Никак не могу понять, это реально зло или я просто еще не привык и просто мыслю моделью памяти C++?

Rust в Dropbox

Как мне кажется, не так давно произошло довольно знаковое событие для мира C++: Dropbox заявил о том, что перевел часть своей инфраструктуры на компоненты написанные на Rust. Чуть позже программисты из Dropbox ответили на вопросы интересующихся посвященные этой миграции. Немного интересных ответов:

> Are you happy using rust ?
Yes, overall the team has been very pleased with it. Compile times are the only serious complaint.

> How many lines of rust code are you using in production?
About 60k of our own, about 300k incl crates.

> Are you using nightly? or just stable version of rust?
We’re pinned to a particular nightly right now, as we rely on a fair amount of features that are still stabilizing. I imagine we’ll be on a stable by this summer.

> What drove the move to Rust precisely? Most of the players in the industry are somewhat reluctant about moving to Rust.
Well, we basically needed C++, but:
1) Dropbox doesn’t have a strong C++ ecosystem on the backend, and it takes a lot of work to build one.
2) Given (1), we had basically a blank slate, and our team is C++ and Haskell type folks, we decided to use Rust so we could get C++ with better type safety and fewer sharp corners.
So realistically, if we had been at a place with an awesome preexisting set of C++ libraries, practices, etc, we probably never would have used Rust.

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