Python медленнее чем c

Почему программисты пишут программы на C++ и подобных сложных языках, если на Python синтаксис проще и можно написать программу быстрее?

В Python действительно больше синтаксического сахара, чем в C++, поэтому разрабатывать на нём проще. Однако, если не брать в расчёт синтаксис, C++ выигрывает у Python по следующим причинам:

1) С++ является языком со статичной типизацией, поэтому соответствия данных заданным типам выполняются на этапе компиляции. В Python динамическая типизация, данные могут быть какого угодно типа на этапе компиляции, а проверки на соответствие производятся на этапе выполнения программы. Так же в статической типизации структура переменной — это указатель на адрес в стеке или в куче, где лежит значение. В Python структура переменной — это указатель на структуру, в которой есть указатель на адрес структуры типа данных и указатель на адрес значения. Очевидно, что C++ здесь выигрывает по компактности хранения данных и по скорости доступа к ним. Если приложение работает с большими объёмами данных и хранит их в памяти, борьба за скорость и экономию RAM идёт не на уровне байтов, а даже битов. В этом случае выбор C++ является скорее необходимостью.

2) В C++ широкие возможности по управлению памятью. Когда и где выделить память под объект и высвободить память, решает программист. В Python же программист полностью лишен рычагами управления памятью. Положить данные на стек или в кучу, и когда освободить память, решает язык. Здесь C++ тоже выигрывает, т.к. возможно более рациональное управление памятью, и нет, хоть и небольших, но всё таки затрат на сборку мусора.

Читайте также:  Css класс ссылки joomla 3

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

Источник

Python медленнее чем c

Перед тем как мы начнем

Мы живем в эпоху Twitter, Instagram и прочих сервисов коротких сообщений. Поэтому для простоты восприятия мы разделили статью на две секции. Первая – краткий и сжатый обзор экспериментов и производительности. Вторая – подробный разбор полетов и найденных багов, который будет интересен тем, кто решит повторить эксперименты.

Приступим!

Итак, у нас есть стереокамера StereoPi на базе raspberry Pi Compute Module 3+, и мы хотим получить карту глубин по видео в реальном времени. Для этого нам надо пройти несколько этапов:

— Собрать устройство и проверить, что все работает правильно

— Сделать серию снимков для калибровки стереокамеры

— Откалибровать стереокамеру, используя сделанные снимки

— Настроить параметры карты глубин

— Получить карту глубин по видео в реальном времени

— Получить 2D карту пространства в реальном времени

Для каждого из этих шагов у нас есть готовые скрипты на Питоне и они же, перенесенные на C++. На GitHub питоновые скрипты живут тут, а C++ вот тут.

Шаг 1 – тестируем скорость захвата видео

На этом этапе мы просто захватываем стереоскопическое видео с камер и отображаем его на экране. В Python мы используем для этого библиотеку PiCamera, а на C++ – передачу через pipe от самого шустрого из нативных приложений, а именно raspividyuv. Подробности можно прочитать ниже в разделе «Детальный разбор полетов».

Вот как выглядит процедура компиляции и запуска на экране:

Сравниваем скорость С++ и Python на примере стереозрения в OpenCV на Raspberry Pi

Тут мы попытались захватить стереокартинку с разрешением 640×240 (два кадра по 320×240). Но 320 не кратно 128. Видно, что кадр с левой камеры более узкий (256 пикселей), а правый нормальной ширины (320 пикселей).

Но пугаться не надо. Есть два пути:

  • либо установить разрешение согласно правилам 384×240 для каждого кадра (или 768×240 для стереокартинки)
  • либо захватывать большее разрешение, и уменьшать до требуемого силами GPU (это вообще решает множество проблем). В нашем коде мы захватываем 1280×480, и уменьшаем картинку в два раза (значение scale_ratio установлено в 0.5 в строке 48 первого скрипта на Python). Мы рекомендуем этот способ, так как он сильно упрощает жизнь (и, кстати, не нагружает дополнительно процессор).

