Программирование

Опыт переноса пары приложений на Apple Silicon

Волею судеб и компании Apple пришлось тут перенести пару приложений на Apple Silicon (M1) и этот опыт хочется описать.

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

Перенос на ARM/создание ARM64-приложения

По счастью, все используемые нами библиотеки умеют собираться под ARMv8, то есть там нет жесткой привязки к x86.

Многие из них умеют в оптимизацию под ARM-NEON и в этом месте возможна засада:  на Linux/Android принято получать информацию о возможностях ARM-CPU через getauxval()...

Чего ни хватишься - ничего нет

Собирая тут всякое (по длинному списку) на Mac с процессором M1 возжелал я иметь все в Universal binaries (это когда в одном флаконе бинарники/объектники для нескольких процессорных архитектур). Я так, в принципе, уже делал, вот RawDigger/Legacy собирается до сих пор с 32/64 бита в одном флаконе и ничего.

Если кто не в курсе, то маковский toolchain понимает clang -arch arm64 -arch x86_64 и в этом случае:

  • Компиляция запускается (внутри) два раза, для разных архитектур
  • Порождается один объектный файл в
  • ...

Про HEVC/HEIC

(скопирую из фейсбука, чуть расширив, может и пригодится кому)

Довелось тут залезть в потроха формату HEIC/HEIF, не могу не высказать к нему свое отношение, хоть никто вроде и не спрашивал.

Summary: хороший формат, с потенциалом.

