Сравнивая эффективность двух функций в ответе Проверить, содержит ли список другой список в R, я наткнулся на интересный результат. Сортировка значительно увеличивает эффективность duplicated
, когда вектор большой. Это стало неожиданностью, поскольку я никогда не замечал значительных различий в своей работе, используя duplicated
. Действительно, для размеров, с которыми я работаю каждый день, нет никакой разницы. Обратите внимание:
set.seed(1007)
s1 <- sample(10^2, 10^3, replace = TRUE)
s1_sort <- sort(s1)
library(microbenchmark)
microbenchmark(dp=duplicated(s1), dp_sort=duplicated(s1_sort), times=1000)
Unit: microseconds
expr min lq mean median uq max neval cld
dp 16.459 16.9425 22.06371 17.2965 22.5050 1541.137 1000 a
dp_sort 17.007 17.5005 25.54953 17.8200 23.3655 1549.198 1000 a
Как вы можете видеть, нет заметной разницы в таймингах при сортировке вектора. Однако на очень больших векторах результаты сильно различаются. Обратите внимание:
s2 <- sample(10^6, 10^7, replace = TRUE)
s2_sort <- sort(s2)
microbenchmark(dp=duplicated(s2), dp_sort=duplicated(s2_sort), times=100)
Unit: milliseconds
expr min lq mean median uq max neval cld
dp 816.6883 847.9231 869.6829 861.8210 882.3978 1019.6339 100 b
dp_sort 287.6779 305.4779 322.8830 315.1198 324.9249 449.1734 100 a
Почти в 3 раза быстрее!!! Это привело меня к кроличьей дыре, которая началась здесь: r-source.../duplicated.R. Отсюда мы видим, что дубликат вызывает вызов .Internal(duplicated(x,...))
. Затем с помощью функции pryr::show_c_source(.Internal(duplicated(x)))
и обходной путь, предложенный @joran (show_c_source
, в настоящее время дает проблемы.. см. Is 'show_c_source()' borken?), мы видим, что duplicated
делает вызов do_duplicated. Наконец, показано сердце duplicated
(оно начинается на линии 667 и заканчивается на 988). Похоже, что весь вектор зацикливается, а затем происходит некоторое хеширование:
724 /* count unique entries */
725 k = 0;
726 for (i = 0; i < n; i++)
727 if (LOGICAL(dup)[i] == 0)
728 k++;
776 /* Build a hash table, ignoring information on duplication */
777 static void DoHashing(SEXP table, HashData *d)
Я не полностью понимаю весь код, но, похоже, сортировка не имеет значения. Мы перебираем весь вектор в любом случае (отсортированный или не отсортированный) и в конечном итоге вызываем набор хеш-функций, который не должен зависеть от того, отсортирован ли вектор или нет. Моя первоначальная мысль заключалась в том, что происходило какое-то предсказание ветвей (см. этот вопрос), но от обновления до этот ответ, похоже, что эти вещи не должны иметь значения.
Что происходит?
ИЗМЕНИТЬ
Зазор, по-видимому, возрастает по мере увеличения как размера вектора, так и количества дубликатов.
set.seed(496)
s3 <- sample(10^6, 10^8, replace = TRUE)
s3_sort <- sort(s3)
microbenchmark(dp=duplicated(s3), dp_sort=duplicated(s3_sort), times = 10)
Unit: seconds
expr min lq mean median uq max neval cld
dp 12.149932 12.175665 12.848843 12.495599 12.719861 15.589190 10 b
dp_sort 2.395636 2.401837 2.706674 2.551375 2.677556 4.373653 10 a
Как заметил @alexis_laz, если нет дубликатов, влияние сортировки значительно уменьшается.
s4 <- sample(10^8)
s4_sort <- sort(s4)
microbenchmark(dp=duplicated(s4), dp_sort=duplicated(s4_sort), times = 10)
Unit: seconds
expr min lq mean median uq max neval cld
dp 8.013995 8.130565 8.593626 8.197501 8.438703 10.639452 10 b
dp_sort 6.135788 6.158140 6.751101 6.256739 7.241381 8.913507 10 a