А чё, mmap правда тормозит?
Считается, что mmap медленнее, чем последовательное чтение.
Скорее всего не mmap тормозит, а интерпретатор, а find быстрее отрабатывает естественно.
oops
mmap прикидывается строкой. В частности в нём есть метод find, написанный на С и не очень сильно отличается от str.find. Ещё он поддерживает так называемый buffer protocol (отдавая всем желающим сырой поинтер и длину поэтому модуль re (регулярные выражения) может искать по ммапнутым файлам точно так же, как по строкам.
Я сравнивал обе штуки — find и re.matchall, на ммапнутых файлах и на целиком зачитанных в строку файлах. Ммапнутые файлы тормозят, раза в три.
Я почитал релевантные сурцы, вроде ничего странного Питон не делает, значит, остаётся только предположить, что тормозят сами ммапнутые файлы, то есть ОСь. Вот, спрашиваю — это правда?
data = str(data)
чтобы исключить различия в find?
а мелкие-то можно и бумажными прочитать.
надо будет проверить ради интересу.
Как бы всё логично. mmap — page fault, возможно, с чтением, на каждые 4 килобайта. read — один системный вызов с последовательным чтением.
мне неочевидно. этот "один системный вызов" складывает внутри себя прочтённую страницу в ядерный кеш (в процессе находя какую-нибудь свободную страницу)
mmap — page fault, возможно, с чтением, на каждые 4 килобайта.да никто не мешает делать map-ahead как и read-ahead
тормозят сами ммапнутые файлы, то есть ОСь. Вот, спрашиваю — это правда?сомневаюсь. всякие тесты, находимые гуглом, говорят, что mmap быстрее или не медленнее. думаю, всё же python что-то делает не так:
[00:21] 2:/tmp$ perf report -i read.data -n
52.16% 2745552883 python2.7 libpython2.7.so.1.0 [.] fastsearch
18.25% 960673551 python2.7 [kernel.kallsyms] [k] copy_user_generic_string
11.50% 605541183 python2.7 [kernel.kallsyms] [k] clear_page_c
2.20% 115848328 python2.7 [kernel.kallsyms] [k] page_fault
1.61% 84824906 python2.7 [kernel.kallsyms] [k] find_get_page
1.44% 75801718 python2.7 [kernel.kallsyms] [k] file_read_actor
1.44% 75739981 python2.7 [kernel.kallsyms] [k] __rmqueue
1.19% 62656527 python2.7 [kernel.kallsyms] [k] filemap_fault
1.14% 59907998 python2.7 [kernel.kallsyms] [k] unmap_vmas
[00:17] 2:/tmp$ perf report -i mmap.data -n
93.98% 9228125820 python2.7 mmap.so [.] mmap_gfind
1.31% 128500821 python2.7 [kernel.kallsyms] [k] page_fault
0.77% 75924077 python2.7 [kernel.kallsyms] [k] find_get_page
0.77% 75903300 python2.7 [kernel.kallsyms] [k] unmap_vmas
0.61% 60000085 python2.7 ld-2.11.so [.] 0x0000000000bd48
0.43% 42359267 python2.7 [kernel.kallsyms] [k] filemap_fault
0.24% 23972596 python2.7 [kernel.kallsyms] [k] page_remove_rmap
Read же делается всего файла. Это один системный вызов, который читает весь файл сразу. В дальнейшем при обращении к данным из этого файла гарантированно не будет никаких page fault-ов (если только что-то не успеет уйти в swap, это не наш случай).
В случае с mmap page fault-ов будет как минимум не меньше (меньше нуля быть не может). Да, read-ahead наверняка есть. Может быть, даже map-ahead есть. Но заранее понятно, что mmap быстрее read-а быть не может. Хотя бы потому что mmap всё равно в итоге должен сделать 1 или несколько read-ов.
Интересно. Результаты противоречат моим представлениям. Скинь ссылку на код тестов.
LC_ALL=C time echo pewpew
[00:40] 2:/tmp$ perf report -i read-faults.data -n
# Events: 487 cycles
#
# Overhead Samples Command Shared Object Symbol
# ........ .......... ......... ................. ......
#
98.15% 241068 python2.7 [kernel.kallsyms] [k] file_read_actor
1.85% 4542 python2.7 ld-2.11.so [.] 0x00000000000ba0
0.00% 2 python2.7 [kernel.kallsyms] [k] __clear_user
0.00% 1 python2.7 [kernel.kallsyms] [k] load_elf_binary
#
# (For a higher level overview, try: perf report --sort comm,dso)
#
[00:40] 2:/tmp$ perf report -i mmap-faults.data -n
# Events: 2K cycles
#
# Overhead Samples Command Shared Object Symbol
# ........ .......... ......... ................... ......
#
98.11% 240918 python2.7 mmap.so [.] mmap_gfind
1.76% 4313 python2.7 libpython2.7.so.1.0 [.] PyDict_New
0.12% 301 python2.7 ld-2.11.so [.] 0x00000000017d20
0.01% 13 python2.7 [kernel.kallsyms] [k] load_elf_binary
0.00% 2 python2.7 [kernel.kallsyms] [k] __clear_user
0.00% 1 python2.7 [kernel.kallsyms] [k] copy_user_generic_string
#
# (For a higher level overview, try: perf report --sort comm,dso)
#
:-p
Скинь ссылку на код тестов.Она же в первом посте.
Профили и про perf я ещё почитаю, как проснусь. А то и так какую-то фигню пишу. :-)
: static PyObject *
: mmap_gfind(mmap_object *self,
0.00 : 2433: 4c 89 dd mov %r11,%rbp
0.00 : 2436: 48 8d 34 3a lea (%rdx,%rdi,1%rsi
0.00 : 243a: 48 29 fd sub %rdi,%rbp
0.00 : 243d: 0f 1f 00 nopl (%rax)
: end_p = self->data + end;
:
: for (p = (reverse ? end_p - len : start_p);
: (p >= start_p) && (p + len <= end_p); p += sign) {
: Py_ssize_t i;
: for (i = 0; i < len && needle[i] == p[i]; ++i)
?:0
10.15 : 2440: 4d 85 db test %r11,%r11
0.00 : 2443: 7e 5b jle 24a0 <mmap_gfind+0x140>
?:0
0.66 : 2445: 0f b6 0a movzbl (%rdx%ecx
?:0
11.91 : 2448: 31 c0 xor %eax,%eax
?:0
0.84 : 244a: 38 0b cmp %cl%rbx)
?:0
8.57 : 244c: 74 0b je 2459 <mmap_gfind+0xf9>
8.40 : 244e: eb 17 jmp 2467 <mmap_gfind+0x107>
?:0
1.63 : 2450: 0f b6 0c 02 movzbl (%rdx,%rax,1%ecx
3.74 : 2454: 38 0c 03 cmp %cl%rbx,%rax,1)
3.60 : 2457: 75 09 jne 2462 <mmap_gfind+0x102>
2.33 : 2459: 48 83 c0 01 add $0x1,%rax
?:0
8.40 : 245d: 4c 39 d8 cmp %r11,%rax
?:0
0.75 : 2460: 7c ee jl 2450 <mmap_gfind+0xf0>
: /* nothing */;
: if (i == len) {
2.33 : 2462: 4c 39 d8 cmp %r11,%rax
0.26 : 2465: 74 40 je 24a7 <mmap_gfind+0x147>
: self->pos += num_bytes;
: return result;
: }
: fastsearch(const STRINGLIB_CHAR* s, Py_ssize_t n,
: const STRINGLIB_CHAR* p, Py_ssize_t m,
: Py_ssize_t maxcount, int mode)
: {
: unsigned long mask;
: Py_ssize_t skip, count = 0;
0.00 : 8eae9: 45 31 ed xor %r13d,%r13d
0.00 : 8eaec: eb 26 jmp 8eb14 <fastsearch+0x114>
0.00 : 8eaee: 66 90 xchg %ax,%ax
: i = i + m;
: else
: i = i + skip;
: } else {
: /* skip: check if next character is part of pattern */
: if (!STRINGLIB_BLOOM(mask, s[i+m]
?:0
7.28 : 8eaf0: 0f be 0c 37 movsbl (%rdi,%rsi,1%ecx
?:0
1.89 : 8eaf4: 4c 89 f3 mov %r14,%rbx
0.00 : 8eaf7: 83 e1 3f and $0x3f,%ecx
1.46 : 8eafa: 48 d3 eb shr %cl,%rbx
?:0
7.71 : 8eafd: 48 89 d9 mov %rbx,%rcx
?:0
4.37 : 8eb00: 83 e1 01 and $0x1,%ecx
?:0
6.55 : 8eb03: 48 0f 45 f5 cmovne %rbp,%rsi
: skip = mlast - i - 1;
: }
: /* process pattern[-1] outside the loop */
: STRINGLIB_BLOOM_ADD(mask, p[mlast]);
:
: for (i = 0; i <= w; i++) {
14.85 : 8eb07: 48 8d 6e 01 lea 0x1(%rsi%rbp
6.26 : 8eb0b: 49 39 eb cmp %rbp,%r11
8.01 : 8eb0e: 0f 8c cc 00 00 00 jl 8ebe0 <fastsearch+0x1e0>
: /* note: using mlast in the skip path slows things down on x86 */
: if (s[i+m-1] == p[m-1]) {
6.11 : 8eb14: 4a 8d 74 15 00 lea 0x0(%rbp,%r10,1%rsi
0.00 : 8eb19: 0f b6 4c 24 c0 movzbl -0x40(%rsp%ecx
0.00 : 8eb1e: 38 4c 37 ff cmp %cl,-0x1(%rdi,%rsi,1)
32.31 : 8eb22: 75 cc jne 8eaf0 <fastsearch+0xf0>
: mask |= (1UL << ch) & (STRINGLIB_BLOOM_WIDTH -1
: #define STRINGLIB_BLOOM(mask, ch) \
: mask & (1UL << ch) & (STRINGLIB_BLOOM_WIDTH -1
но этот кусок кода написан хреново и мапит страницы по одной на каждую беря пару локов.
на микротестах получается что смапить одну страницу медленне чем её скопировать,
это всё без учёта чтения, считается что всё уже в пэйдж-кэше.
Ну видишь ли, я обнаружил проблему когда использовал регекспы вообще. И оно дико тормозило! Потом начал тестить, только потом заглянул в сурцы наконец...
Короче, сейчас я ещё немножко поэкспериментировал (добавив поиск регекспом и тупо зачитывание ммапленных данных в строку таки да, ммап почти не тормозит (ну, там, может, на пять процентов медленнее, и то опять это могут быть какие-то тонкие различия в реализации чего-нибудь). То есть в моём эксперименте тормозил его файнд.
Интересно, в чём была проблема в той программе, при виде которой я собственно начал задаваться вопросом. Она там искала всякое в выкачаном svn репозитории. С накладными расходами на открытие множества мелких файликов я поборолся путём использования ммапа только для >1 мегабайта (или 100к, уже не помню — в репозитории куча тяжёлых документов лежала, но вот насколько тяжёлых... и всё равно ощутимо тормозило. Может, ммапленные файлы не выгружаются сразу из памяти, и от этого какая-нибудь фигня происходит?
Ладно, неважно, будем считать, что честное имя ммапа восстановлено!
Оставить комментарий
bleyman
Надобно тут было нечто грепообразное написать на Питоне, ну я и подумал, а не использовать ли мне mmap!И с диким удивлением обнаружил, что на множестве маленьких файликов оно вообще всё тормозит нечеловечески, но и на стомегабайтном файле в три раза медленнее чем "open(fname, 'rb').read.find('abcde')". Ну, если такой строки там нет, то есть файл пришлось целиком проглядеть.
Причём числа подозрительно похожи и на унихе, и на винде!
Причём внутри этот самый питоновский mmap написан на чистой сишке и выглядит довольно эффективным — типа, find у него тупо ищёт строку вдоль некоего пойнтера, всем остальным занимается ОС! Ну, там, у str.find олгоритм поумней, типа Боер-Мур, правда, но у меня и регекспами тоже дико тормозило (они умеют ммапом питаться!)
Так и должно быть или всё-таки некая кривизна в Питоне виновата? То есть я не могу сказать что это меня дико удивляет после определённых размышлений, но до того я почему-то полагал, что польза mmap не только в удобстве, но и в том что он всё-таки быстрее даже если мне нужно тупо в файле какую-нибудь фигню поискать.
Кот: http://python.pastebin.com/pbmwtgGX