?

Log in

No account? Create an account
nyaload

Журнал Пушыстого

Журнал Пушыстого

Previous Entry Share Next Entry
Названия переменных в динамических ЯП
nyaload
_winnie
1. Квадрат длины вместо длины 3d-вектора.
Известная многим фишка оптимизации при сравнении длин векторов, вкратце расскажу про неё.
Пусть у нас есть 3d-вектора. Многие помнят как вычислять его длину - sqrt(x2+y2+z2). Часто в коде надо сравнивать длины векторов (найти самого близкого противника и тп).

Код вида Point3d direction1, direction2; direction1.length() < direction2.legnth()) вычисляет два квадратных корня, можно смело заменять его на direction1.lengthSquared () < direction2.lengthSquared ()), где lengthSquared не извлекает корень из x2+y2+z2. Что может быть раз в 10 быстрей.

2. Сюрприз при скриптинге. 100 тактов FPU против хеш-таблиц. .
В нашем движке, экспортированные из C++ для python 3d-вектора имеют методы length и lengthSquared. И в документации постулируется, что лучше вызывать lengthSquared когда надо сравнить длины векторов, типа это быстрей и корень не извлекается. Проверил это, и получил сюрприз - lengthSquared оказался на 5% медленней чем length. Объяснение простое - все поля/методы объекта в python это строки в хеш-таблице. Строка "lengthSquared" длинней чем "length". И суета с длиной идентификатора в хеш-таблице оказалась важней чем наличие квадратного корня. Вывод - в динамическом ЯП неважно что звать, вычисление "сложного" синуса или тупое сложение. Если хотим выжать сколько-то (десятков) процентов скорости не переписывая на C - стоит класть методы объектов в локальные переменные, ls = Point3d.lengthSquared; ls(dir1) < ls(dir2);

3. Сюрприз при скриптинге - 2. Глобальные переменные против локальных. .
Доступ к глобальной переменной в питоне - достаточно долгая операция. Питону нужно проверить пачку вложеных неймспейсов, что бы найти глобальную переменную, причем по строковому имени.
Доступ к локальным переменным - гораздо быстрей, без поисков по хеш-таблицам ( строка превращается в индекс при генерации байт-кода ).
У нас много всяких Enum, часто код насыщен использованием enum-типа вроде
Moving.Direction.NORTH, Moving.Direction.SOUTH, Moving.Direction.EAST, Moving.Direction.WEST.
После присваивания Moving.Direction в локальную переменную
Dir = Moving.Direction; Dir.NORTH, Dir.SOUTH, Dir.EAST, Dir.WEST
опять же выигрывал десятки процентов по производительности.
Разрушением картины миры для моего коллеги так же стало, что код при переписывании со строк на enum ( напр. MoveObject("EAST") на MoveObject(Moving.Direction.EAST)) замедлился. У него после C++ в голове не укладывалось, что использование строк может быть быстрее, чем целочисленного enum. А причина была именно в том, что Moving.Direction.EAST - это аж три поиска строк по хеш-таблицам, причем первый - в глобальном namespace с сотнями глобальных имен.
По той же причине for i in range(1000*1000): j = i*i перенесённый из глобальной области в функцию тоже может сильно ускориться (переменные i,j из глобальных станут локальными).


  • 1
Спасибо, это прекрасно :)

Наверное лучше тогда сразу сделать функцию bool isLonger( Vec3 a, Vec3 b )

А вообще все это от скриптовых языков отпугивает. Типа если надо что-то быстро мелкое сделать, то ок. А что-то объемное, или не дай бог нагруженное, ни-ни.

> Наверное лучше тогда сразу сделать функцию bool isLonger( Vec3 a, Vec3 b )
Да, точно.
А что-то объемное, или не дай бог нагруженное, ни-ни.
Тут много контрпримеров. afai, http://youtube.com и много всякого у гугля/яндекса (типа Яндекс.Афиша) написаны на питоне, а уж сколько высоконагруженного написано на PHP ( типа "вконтакте" )...

Объяснение простое - все поля/методы объекта в python это строки в хеш-таблице. Строка "lengthSquared" длинней чем "length". И суета с длиной идентификатора в хеш-таблице оказалась важней чем наличие квадратного корня.

Что как бы намекает нам на качество интерпретатора Python. *facepalm*