Что там хорошего:

  • есть 8 и 10 бит (может быть и больше, но в дикой природе никто не видел)
  • Сжатие H.265 гораздо лучше (старого) JPEG при том же/сравнимом качестве.
  • Контейнер (MP4/ISO BMFF) тоже удобный, удобно разбирать, можно что хочешь положить (стандартно кладут EXIF и
  • ...

И еще про переезд с Qt 5.4 на 5.12

Продолжение опупеи, добавление в этому и этому.

Начиная с Qt версии 5.6 там изменилась поддержка HiDPI-дисплеев, внутренний фактор масштабирования может быть дробным (а не 1/2 как для поддержки Mac Retina). Масштабирование может быть автоматическим (стандартно это выключено), но что хуже всего - оно управляется, в том числе, переменными окружения.

Дальше происходит интересное: пользователь (по рекомендации из интернета) или инсталлятор какой-то Qt-based софтины ставит одну из вышеописанных переменных в глобальное окружение и... все Qt-based приложения разъезжаются (если, к...

Лучшее - враг хорошего

В продолжение предыдущей записи:

  • Поступила жалоба от пользователя: в свежей версии FRV под  macOS не копируются locked-файлы
  • Даже не залезая в исходники Qt (я знаю что я там увижу) тут же догадался
    • QFile::copy раньше не копировал атрибуты файла (в частности, uchg)
    • А в новой версии - копирует.
    • А FRV копирует файлы правильно
      • Сначала со временными именами в папке назначения
      • И только если все нужные временные файлы (а  одна операция копирования - это может быть много файлов: RAW, JPG, .XMP) создались - происходит переименование временных в нужные имена.
      • А переименование для файлов с атрибутом uchg - запрещено в macOS.

Весь фикс, понятно, две строчки (в принципе, можно было бы и одну), потому что все запчасти нужные уже готовы (для переименования locked-файлов давно есть и нужный код и настройка "разрешать или нет").

Но я о том, что просто подмена Qt*.dll (или, в случае macOS .framework) с более старых на более новые - хоть и должна работать внутри одной major-версии Qt, но на практике - не работает.

Опыт обновления версии Qt в FastRawViewer

Бэрримор, кто это ночью выл на болоте?
Простите, сэр, накопилось!

Мы тут, в порядке общего улучшения всего, осуществили переезд с версии Qt 5.4 (на Mac использовалась вовсе 5.3, потому что в 5.4.2 были некоторые глюки с OpenGL) на Qt 5.12.

Начинали с Qt 5.12.5, закончили Qt 5.12.8 + патч, который будет только в Qt 5.12.9. Незадолго до того, переехали вовсе с...

Многопоточность здорового человека и многопоточность курильщика

В прошлой серии своих упражнений я дошел до того, что уперся в malloc. И успокоился. Но нет. Что-то чесалось и я решил таки продолжить, как выяснилось - не зря.

Чтобы не упираться в malloc, я перешел к такой единичной задаче:

  • Передаем в worker указатель на данные (и размер)
  • Считаем fnv64
  • Результат - 64-битное число, которое копируется без аллокации данных и подобного.

Кроме того, мне очень хотелось добиться двух вещей

  • Вписать всю конструкцию в Qt-шные signal/slot, то
  • ...

Многопоточность: производительность map/reduce

В очередной раз профайля одну программу, обратил внимание на большой spin time (на мьютексе) в достаточно неожиданном месте, а именно в QtConcurrent::blockingMappedReduced (и в подобных). Пришлось сделать стенд и сравнить с TBB (спойлер: TBB нигде не хуже, а есть где и прямо вот в разы лучше).

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

Еще о многопоточности

Три недели в углу лежал незакрытый гештальт, надо закрывать.

Если кто помнит, то в прошлой серии мы дошли до того, что Qt-шная система signal-slot плохо масштабируется в многопоточном случае и нужно для передачи данных между потоками использовать что-то еще. Ну, к примеру, lock-free очередь (из TBB или вот эту, такой уж большой разницы я пока не обнаружил, хотя и должна быть).

Ну что ж, берем делаем приложение:

  • пачка потоков (1...32) делает какую-то простую работу (копирует строку)
  • складывает
  • ...

Ускоряем многопоточную программу в 150 раз за три простых шага

Во вчерашней истории я, не задумываясь, предложил два способа решения проблемы congestion у Qt signal/slot в многопоточном случае: выдавать результат работы потока пачками и/или вообще выдавать его другим способом (через неблокирующую очередь).

Вчера вечером я проверил первый способ (сегодня задокументировал-нарисовал графики) и вот что у меня получилось:

Исходный псевдокод собственно обработчика выглядит так:

while(job = nextJob()){ result = processJob(job); emit processed(result); }

Давайте накопим результаты в пачку и выдадим эту пачку принимающей стороне:

while(job = nextJob()){...

Закон Амдала против 'interrupt rate'

Давно не писал о программировании, а тут вдруг повод появился.

Допустим, мы хотим на Qt в много потоков что-то фигачить. Ну, к примеру, читать метаданные файлов (или метаданные RAW-снимков из RAW-файлов). Понятно, что в реальной жизни это все упирается в диск, но если все уже было прочитано и закэшировано, то нет.

В реальной жизни прекрасно работает вот такой вот паттерн: запустим N threads и в каждой из них будем делать как-то так:

while(job = nextJob()){ result =...

QtCreator: generated AndroidManifest.xml workaround

Еще в комплект записок про Qt+QML+Android.

При попытке задеплоить настоящий уже релизный apk, с манифестом и т.п. наткнулся на QTCREATORBUG-20419

Обход, вкратце (для тех кто не понимает, что написано в AndroidManifest.xml, вроде меня):

  • Открыть сгенерированный AndroidManifest.xml в простом текстовом редакторе
  • Найти строчку с android:configChanges="orientation|uiMode|.....
  • Убрать оттуда |density
  • и не спрашивайте меня, что это значит

После чего сборщик (gradle) начинает работать (и полученная в результате apk-шка - ставится на устройство).

В комментариях поминают еще QTBUG-74432 но я на него не натыкался.

В очередной раз убеждаюсь, что деплой (в частности, Qt apps, но думаю что не только) - одно из самых сложных мест, сколько ж там грабель разложено, что в винде, что на маке, что вот тут вот. Всякие *deployqt помогают, но на практике там есть всякие смешные баги и после них на практике же всегда есть доработка напильником. Одна радость - в это место ходить приходится с частотой релизов host OS, не чаще.

Настройка Qt Creator для разработки под андроид: быстрый старт под Windows

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

Загорелось мне тут сделать программу одну для телефона, чисто для себя. Ну, понятно, Qt+QML, потому что Qt я знаю, QML видел издалека, ничего сложного не запланировано. Как и всегда, главный вопрос "как начать" (т.е. настроить все, чтобы оно работало, компилировало, загружало в телефон/эмулятор), я на него потратил минимум...

Два слова про libtiff

Все-таки вот загадочная вещь этот ваш libtiff.

TIFF-файлы, как мы знаем, бывают tiled, бывают striped, а бывают одним куском (это, конечно, один страйп и никак иначе). Когда оно одним куском - стандартно собранная libtiff умеет эмулировать страйпы вменяемого размера (для тех, кто читает полосками), но речь не об этом.

Понятно что striped - это частный случай tiled, просто тайлы имеют ширину со все изображение. Но не наоборот.

И вот допустим некто (то есть я) хочет читать картинку банальным TIFFReadScanline(), но...

Снова трудо-выебудни

Повозившись сначала с libpng (формат простой и если следовать заложенным там идеям, то как бы и ничего так), а затем в libtiff (формат, как мы знаем, весьма развесистый)....

Я начинаю понимать популярность всяких OpenImageIO, FreeImage, ну на худой конец стандартных кодеков типа Qt-шных (говоришь ему read("file.png") оно само разберется что и куда).

Потому что та же libtiff - это, на самом деле, тихий ужас:

  • Есть TIFFReadRGBAImageOriented, которая может многое (Grayscale, RGB, Lab, CMYK - все вернет в виде RGBA). Правда с Lab/CMYK оно делает это как-то криво...., а какие-нибудь 12-битные RGB или 16-bit FP вовсе не умеет.
    Правда интерфейс там такой, что если исходный TIFF повернут на 90 градусов, результат TIFFReadRGBAImageOriented вас сначала огорчит, а потом (когда поймете отчего он такой) позабавит.
  • Поэтому есть TIFFReadSсanline - и можно читать по строчкам.
    Правда бывают TIFF-файлы, где строчек нет, а есть тайлы
  • Поэтому есть TIFFReadTile и можно читать тайлы.... ну уже криво, тайлу надо давать буфер под тайл, поместить его прямо в выходной растр нельзя, придется копировать.
    Правда бывают файлы, где цветовые компоненты записаны отдельно....
    Правда бывают файлы с тегом TIFFTAG_IMAGEDEPTH (про который я вообще не нашел ничего вменяемого за две минуты - и успокоился)

И вот не знать бы этих всех подробностей бы.....

И это как бы не говоря о том, что для striped/tiled tiffs конечно бы декодировать их многопоточно бы (чего libtiff не умеет).

Ну то есть мы тут все привыкли, что в RAW - бардак, ну OK. Но, э, если смотреть на "индустриальный стандарт" (TIFF и libtiff как имплементацию) - ну тоже невозможно сказать что все хорошо, ну то есть для чтения FP16-tiled-RGB tiff придется самому написать большую гору кода.

Трудо-выебудни

А вот, например, Adobe Bridge:

  1. Читает TIFF-файлы (ура).
  2. Умеет их повернуть (тоже неплохо). Делает это сменой тега Orientation.
  3. Но: если в файле содержится несколько изображений (т.е. image pyramid в терминах фотошопа), то Orientation меняется только у IFD0.
  4. При этом стандарт конечно умалчивает "как правильно поворачивать TIFF", но поскольку в IFD1..n прочие описания изображения (ну там размер, BitsPerSample, etc) описывают данное "под"-изображение, логично думать что и Orientation там верный (если этот тег есть, понятно что если его нет, то надо
  5. ...

Душераздирающее о Qt

Я на это уже жаловался, но в фейсбуке, а не тут.

Но просматривая сегодня Qt mailing lists/development не смог пройти мимо:

Несчастный пользователь пишет:

I'd like to point out how badly this issue has been handled. It's been reported on February 2016 and marked as P4.To me, it seems a nonsense to ignore issues like this, especially now that Qt3D offers a lot of possibilities and sooner or later you might need to switch your app to core profile....

Про Qt

1 апреля 2014 года я порепортил (очередной) баг в Qt4

Прошло 4.5 года, его наконец закрыли:

Thank you for the report. Qt 4 is no longer supported, please check if this is still an issue with a recent Qt 5 version, in which case you can re-open this report.

Молодцы, че.

Ссылок не даю, потому что не единственный случай, а паттерн всегда один и тот же:

  • В актуальной (на момент репорта) версии - всем насрать (я уже воспроизводить-репортить начал в свежих из принципа)
  • Через несколько лет - отписка "эта версия не поддерживается, попробуйте в новой"

Дать им денег что ли, может будет иначе....

AVX2 speedup

На одном и том же CPU (i7-7700k на базовых частотах), переход с SSE3 на AVX2 дает выигрыш, если по отдельным кускам:

  • Half-демозаика + ББ + цветовая конверсия + тоновая кривая, запись RGB bitmap: 1.97/1.11 sec (SSE/AVX)
  • Half-демозаика + ББ + цветовая конверсия + построение гистограммы: 2.11/1.483
  • Чтение raw-данных (int16), преборазование в float, вычитание черного, построение RAW-гистограммы: 1.78/1.72. Гистограмма - это очень медленно.....

Тестовый набор: 22 файла Sony A7RM2, т.е. ~900Mpix в сумме. Чиселки - CPU time (не wall time, потому что мультитрединг).

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

 

Про AVX2 и размывание кэшей

Вот наконец удалось ощутить офигенную пользу от AVX2, причем двойную. Вот такой вот код:

_mm256_stream_si256((__m256i *)&drowp[col], _mm256_i32gather_epi32((const int*)table, _mm256_cvttps_epi32(_mm256_load_ps(&srowp[col])), 4));

в 4.5-5 раз быстрее, чем простой SSE2 аналог (в котором, понятно, нет gather) и в ~6 раз быстрее скалярного C-кода:

drowp[col] = table[(unsigned)srowp[col]];

Рассмотрение всего хозяйства под микроскопом показало, что основной взнос в результат дает _mm256_stream, а вовсе не gather. Стоит заменить stream на store, как все сразу портится. По достаточно очевидной причине: длина строки и drowp и srowp -...

Pages

Subscribe to Программирование