?збавляемся от ненужных циклов и ускоряем скрипт на Python
Задача: выбрать из массива данных только данные удовлетворяющие условию и сделать с ними какую ни-будь гадость
Решение: numpy.where
Слухи о тормознутости Python сильно преувеличены, просто нужно уметь его готовить. Конечно скорости C или Fortran вы на нем не добьетесь, но и его вполне можно заставить быстро обрабатывать огромные массивы информации. Если вы хотите считать быстро, то ваш враг номер один в Python (также как и в MATLAB, кстати) это циклы, заданные в явном виде (оператор for). От большинства из них можно избавиться, применяя нехитрые приемы. Об одном таком приеме, позволившем увеличить скорость обработки массива размером более 100 гигабайт в 46 раз, я очень коротко расскажу в этом посте.
Дано: четерехмерный массив с полями температуры (x,y,глубина, время). Поскольку сетка у нас не регулярная, имеются два двумерных массива широт (lat) и долгот (lon).
Задача: вырезать из этого массива хитрую область, и получить среднее по этой области для каждой глубины, за каждый отсчет времени.
Область выглядит следующим образом:
Одним из возможных решений данной задачи является такой код:
for lev in range(0,51):
THO_new = []
for ff in range(285):
for zz in range(3602):
if 100 < lon[ff,zz] < 140:
if lat[ff,zz] > 66:
THO_new.append(ffile.variables["tho"][ttime,lev,ff,zz])
elif 100 > lon[ff,zz] > 0:
if lat[ff,zz] > 80:
THO_new.append(ffile.variables["tho"][ttime,lev,ff,zz])
elif 300 < lon[ff,zz] < 360:
if lat[ff,zz] > 80:
THO_new.append(ffile.variables["tho"][ttime,lev,ff,zz])
У нас имеется внешний цикл по времени (ttime), внутри него цикл по глубинам (lev), затем цикл по x (ff) и по y (zz). Вот в этом последнем цикле мы и проверяем каждую точку на соответствие нашим условиям по широте и долготе и если она им удовлетворяет, добавляем ее значение в массив (THO_new), с которым будем дальше совершать все необходимые непотребства.
Количество точек в одном шаге по времени, которые небходимо прогнать через циклы и обработать условными операторами составляет в данном случае 52355070 штук, что занимает 2 минуты 18 секунд. Шагов по времени у нас 487, так что ждать окончания работы программы мы будем дооолго.
Способ второй:
i2,j2 = numpy.where((100>lon)&(lon>0)&(lat>80))
i3,j3 = numpy.where((300<lon)&(lon<360)&(lat>80))
for ttime in range(0,487):
for lev in range(0,51):
THO_new = []
a = ffile.variables["tho"][ttime,lev,:,:]
THO_new = numpy.concatenate((THO_new, a[i1,j1]), 0)
THO_new = numpy.concatenate((THO_new, a[i2,j2]), 0)
THO_new = numpy.concatenate((THO_new, a[i3,j3]), 0)
Понятное дело, что точки с необходимыми нам координатами будут занимать одинаковое положение во всех полях. Поэтому проще сразу определиться с индексками тех точек что нас интересуют. Для этого используем оператор numpy.where, которому в качестве условия передаем наши пожелания по ограничению региона. К сожалению, конструкции вида 100 < lon < 140 он не воспринимает, так что небоходимо пользоваться логическими операторами (естественно можно использовать не только AND (&) но и, напримерр OR (|)). В качестве вывода, мы получаем индексы тех точек, что удовлетворяют нашим условиям.
Циклы по времени и глубинам мы оставляем, а вот перебирать каждую точку двумерного поля нам уже не нужно, мы точно знаем какие точки нам нужны. Сначала создаем переменную a, в которую заливаем двумерное поле (это сделано исключительно из-за того что 100 гигабайт в память не влезало :)). Затем добавляем в переменную THO_new те значения, что вписываются в заданную нами область. Дальше уже происходит наложение маски (пропущенных значений) и осреднение THO_new, что для нас в данном контексте не особо важно, поэтому эту часть я не привожу. Время работы нового кода для одного шага по времени составляет 3 секунды.
Спасибо Марку Карсону (Mark Carson) за идею.
При больших размерах ttime, lev, дополнительного увеличения производительности можно добиться заранее вычислив размерность THO_new, и заполняя его используя срезы. Функция numpy.concatenate, достаточно тяжелая, т.к. копирует исходные массивы.
? если я правильно путаю, то лучше будет так:
THO_new = numpy.concatenate((THO_new, a[i1,j1], a[i2,j2], a[i3,j3]), 0)
— тоже должно увеличит производительность, т.к. основной массив копируется только один раз за цикл.
PS numpy — отличная, очень мощная штука, не только увеличивает производительность, но и упрощает написание кода, взять хотябы a[a>0].
2comm
Спасибо за дельные советы, логично что numpy.concatenate лучше делать один раз, ну и заполнять заранее созданный массив быстрее! Но я был так счастлив трем секундам вместо двух минут, что о дальнейшей оптимизации как-то даже не подумал 🙂
А numpy да, штука отличная, без него как без рук 🙂
я вот тут тоже в растрах вместо цикла поставил
temp1_bool = numpy.equal(raster,5)
numpy.putmask(raster,temp1_bool,10)
и порадовался
http://gis-lab.info/blog/2010-07/array-numpy/
Ситуация с питоном примерно такая же, как и с МАТЛАБом — его тормоза тоже никому не нравятся, но альтернативы ему не видно. Октависты только недавно дотумкали (с версией 3), что вся из себя альтернативная система численных расчётов никому не упёрлась, и без 100% совместимости с МАТЛАБом октависты будут вечно курить шишки за углом со своей пионерской поделкой, которая даже меши сама строить не умеет.
К теме. Циклы for в матлабе не настолько тормозные, хотя векторизация (это вот то самое a[a>0]) существенно ускоряет вычисления. Здесь не надо перебарщивать: отдельные поциенты выкобениваются так, что их вся такая векторизованая в доску функция попросту нечитаема. Прекрасным примером такого идиотизма, доведённого до полного маразма, является функция zernfun, которая расчитывает полиномы Цернике. Автор этих строк, изрыгая проклятия на всех языках, потратил полдня на написание своей собственной функции, с блэкджеком и циклами 🙂
Кстати, мне стало интересно, насколько велик проигрыш for в MATLAB по сравнению с векторизацией.
2Максим Дубинин
Отличный пример, спасибо!
2virens
Ну отличие ситуации с питоном от ситуации с MATLABом в том что для него не нужно создавать пионерских заменителей, он и так свободен и открыт 🙂 Хотя до сих пор количество библиотек и готовых скриптов для научных вычислений не сравнимо, к сожалению. Поэтому иногда легче посчитать матлабом, чем два дня писать функцию на питоне, в конечном счете важен результат а не инструмент.
Соглашусь с тобой в том что код написанный с векторизацией менее читаем чем знакомые всем с детства циклы 🙂 Я вообще за то, что если функция после улучшений будет считать на пять минут быстрее, но при этом станет нечитаемой, то нафиг такие улучшения.
Про проигрыш не знаю, возможно это хорошая тема для твоего следующего поста 🙂
Офтоп: Я слышал на вас там страшный тропический циклон надвигается, тебя заденет?
2virens
миф пор тормознутость питона — глупость.
Все дело в интерпритаторе, но среди подобных, производительность CPython не уступает, а правильное использование продвинутых конструкции (на подобии генераторов), позволяет добиться дополнительго выигрыша в скорости не калеча симантику языка!
Существуют такие же «мифы» про «тормознутый» PHP, Perl, Ruby, Matlab и тд. достаточно спросить у гугла.
В общем существует выбор либо быстро писать, либо быстро получить результат выполнения программы, так что все зависит от задачи.
Ситуация меняется с приходом JIT, но даже с ним итерпитатор проигрывает копилятору.
про векторизацию
Действительно использование numpy по сути изменяет стиль написания кода в сравнении с pure python. Но это лишь подтверждает мошь языка)
Читабельность при этом может страдать, по двум причинам:
1) ?спользование новых функций (поэтому первый пример кода более понятен человеку незнакомому с numpy, и python), это нормально учитывая, что numpy негласный стандарт для определенного класса задач.
2) Зависимость смысла кода (то что он делает) от контекста выполнения:
если в тексте на pure python встретится
с = a * b
то сразу понятно, что a и b — числа, а с — результат их перемножения;
если же данный кусок кода встречается в функции использующей numpy, то смысл данных строк будет зависеть от значений a и b, это может быть:
вектор * число
вектор * вектор
массив * число
массив * вектор
массив * массив
матрица * число
…
из за чего приходится отслеживать всю цепочку возникновения переменных. Но это уже естественная плата за гибкость.
А мне уже давно интересует вопрос про скорость работы Python vs. Matlab. Такие тесты, случайно, не попадались?
@Jenyay
>> скорость работы Python vs. Matlab. Такие тесты, случайно, не попадались?
Нет, зато попадались тесты про Октаву и Матлаб: http://mydebianblog.blogspot.com/2010/10/matlab-gnuoctave.html
@comm on февраля 3, 2011
2virens
>> миф пор тормознутость питона – глупость. Все дело в интерпритаторе…
Для меня, как для конечного пользователя, это неважно. Важен факт: при мне один заядлый питонист показал, как лихо и круто считать матрицы на питоне. Я заказал перемножение двух матриц 4000х4000 элементов с двойной точностью. Тут мой питонист крепко загрустил, да. 🙂
@koldunovn on февраля 3, 2011
2virens
>> Ну отличие ситуации с питоном от ситуации с MATLABом в том что для него не нужно создавать пионерских заменителей
В этом вопросе я не дока, так что просьба вежливо поправить, если что. Насколько мне известно, с питоном для научных вычислений другая беда: приходится создавать пионерские тулбоксы. Скажем, мне сегодня надо быстренько посчитать sparce matrices. В матлабе оно из коробки, а в питоне?
Кроме того, матлаб выигрывает за счёт legacy-кода. На нём написано так много софтвария, что использовать что-то кроме него уже не хочется.
>> Хотя до сих пор количество библиотек и готовых скриптов для научных вычислений не сравнимо, к сожалению.
Вот это-то и огорчает.
>> в конечном счете важен результат а не инструмент.
Золотые твои слова, Николай.
>> Соглашусь с тобой в том что код написанный с векторизацией менее читаем чем знакомые всем с детства циклы 🙂
Может быть, я несколько эмоционально это выразил, но когда «типа серьёзные дяди» начинают откровенный выпендрёж и трюкачество в коде, я теряю самоконтроль и хватаюсь за маузер 🙂
>> Про проигрыш не знаю, возможно это хорошая тема для твоего следующего поста 🙂
Я вообще хочу собрать все свои бенчмарки и выдать их на небольшой OSA Topical Meeting в Канаде. Шеф канючит и говорит, что это не есть Большая Наука. Это в общем так, но когда ты лезешь в Гугл за бенчмарком, а находишь кукиш, оно не радует.
>> Офтоп: Я слышал на вас там страшный тропический циклон надвигается, тебя заденет?
К счастью, нет: я живу на 2-3 тыс.км южнее, в Ньюкасле, Новый Южный Уэльс. Зато наших северных соседей, штат Квинсланд, здорово побило. Квинсландцам вообще невезёт в этом году: сначала их протопило по самые уши (Брисбен, миллионный город, был залит на 3-5 метров), а теперь побило ветром (Карнс закрыл международный аэропорт). У нас сейчас heat wave — жара 40 и влажность 90%.
Вот не получается numpy’ем ускорить обработку файла, в котором информация о дате и времени на разных строках:
00000.00 09.09.06 016
10487.39 16:40:00 001
10487.32 16:42:00 001
10487.31 16:44:00 001
10487.23 16:46:00 001
…
где 090906 — дата, а в следующих строках до 0 в первом столбце — время.
Добавляю значения даты/времени в list append’ом — выходит быстрее чем с векторами. awkом можно подготовить файл — но нужно чтобы работало на любой платформе.
2virens
>>> http://mydebianblog.blogspot.com/2010/10/matlab-gnuoctave.html
то, что Octave ~2 раза быстрее MATLAB за счет проигрыша в потреблении памяти, абсолютно ничего не значит. Это классическая оптимизация память vs процессор. Для простых операций с матрицами они используют теже библиотеки (в том числе и numpy).
>>>В матлабе оно из коробки, а в питоне?
есть
>>>Я заказал перемножение двух матриц 4000х4000
а надо было поискать
Зачем устраивать холивар? Вы выбрали MATLAB — удачи, это не значит что остальные должны поступить также.
У матлаба есть его отличные тулбоксы. У питона отличный динамический язык, и пакеты общего назначения и open source. А по скорости она оба проиграют C, Fortran и не в два раза, а в 10-100
@virens
Для меня, как для конечного пользователя, это неважно. Важен факт: при мне один заядлый питонист показал, как лихо и круто считать матрицы на питоне. Я заказал перемножение двух матриц 4000х4000 элементов с двойной точностью. Тут мой питонист крепко загрустил, да. 🙂
я попробовал заполнить единицами и перемножить две матрицы 6000×6000, двойной точности, в матлабе под виндой, и в питоне на линуксе (тот линукс в виртуалке под той виндой). и там, и там заполнение и перемножение заняло несколько секунд.
непонятно, отчего грустил тот питонист.
Тормознутость питона иногда можно исправить. Банальную математику на 32-битных архитектурах очень хорошо (в добрых 100 раз) ускоряет такая штука, как psyco.
А код из первого примера, сдаётся мне, можно нехило ускорить даже и без numpy — например, созданием сначала списка индексов без лишних проходов по ненужным данным, и использованием xrange вместо range.
Like!! Really appreciate you sharing this blog post.Really thank you! Keep writing.
Acyclovir Pills For Sale In U.S. Cialis Buy Doxycycline In The Us Cialis Best Brand Viagra
Canadian Medicines Online best place to buy cialis online reviews Cheapest 4 Quantity Of Viagra Cialis Order Online Stendra
Confezioni Viagra 100 Mg Cialis Kamagra Recensione Online Cialis Where Can I Buy Acticin