Повторим тут часть из первого раздела статьи. На самом деле, в Python можно использовать способ захвата, аналогичный применяемому в коде на C++ – передача видео через pipe шустрой утилитой raspividyuv. Второй подход – решение, предложенное автором PiCamera в одном из ответов на Raspberry.stackexchange (метод numpy.frombuffer, без лишнего копирования данных в памяти).

Скрипт 4 – калибровка, нюансы

Сначала пару слов о лайфхаке, который сильно улучшает поиск шахматной доски на изображении. Нам надо откалиброваться для разрешения 320х240, но на картинках такого разрешения шахматная доска детектируется плохо. Поэтому мы снимаем картинки вдаое большего разрешения (640х480), ищем на них координаты углов шахматной доски, а потом уменьшаем найденные координаты в два раза по X и по Y. И только потом отдаем их в механизм калибровки. Если вам нужна еще более высокая точность – можете откалиброваться на 1280х960, и уменьшить найденные координаты в 4 раза.

Одинаковое бывает разным

Занятное наблюдение – при одинаковых параметрах поиска шахматной доски код на питоне находит ее на большем количестве изображений. Вы заметили на видео, что Python откидывает всего одну пару, где он не нашел шахматную доску, а C++ с десяток. Большинство картинок, где не найдена шахматная доска – это фото, где доска находится на большом расстоянии от камеры. Я не вижу иного объяснения кроме как разницы в алгоритмах OpenCV 4.1.1 и 4.1.0 (первая версия у нас используется в C++, вторая в Python).

Баги при калибровке Python.

Если запустить калибровку со всеми картинками, имеющимися в примерах, то калибровка выпадает с ошибкой:

Traceback (most recent call last): File "/home/pi/stereopi-fisheye-robot/4_calibration_fisheye.py", line 297, in result = calibrate_one_camera(objpointsRight, imgpointsRight, 'right') File "/home/pi/stereopi-fisheye-robot/4_calibration_fisheye.py", line 170, in calibrate_one_camera (cv2.TERM_CRITERIA_EPS+cv2.TERM_CRITERIA_MAX_ITER, 30, 1e-6) cv2.error: OpenCV(4.1.0) /home/pi/opencv-python/opencv/modules/calib3d/src/fisheye.cpp:1372: error: (-215:Assertion failed) fabs(norm_u1) > 0 in function 'InitExtrinsics' 

Это очень неприятная ошибка, и ее подлость в том, что трудно понять логику ее появления. На самом деле, в данном случае ее вызывает картинка с номером 23. Причем эта картинка вполне хорошо выглядит, на ней отлично детектируется шахматная доска, и ничего не предвещает беды. Единственный способ избавиться от ошибки – это вычислить и удалить проблемную картинку. Я для этого пользуюсь методом «половинного деления». Допустим, у нас 50 изображений для калибровки. Я удаляю 25 картинок и смотрю, повторяется ли ошибка. Если исчезла – оставляю эти картинки, и возвращаю половину из удаленных (12 или 13). Если ошибка появляется – снова удаляю, но возвращаю вторую половину. Далее уже работа с половиной от половины – 6 или 7 картинок. Затем повторяю это уже с 3 картинками, и нахожу хулигана. Как нам удалось понять, виновата не сама картинка, а соотношение ее параметров с другими картинками в наборе. В некоторых случаях проблемными могут стать картинки с другим номером – например, если вы начнете калибровку с 1 картинки и будете добавлять другие по одной.

Баги при калибровке C++

Наличие в калибровочной серии картинки 47 вызывает такую вот ошибку:

terminate called after throwing an instance of 'cv::Exception' what(): OpenCV(4.1.1) /home/pi/opencv/modules/calib3d/src/fisheye.cpp:1421: error: (-3:Internal error) CALIB_CHECK_COND - Ill-conditioned matrix for input array 37 in function 'CalibrateExtrinsics' Aborted 