В интерпретаторах JavaScript с глобальными переменными подозреваю тоже беда, сложно отследить когда они резко изменились. ( Moving.Direction.EAST )

Винни, я думал при использовании питона xrange пишется вместо range уже на уровне спинного мозга...

Учитывая python3, эту привычку в спинном мозге вшивать не стоит :)
Согласен, в таких циклал xrange чуть лучше. Но не на порядок, процентов на 20%. Скорость исполнения и муки GC примерно одинаковые. В одном случае создаётся временный объект генератор, в другом временный список. Ну и частенько список можно переиспользовать, для двух for подряд.

imho, если приходится заниматься подобными оптимизациями ("низкоуровненым программирование на ..."), то скорей всего имеет место быть использование инструмента не по назначению. Первый звонок подобных проблем - враппинг математики в скрипты. А вообще, не дсльные скрипты - отстой (не считая гуя)

У каждой платформы свои глюки, конечно. Я знаю одну мобильную платформу, где выборка значения final static приводит к обращению к флеш-памяти для запоминания этой константы, каждый раз.

познавательно

ой, а я пока читал всю дорогу думал - на кой нужен питон в вопросах борьбы за производительность... считал всегда это несовместимыми вещами :)

1) хэширование от длины строки зависеть не должно, они ж интернятся

2) почему range, а не xrange?

3) обязательно читать сюда: http://wiki.python.org/moin/PythonSpeed/PerformanceTips http://www.python.org/doc/essays/list2str.html

1) хэширование от длины строки зависеть не должно, они ж интернятся
http://users.livejournal.com/_winnie/254903.html?thread=3150519#t3150519 я там график нарисовал

>2) почему range, а не xrange?
Вообще да, лучше xrange, но это не даёт разницы на порядки, и даже есть иногда ситуации когда range работает быстрее -
http://users.livejournal.com/_winnie/254903.html?thread=3143095#t3143095

3)обязательно читать сюда: http://wiki.python.org/moin/PythonSpeed/PerformanceTips http://www.python.org/doc/essays/list2str.html
Ага, спасибо. Про второе я уже когда-то читал. В первом много полезных ссылок про визуализирующие профайлеры, в конце статьи.

А есть ли какой-нибудь tool, который в финальном питон-коде укорачивает все идентификаторы до 1-2 буквенных ?
Интересно, на сколько вырастет скорость от его применения на боевом проекте? :)

Для локальных переменных это не нужно (они уже оптимизированы), для членов классов это почти невозможно (продвинутые интерпретаторы JavaScript делают это в runtime).

Для Lua я сравнил время выполнения для обычного интерпретатора (lua) и JustInTime (luajit):

(для миллиона вычислений в lua)

>lua speed2.lua 1e+6

length() ------------------ 0.77s
lengthSquared() ----------- 0.63s

enum ---------------------- 0.21s
string -------------------- 0.08s


(для ста миллионов в luajit)

>luajit speed2.lua 1e+8

length() ------------------ 2.11s
lengthSquared() ----------- 1.99s

enum ---------------------- 0.13s
string -------------------- 0.13s


Забыл код: http://barabanus.mylivepage.com/wiki/article/speed2.lua

(блин, хотел подсветить в everfall, так там цвета для луа подобрали трэшевые)

познавательно!

Кстати, я периодически использую ткинтер и меня постоянно мучает вопрос -- нафига кто-то, например, его автор, пытается использовать как бы энумы в Питоне?

Никакой устойчивости к опечаткам они не дают, вообще. Прироста производительности -- тоже, скорее, наоборот. "do_stuff('west')" смотрится гораздо эстетичнее чем "do_stuff(Dir.WEST)". Не говоря уж о том, что сама эмуляция энумов ужасно кривая. Так нахрена?

То есть для себя я вопрос как бы решил и энумами не пользуюсь, но вопрос продолжает мучать -- вдруг все эти люди -- не идиоты, некритически использующие свои жавовские привычки, вдруг это я чего-то не понимаю?

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

1) устойчивость к опечаткам чуть выше, так как исключение выскочит в месте опечатки, а не в километре от этого traceback.

2) проще использовать поиск по коду и автозамену, есть 100% гарантия что Dir.WEST - это именно направление, а не случайно совпавшая строка.
То есть даже если используем строки, то всё равно лучше использовать префиксы, 'dir_west', если даже не "mov_dir_west'.

  • 1