Comments 13
Картинка 8192×8192 RGB32 занимает в памяти 256 MiB. Копирование области памяти такого размера занимает около 10 мс. Насколько же неэффективно сделаны многие вещи...
Не очень понятно, как вы посчитали. У Малинки скорость памяти примерно 4 Гб/с (полагаю, в обе стороны одновременно), но это если задействовать все ядра. У одного ядра где-то 2300 Мб/c. Так что получается 256/2300 = 111 мс.
Но самое большое время тратится не на само копирование, а на выделение операционной системой. Тогда скорость падает до 630 Мб/c.
Да собственно, что я рассказываю, вот тест, который я сделал для статьи, но не придумал куда его воткнуть:
In [3]: for i in range(20):
...: b = b'0123456789abcdef' * 64 * 2 ** i
...: print('len:', len(b) // 1024, 'kb')
...: %timeit n = bytearray(b)
...: b = None
...:
len: 1 kb
693 ns ± 0.667 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
len: 2 kb
992 ns ± 8.91 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
len: 4 kb
1.17 µs ± 12.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
len: 8 kb
1.55 µs ± 12.8 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
len: 16 kb
2.15 µs ± 13.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
len: 32 kb
4.16 µs ± 34.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
len: 64 kb
8.08 µs ± 153 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
len: 128 kb
18.7 µs ± 2.52 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
len: 256 kb
51.7 µs ± 5.58 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
len: 512 kb
154 µs ± 4.4 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
len: 1024 kb
408 µs ± 1.97 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
len: 2048 kb
851 µs ± 4.29 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
len: 4096 kb
1.71 ms ± 1.78 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
len: 8192 kb
3.55 ms ± 10.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
len: 16384 kb
7.14 ms ± 21.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
len: 32768 kb
52.1 ms ± 119 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
len: 65536 kb
102 ms ± 147 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
len: 131072 kb
202 ms ± 215 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
len: 262144 kb
372 ms ± 1.11 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
len: 524288 kb
751 ms ± 3.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Примерно до 64 Кб скорость растет, т.к. падают накладные расходы, а потом кончается кэш и скорость падает до 2330-2440 Мб/c — скорости копирования памяти для одного ядра. А при 32 Мб Питон (а точнее glibc) начинает каждый раз ходить к системе за новой памятью.
Да я просто вот эту фразу почему-то проглядел:
Для разнообразия сегодня я буду запускать бенчмарки на Raspberry Pi 4 1800 MHz под 64-разрядной Raspberry Pi OS.
Что я не понимаю?
Что Pillow, что NumPy и OpenCV внутри написаны на Си и замедление не из-за того, что Пайтон медленный, а из-за того, что интерфейс между библиотеками делает лишнее. Конкретно в этом месте удалось избежать переход на уровень ниже.
самый медленный зык какой есть, но это не страшно потому, что всегда можно написать модуль и он сделает медленное быстрым.
Написать для Пайтона что-то на Си это не то же самое, что написать что-то для HTML на Джаваскрипте, и даже не то же самое, что написать для Си на Ассемблере. Это сложно и долго.
В то, что писать на С модуль сложно и долго, усомнился. Было бы так, их бы столько не было. Вечером проверю.
Под тем, что «переписывать часть на C — последнее дело» предполагаю автор имел ввиду переписать внутри Pillow использование формата хранения данных на NumPy массивы.
Почему вы тогда используете Pillow, а не OpenCV?
Pillow-SIMD быстрее и точнее на типичных операциях работы с графикой (ресасайз, блюр, трансформации цвета). Умеет больше форматов, дает доступ к exif и icc профилю.
OpenCV — это библиотека компьютерного зрения. У нее другие первостепенные задачи.
Подробнее вы можете почитать или посмотреть в докладе «Работа с изображениями на Python».
https://habr.com/p/425471/
Александр, классно, что вы разработчик Pillow-SIMD. Вы будете добавлять описанную в статье функцию в библиотеку? И может быть действительно сможете переписать ее на C, чтобы еще чуточку быстрее все работало?
В Pillow-SIMD никогда не будет ничего добавляться, что меняет API или поведение.
Вчера попробовал сделать в Pillow, чтобы tobytes()
возвращал не байты, а bytearray
, оказалось что это ломает все места, где bytearray
потом попадает на уровень Си. В самой библиотеке таких мест несколько, а уж что будет со сторонним софтом. Дальше можно смотреть разве что в сторону ускорения только __array_interface__
без ускорения tobytes()
.
Перегон картинок из Pillow в NumPy/OpenCV всего за два копирования памяти