Comments 26
При прочтении статьи закралась мысль, а не может ли оказаться так, что вместо map+RWMutex лучше себя проявит map+Mutex в ситуации cache contention или близкой к ней?
You're welcome!
При прочтении статьи закралась мысль, а не может ли оказаться так, что вместо map+RWMutex лучше себя проявит map+Mutex в ситуации cache contention или близкой к ней?
Ну, для этого (*sync.Mutex).Lock() + чтение из map + (*sync.Mutex).Unlock()
должны быть быстрее, чем трансфер значения из L2 кеша между ядрами CPU на данной архитектуре. Несложно замерить, впрочем.
Простите, а можно ли ссылку на бенчмарк, с которого взят график скорости для python? Не думал что он настолько медленный, интересно посмотреть, почему так.
А, кажется это можно найти тут. Итого, мы сравниваем (если я правильно понял): "Java openjdk 1.8.0 java.util.HashMap, c++11 gcc 4.8.4 unordered_map, -O2. Python 2.7.6".
Что ж такую новую версию питона взяли, а не какой-то python 2.3? Уверен, результаты были бы еще более шокирующими.
Если верить вот этим бенчмаркам, основанным на python/performance, то зависит от того, что использовать.
Тот же pickle_dict проходит быстрее, следовательно, вполне возможно, что сам dict в python3.6 в целом работает быстрее, чем в python 2.7.6.
И даже если забить на это, почему нельзя было использовать последнюю версию python 2.7?
И даже если забить на это, почему нельзя было использовать последнюю версию python 2.7?Потому что бенчмарки эти делались, в первую очередь, для того, чтобы принять решения. И была использована последняя доступная версия python2. То что она оказалась несколько… старше, чем фанатам бы хотелось — ну что ж тут поделать. Зато это то, что реально используется в проде.
По этой же принчине использовался gcc 4.8.4, а не gcc 7, OpenJDK 8 и так далее.
Переписывать миллионы строк на python 3 ради копеечного ускорения никто не будет. Вот если бы python3 был сравним с go или java — тогда другой вопрос, а так — разница вместо стократной станет, если повезёт, десятикратной. И то вряд ли. Если уж переписывать, то стоит взять другой язык и получить-таки стократное ускорение…
Ну так не переписывайте. Вот я взял и провел бенчмарк из этого комментария для 2.7.13. Получил аналогичные результаты стандартным %timeit, то есть 35-40 ns.
Страшные циферки из графика у меня получилось получить только низким количеством прогонов, а следовательно, реальное отставание от лидеров у python всего в 2-3 раза, что не так плохо.
Ну и да, это какое-то странное лукавство, сравнивать "реальные версии, которые есть в дистрибутиве" и новый релиз go.
Если же на python 2.7.6 в самом деле все так плохо, то уж обновить пакет ради прироста производительности то можно, я думаю.
Тоже заинтересовали цифры. Проверил:
$ ipython
Python 3.5.3 (default, Apr 22 2017, 00:00:00)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.1.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: m = dict(enumerate('abcdefghij'))
In [2]: %timeit m[4]
38.1 ns ± 0.247 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
In [4]: m = dict((v, v) for v in 'abcdefghij')
In [5]: %timeit m['c']
35.6 ns ± 0.346 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
Интересно, в каки условиях могло получиться 150 ns.
А потому что нужно делать вот так:
Python 3.6.2 (default, Jul 20 2017, 03:52:27)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.1.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: m = dict(enumerate('abcdefghij'))
...:
In [2]: %timeit -n 15 m[4]
265 ns ± 43.7 ns per loop (mean ± std. dev. of 7 runs, 15 loops each)
Можно попрогонять так раз 20 и получить совершенно фантатические результаты, от 36.3 до 265.
In [1]: m = dict(enumerate('abcdefghij'))
In [2]:
%%timeit x = 0
x = x + 7
if x > 9: x = 0
m[x]
10000000 loops, best of 3: 58.7 ns per loop
А можно и так
In [3]: import random
In [4]:
%%timeit
m[random.randint(0, 9)]
1000000 loops, best of 3: 708 ns per loop
Сами по себе хеш-таблицы в Python очень быстрые, что вполне логично для языка, где любой объект по сути хеш таблица. Думюа в Python они побыстрее, нежели в Go.
Но вот обвязочка медленная. Скажем банальный вызов random.randint добавляет еще одно чтение из хеш-таблицы random и т.п. Поэтому все подобные «бенчмарки» без исходников не стоят и выеденного яйца.
Можно попрогонять так раз 20 и получить совершенно фантатические результаты, от 36.3 до 265.
Я получаю либо 0, либо 63ns. Если посмотреть time.get_clock_info('clock')
, будет видно, что разрешение таймера 1e-06 секунд, то есть 1000 наносекунд. Проверяем:
In [35]: (time.clock() - time.clock()) * 1e9
Out[35]: -1000.000000139778
1000ns / 15 раз = 66.6ns. Так что замерять время выполнения таких быстрых операций отдельно или даже по 15 штук нет никакого смысла — время выполнения меньше разрешения таймера.
В текущей реализации map очень быстрНо медленнее Java примерно на 15-40% (на глаз по графику)? Было инетересно взглянуть на бенчмарки.
runtime: working with small maps is 4x-10x slower than in nodejs.
Хотя выигрыш PyPy в этом бенчмарке выглядит забавно :)
Ситуация для Java иная — хеш таблицы не имеют поддержки на уровне языка и реализованы как часть стандартной библиотеки. По идее реализация на уровне языка (go) должна быть побыстрее… Поэтому и интересно было бы взглянуть на бенчмарки.
Интересно, а есть ли возможность прикрепить пул горутин к одному потоку ОС?
В этом случае синхронизация внутри такого пула не нужна по построению.
Да, если вы про график вначале статьи, то там учтены (он из статьи по ссылкам в разделе Ссылки).
Оверхед от конвертации в и из интерфейсом можно померять бенчмарками из стандартной библиотеки:
go test -test.run none -bench Conv runtime
У меня вот такой результат выдает (T — type, I — interface, E — empty interface):
$ go test -test.run none -bench Conv runtime
goos: darwin
goarch: amd64
pkg: runtime
BenchmarkConvT2ESmall-4 500000000 3.50 ns/op
BenchmarkConvT2EUintptr-4 500000000 3.49 ns/op
BenchmarkConvT2ELarge-4 50000000 26.3 ns/op
BenchmarkConvT2ISmall-4 500000000 3.44 ns/op
BenchmarkConvT2IUintptr-4 500000000 3.43 ns/op
BenchmarkConvT2ILarge-4 50000000 25.5 ns/op
BenchmarkConvI2E-4 2000000000 1.12 ns/op
BenchmarkConvI2I-4 200000000 9.91 ns/op
BenchmarkConvT2Ezero/zero/16-4 500000000 3.53 ns/op
BenchmarkConvT2Ezero/zero/32-4 500000000 3.53 ns/op
BenchmarkConvT2Ezero/zero/64-4 500000000 3.42 ns/op
BenchmarkConvT2Ezero/zero/str-4 500000000 3.35 ns/op
BenchmarkConvT2Ezero/zero/slice-4 500000000 3.33 ns/op
BenchmarkConvT2Ezero/zero/big-4 20000000 99.7 ns/op
BenchmarkConvT2Ezero/nonzero/16-4 100000000 14.2 ns/op
BenchmarkConvT2Ezero/nonzero/32-4 100000000 15.0 ns/op
BenchmarkConvT2Ezero/nonzero/64-4 100000000 17.6 ns/op
BenchmarkConvT2Ezero/nonzero/str-4 50000000 30.4 ns/op
BenchmarkConvT2Ezero/nonzero/slice-4 50000000 36.3 ns/op
BenchmarkConvT2Ezero/nonzero/big-4 20000000 93.4 ns/op
PASS
ok runtime 38.947s
Разбираемся с новым sync.Map в Go 1.9