Ответ 1
Следуя более или менее инструкциям для использования operf, я создал файл с единственной строкой sapply(1:1e5, sum)
и запустил
$ operf ~/bin/R-3-1-branch/bin/R -f sum.R
$ opreport -l ~/bin/R-3-1-branch/lib/libR.so |less
производство
CPU: Intel Sandy Bridge microarchitecture, speed 2.401e+06 MHz (estimated)
Counted CPU_CLK_UNHALTED events (Clock cycles when not halted) with a unit mask of 0x00 (No unit mask) count 100000
samples % image name symbol name
835882 93.0929 libR.so RunGenCollect
27731 3.0884 libR.so SortNodes
9323 1.0383 libR.so AgeNodeAndChildren
2038 0.2270 libR.so CheckFinalizers
1593 0.1774 libR.so Rf_allocVector3
1222 0.1361 libR.so duplicate1
...
и т.д.. Большую часть времени тратится на сборщик мусора (RunGenCollect
- запускает сборщик мусора поколения). Поэтому я побежал
$ R -d gdb R
(gdb) run
> sapply(1:1e5, sum)
^C
(gdb) break RunGenCollect
(gdb) continue
Continuing.
Breakpoint 1, RunGenCollect (size_needed=50000) at /home/mtmorgan/src/R-3-1-branch/src/main/memory.c:1504
1504 bad_sexp_type_seen = 0;
(gdb) where
который произвел
#0 RunGenCollect (size_needed=50000) at /home/mtmorgan/src/R-3-1-branch/src/main/memory.c:1504
#1 0x00007ffff789d354 in R_gc_internal (size_needed=50000) at /home/mtmorgan/src/R-3-1-branch/src/main/memory.c:2825
#2 0x00007ffff789e99b in Rf_allocVector3 (type=13, length=100000, allocator=0x0) at /home/mtmorgan/src/R-3-1-branch/src/main/memory.c:2563
#3 0x00007ffff788e1a5 in Rf_allocVector (type=13, length=100000) at /home/mtmorgan/src/R-3-1-branch/src/include/Rinlinedfuns.h:189
#4 0x00007ffff7831787 in duplicate1 (s=0x7ffff3b0b010, deep=TRUE) at /home/mtmorgan/src/R-3-1-branch/src/main/duplicate.c:335
#5 0x00007ffff783371a in duplicate_child (s=0x7ffff3b0b010, deep=TRUE) at /home/mtmorgan/src/R-3-1-branch/src/main/duplicate.c:199
#6 0x00007ffff783357a in duplicate_list (s=0x2c98b30, deep=TRUE) at /home/mtmorgan/src/R-3-1-branch/src/main/duplicate.c:261
#7 0x00007ffff7830fc2 in duplicate1 (s=0x2c98b30, deep=TRUE) at /home/mtmorgan/src/R-3-1-branch/src/main/duplicate.c:308
#8 0x00007ffff783371a in duplicate_child (s=0x2c98b30, deep=TRUE) at /home/mtmorgan/src/R-3-1-branch/src/main/duplicate.c:199
#9 0x00007ffff783357a in duplicate_list (s=0x2c98a88, deep=TRUE) at /home/mtmorgan/src/R-3-1-branch/src/main/duplicate.c:261
#10 0x00007ffff7830fc2 in duplicate1 (s=0x2c98a88, deep=TRUE) at /home/mtmorgan/src/R-3-1-branch/src/main/duplicate.c:308
#11 0x00007ffff7830c7f in Rf_duplicate (s=0x2c98a88) at /home/mtmorgan/src/R-3-1-branch/src/main/duplicate.c:132
#12 0x00007ffff79257f4 in do_summary (call=0x2c98a88, op=0x6259a0, args=0x303cf88, env=0x2c97f48) at /home/mtmorgan/src/R-3-1-branch/src/main/summary.c:462
...
а соответствующая строка - строка 462
(gdb) up 12
#12 0x00007ffff79257f4 in do_summary (call=0x2c98a88, op=0x6259a0, args=0x303cf88, env=0x2c97f48) at /home/mtmorgan/src/R-3-1-branch/src/main/summary.c:462
462 PROTECT(call2 = duplicate(call));
(gdb) list
457 return ans;
458 }
459
460 /* match to foo(..., na.rm=FALSE) */
461 PROTECT(args = fixup_NaRm(args));
462 PROTECT(call2 = duplicate(call));
463 SETCDR(call2, args);
464
465 if (DispatchGroup("Summary", call2, op, args, env, &ans)) {
466 UNPROTECT(2);
Вызов дублируется
(gdb) call Rf_PrintValue(call)
FUN(1:100000[[5339L]], ...)
для каждой итерации цикла, вызывая сбор мусора. Аналогичный код не выполняется для cumsum. Это было в течение длительного времени и по причинам, которые не являются на 100% очевидными.
$ svn annotate ~/src/R-3-1-branch/src/main/summary.c |less
...
42643 ripley /* match to foo(..., na.rm=FALSE) */
42643 ripley PROTECT(args = fixup_NaRm(args));
42643 ripley PROTECT(call2 = duplicate(call));
42643 ripley SETCDR(call2, args)
...
$ svn log -r42643
------------------------------------------------------------------------
r42643 | ripley | 2007-08-25 23:09:50 -0700 (Sat, 25 Aug 2007) | 1 line
make the rest of the group generics primitive
------------------------------------------------------------------------
Было бы интересно рассмотреть это в списке рассылки R-devel. Дело не в том, что sum
особенно медленное, а скорее, что вызовы сборщика мусора достигают времени выполнения.
Hmm, при отражении оказывается, что
sapply(1:1e5, function(x) sum(x))
работает в том же самом шаре, что и cumsum
. Я думаю, потому что duplicate
в строке 462 в исходной версии делает копию элементов 1e5 при подготовке к выбору i-го элемента для суммирования. Напротив, в function(x) sum(x)
вектор уже был подмножеством, поэтому дублирование - только i-го элемента. Дублирование исходного вектора также объясняет, почему элементы 1e5 намного медленнее, чем 1e4, и почему as.list(1:1e5)
относительно совершенен (только элемент списка фактически дублируется, а может быть, и не тот). Дублирование во время вызова sum
имеет отношение к тому факту, что оно относится к общей группе (S3) Summary
, см. ?"group generic"
.