Картинка 47 была найдена методом половинного деления, как и в случае с кодом Python (а там, как вы помните, плохо себя вела картинка номер 23).

Надо заметить, что в ошибке есть подсказка – проблема в массиве точек No 37. Это большое достижение, так как в прошлых версиях OpenCV вы не получали даже такой информации.

Мы видим, что ошибку выдала обработка по флагу CALIB_CHECK_COND, который мы явно не устанавливали. Если попробовать этот флаг явно снять (закомментированная строчка //fisheyeFlags &= cv::fisheye::CALIB_CHECK_COND), то мы получаем другую ошибку:

terminate called after throwing an instance of 'cv::Exception' what(): OpenCV(4.1.1) /home/pi/opencv/modules/calib3d/src/fisheye.cpp:1023: error: (-215:Assertion failed) abs_max < threshold in function 'stereoCalibrate' Aborted 

Как и в случае с кодом на Python, причина в одном изображении, удаление которого из списка обрабатываемых решает эту проблему.

Ну что можно тут сказать – это очень недружелюбная для пользователя ситуация. Логично было бы добавить, например, игнорирование ошибочных массивов вместо прекращения работы (в качестве дополнительного флага), либо дать пользователям инструмент предварительной проверки корректности массива перед отправкой на расчет. А пока нам остается делать лишь «просев» картинок половинным делением (или править код OpenCV и пересобирать всё целиком). Надеемся, что в следующих релизах OpenCV этот момент с калибровкой будет поправлен.

Скрипт 5 – настройка карты глубин, тонкости

В нашей реализации интерфейса настройки параметров в C++ есть один «кривой» момент – параметр min_disp может быть отрицательным, а бегунок на нашем интерфейсе начинается от нуля. Поэтому мы вычитаем от значения на бегунке число 40, чтобы захватить отрицательный диапазон. Так что 0 на шкале означает -40 в параметрах.

Играясь с параметрами в C++, вы в консоли можете видеть текущий FPS (точнее, DMPS – Depth Maps Per Second). Таким образом можно легко вычислить, какие параметры и как влияют на скорость расчета карты глубин.

Скрипт 6 – карта глубин по видео

Про скорость. Запустив карту глубин, посмотрите загрузку процессора командой top или htop (вторая покажет загрузку по ядрам). Вы увидите, что загружено, как правило, примерно полтора ядра. Текущая реализация Depth Map не поддерживает многопоточность (возможность которой в коде таки заложена). Это значит, что у нас есть примерно двукратный потенциальный запас производительности, но его достижение требует серьезной правки исходников OpenCV.

Скрипт 7 – построение 2D карты пространства в режиме сканирующего лидара

Большинство трюков в этом коде связаны с отображением карты на экране. В некоторых случаях возможны «выбросы» на 2D карте в виде точек, которые находятся далеко от камеры. При этом автоматический масштаб меняется так, что карта становится очень мелкой. На этот случай мы оставили возможность отключения автоматического масштаба, и вы можете установить тот, который наиболее подходит для вашего случая. Надо отметить, что при реальном использовании на роботе эта проблема исчезает, так как вам не надо отображать карту, а нужно лишь сделать необходимые вычисления.

В зависимости от положения камеры на роботе вы можете менять настройки вырезаемой полосы изображения, перемещая ее выше, ниже или меняя высоту. А если у вас StereoPi будет связана с датчиком положения в пространстве (IMU), то возможности использования полученной карты существенно расширяются. Но тут мы уже выходим за рамки статьи (и касаемся темы ROS).

На этом пока всё. Надеюсь, наши эксперименты помогут вам в ваших проектах!

Больше полезной информации вы можете получить на наших телеграм-каналах «Библиотека питониста» и «Библиотека C/C++ разработчика».

Источник

Оцените статью