Pull to refresh

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.

Так удивился, что зарегистрировался. Только заинтересовался Python, везде пишут: самый медленный зык какой есть, но это не страшно потому, что всегда можно написать модуль и он сделает медленное быстрым. Поэтому ожидал подход: вот медленное, вот я изучил почему медленное, кто теперь будет писать модуль? А вместо этого — « переписывать часть на C — последнее дело». И предлагается радоваться ускорению в 2.5 раза при характерном согласно учебника замедлении за счёт использования Python в 20 раз.

Что я не понимаю?

Что Pillow, что NumPy и OpenCV внутри написаны на Си и замедление не из-за того, что Пайтон медленный, а из-за того, что интерфейс между библиотеками делает лишнее. Конкретно в этом месте удалось избежать переход на уровень ниже.


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

Написать для Пайтона что-то на Си это не то же самое, что написать что-то для HTML на Джаваскрипте, и даже не то же самое, что написать для Си на Ассемблере. Это сложно и долго.

Понял так: как раз тот случай, когда замена Python на С не даст ничего. И, кажется, понял почему. Спасибо.

В то, что писать на С модуль сложно и долго, усомнился. Было бы так, их бы столько не было. Вечером проверю.
Python и C это разные инструменты, и решают ими разного класса задачи. Кроме того, если у вас в коде на C будут использоваться две библиотеки с принципиально разными форматом данных, то конвертация из одного формата в другой аналогично будет занимать существенное время. Т.е. причина здесь в «архитектуре» приложения, а не в языке, и если можно, то таких конвертаций лучше избегать.

Под тем, что «переписывать часть на C — последнее дело» предполагаю автор имел ввиду переписать внутри Pillow использование формата хранения данных на NumPy массивы.
Спасибо за статью, как раз на прошлой неделе удивлялся, почему если читать Pillow, а потом конвертировать в NumPy arrays, это занимает так много времени. Я то думал, что Pillow тоже внутри себя использует np.

Почему вы тогда используете Pillow, а не OpenCV?

Pillow-SIMD быстрее и точнее на типичных операциях работы с графикой (ресасайз, блюр, трансформации цвета). Умеет больше форматов, дает доступ к exif и icc профилю.


OpenCV — это библиотека компьютерного зрения. У нее другие первостепенные задачи.


Подробнее вы можете почитать или посмотреть в докладе «Работа с изображениями на Python».
https://habr.com/p/425471/

Спасибо за ссылку на статью и пояснение, раньше плотно с Pillow не работал. Мне нужно только открывать картинки из проверенных источников и преобразовывать их к RGB (если нужно). Дальше обработка идет уже в Numpy.

Александр, классно, что вы разработчик Pillow-SIMD. Вы будете добавлять описанную в статье функцию в библиотеку? И может быть действительно сможете переписать ее на C, чтобы еще чуточку быстрее все работало?

В Pillow-SIMD никогда не будет ничего добавляться, что меняет API или поведение.


Вчера попробовал сделать в Pillow, чтобы tobytes() возвращал не байты, а bytearray, оказалось что это ломает все места, где bytearray потом попадает на уровень Си. В самой библиотеке таких мест несколько, а уж что будет со сторонним софтом. Дальше можно смотреть разве что в сторону ускорения только __array_interface__ без ускорения tobytes().

Понятно. Может быть тогда сможете добавить метод to_numpy() в основной Pillow?
Only those users with full accounts are able to leave comments. Log in, please.