Я думаю, что самое большое замедление вашего примера с эликсиром - это сколько вы перечисляете. На каждой итерации вы делаете дорогостоящую разницу, тогда как Enum.random/1
, который повторяет список снова.
Редактировать: я собираюсь предоставить более простые примеры и тесты для них
Есть я могу подумать о двух основных подходах:
- Повторно генерировать случайное число, пропуская любые повторы.
- Выбрать случайные числа из набора оставшихся опций.
Вариант 1 будет быстрее, когда ваш max
(то, что я называю вашим вводом "наибольшего числа") значительно больше, чем ваш count
(то, что я называю вашим вводом "суммы"). Когда count
приближается к max
, вариант 2 будет догонять и затем передавать скорость по варианту 1.
Вариант 1
Этот подход сохраняет набор уже выбранных чисел и проверяет его. как это порождает новые. Самый большой фактор, определяющий производительность - столкновения. Когда count
мало по сравнению с max
, столкновения менее вероятны, и выбрасывается меньшее число. По мере приближения count
к max
коллизии становятся все более вероятными, и тратится много времени на генерирование чисел, которые будут выбрасываться.
Мой первоначальный ответ был более сложным примером такого подхода. Наш сторонник этого подхода в приведенном ниже тесте может быть довольно простым.
defmodule RandUniq.Stream do
@behaviour RandUniq
@impl RandUniq
def take(count, max) do
fn -> :random.uniform(max) end
|> Stream.repeatedly()
|> Stream.uniq()
|> Enum.take(count)
end
end
Вариант 2
Общие реализации этого подхода создают новую, рандомизированную структуру данных на основе max
, которая тогда можно просто взять count
из него. Это требует больших начальных затрат, поскольку каждый элемент размещается в случайном порядке, что может быть расточительным для небольшого count
по сравнению с max
. Тем не менее, работа для большого count
в основном такая же, как и для маленького, так что именно в этом и заключается этот подход.
Мы включим пару чемпионов для этого подхода на основе Enum.take_random/2
и Enum.shuffle/1
.
defmodule RandUniq.TakeRandom do
@behaviour RandUniq
@impl RandUniq
def take(count, max) do
Enum.take_random(1..max, count)
end
end
defmodule RandUniq.Shuffle do
@behaviour RandUniq
@impl RandUniq
def take(count, max) do
1..max
|> Enum.shuffle()
|> Enum.take(count)
end
end
Контроль
Для теста давайте также включим оригинал.
defmodule RandUniq.Control do
@behaviour RandUniq
@impl RandUniq
def take(count, max) do
numbers = Enum.to_list(1..max)
Enum.reduce(1..count, [], fn _t, a ->
[Enum.random(numbers -- a)] ++ a
end)
end
end
Тест
Вот как на моем автомате (входные данные вида count / max
):
##### With input 1000 / 10000 #####
Name ips average deviation median 99th %
Elixir.RandUniq.Stream 1404.78 0.71 ms ±17.04% 0.67 ms 1.07 ms
Elixir.RandUniq.TakeRandom 247.34 4.04 ms ±4.77% 3.99 ms 4.65 ms
Elixir.RandUniq.Shuffle 167.64 5.96 ms ±13.41% 5.83 ms 8.49 ms
Elixir.RandUniq.Control 0.33 3013.59 ms ±0.63% 3013.59 ms 3026.97 ms
Comparison:
Elixir.RandUniq.Stream 1404.78
Elixir.RandUniq.TakeRandom 247.34 - 5.68x slower +3.33 ms
Elixir.RandUniq.Shuffle 167.64 - 8.38x slower +5.25 ms
Elixir.RandUniq.Control 0.33 - 4233.43x slower +3012.87 ms
##### With input 2000 / 10000 #####
Name ips average deviation median 99th %
Elixir.RandUniq.Stream 718.81 1.39 ms ±9.76% 1.35 ms 1.90 ms
Elixir.RandUniq.TakeRandom 184.45 5.42 ms ±8.14% 5.33 ms 6.73 ms
Elixir.RandUniq.Shuffle 162.37 6.16 ms ±11.64% 6.03 ms 8.67 ms
Elixir.RandUniq.Control 0.170 5897.41 ms ±0.00% 5897.41 ms 5897.41 ms
Comparison:
Elixir.RandUniq.Stream 718.81
Elixir.RandUniq.TakeRandom 184.45 - 3.90x slower +4.03 ms
Elixir.RandUniq.Shuffle 162.37 - 4.43x slower +4.77 ms
Elixir.RandUniq.Control 0.170 - 4239.14x slower +5896.02 ms
##### With input 3000 / 10000 #####
Name ips average deviation median 99th %
Elixir.RandUniq.Stream 448.74 2.23 ms ±8.41% 2.20 ms 2.80 ms
Elixir.RandUniq.Shuffle 166.81 5.99 ms ±11.74% 5.86 ms 8.44 ms
Elixir.RandUniq.TakeRandom 162.07 6.17 ms ±5.27% 6.12 ms 7.18 ms
Elixir.RandUniq.Control 0.112 8951.54 ms ±0.00% 8951.54 ms 8951.54 ms
Comparison:
Elixir.RandUniq.Stream 448.74
Elixir.RandUniq.Shuffle 166.81 - 2.69x slower +3.77 ms
Elixir.RandUniq.TakeRandom 162.07 - 2.77x slower +3.94 ms
Elixir.RandUniq.Control 0.112 - 4016.91x slower +8949.31 ms
##### With input 4000 / 10000 #####
Name ips average deviation median 99th %
Elixir.RandUniq.Stream 293.83 3.40 ms ±9.04% 3.35 ms 4.39 ms
Elixir.RandUniq.Shuffle 173.81 5.75 ms ±10.26% 5.64 ms 7.54 ms
Elixir.RandUniq.TakeRandom 138.75 7.21 ms ±8.61% 7.11 ms 9.26 ms
Elixir.RandUniq.Control 0.0865 11566.12 ms ±0.00% 11566.12 ms 11566.12 ms
Comparison:
Elixir.RandUniq.Stream 293.83
Elixir.RandUniq.Shuffle 173.81 - 1.69x slower +2.35 ms
Elixir.RandUniq.TakeRandom 138.75 - 2.12x slower +3.80 ms
Elixir.RandUniq.Control 0.0865 - 3398.48x slower +11562.71 ms
##### With input 5000 / 10000 #####
Name ips average deviation median 99th %
Elixir.RandUniq.Stream 216.28 4.62 ms ±7.90% 4.60 ms 5.66 ms
Elixir.RandUniq.Shuffle 168.73 5.93 ms ±10.47% 5.83 ms 7.93 ms
Elixir.RandUniq.TakeRandom 126.83 7.88 ms ±6.92% 7.81 ms 9.17 ms
Elixir.RandUniq.Control 0.0687 14556.00 ms ±0.00% 14556.00 ms 14556.00 ms
Comparison:
Elixir.RandUniq.Stream 216.28
Elixir.RandUniq.Shuffle 168.73 - 1.28x slower +1.30 ms
Elixir.RandUniq.TakeRandom 126.83 - 1.71x slower +3.26 ms
Elixir.RandUniq.Control 0.0687 - 3148.10x slower +14551.37 ms
##### With input 6000 / 10000 #####
Name ips average deviation median 99th %
Elixir.RandUniq.Stream 176.27 5.67 ms ±6.76% 5.61 ms 6.80 ms
Elixir.RandUniq.Shuffle 172.27 5.80 ms ±9.31% 5.73 ms 7.34 ms
Elixir.RandUniq.TakeRandom 115.76 8.64 ms ±11.56% 8.66 ms 10.86 ms
Elixir.RandUniq.Control 0.0635 15740.88 ms ±0.00% 15740.88 ms 15740.88 ms
Comparison:
Elixir.RandUniq.Stream 176.27
Elixir.RandUniq.Shuffle 172.27 - 1.02x slower +0.132 ms
Elixir.RandUniq.TakeRandom 115.76 - 1.52x slower +2.97 ms
Elixir.RandUniq.Control 0.0635 - 2774.60x slower +15735.20 ms
##### With input 7000 / 10000 #####
Name ips average deviation median 99th %
Elixir.RandUniq.Shuffle 171.13 5.84 ms ±10.26% 5.70 ms 7.77 ms
Elixir.RandUniq.Stream 140.55 7.11 ms ±9.16% 7.10 ms 8.84 ms
Elixir.RandUniq.TakeRandom 103.55 9.66 ms ±9.93% 9.92 ms 11.36 ms
Elixir.RandUniq.Control 0.0561 17837.14 ms ±0.00% 17837.14 ms 17837.14 ms
Comparison:
Elixir.RandUniq.Shuffle 171.13
Elixir.RandUniq.Stream 140.55 - 1.22x slower +1.27 ms
Elixir.RandUniq.TakeRandom 103.55 - 1.65x slower +3.81 ms
Elixir.RandUniq.Control 0.0561 - 3052.52x slower +17831.30 ms
##### With input 8000 / 10000 #####
Name ips average deviation median 99th %
Elixir.RandUniq.Shuffle 172.44 5.80 ms ±9.72% 5.68 ms 7.46 ms
Elixir.RandUniq.Stream 109.34 9.15 ms ±10.45% 8.99 ms 11.37 ms
Elixir.RandUniq.TakeRandom 90.51 11.05 ms ±9.95% 11.11 ms 13.33 ms
Elixir.RandUniq.Control 0.0507 19712.01 ms ±0.00% 19712.01 ms 19712.01 ms
Comparison:
Elixir.RandUniq.Shuffle 172.44
Elixir.RandUniq.Stream 109.34 - 1.58x slower +3.35 ms
Elixir.RandUniq.TakeRandom 90.51 - 1.91x slower +5.25 ms
Elixir.RandUniq.Control 0.0507 - 3399.20x slower +19706.22 ms
##### With input 9000 / 10000 #####
Name ips average deviation median 99th %
Elixir.RandUniq.Shuffle 163.33 6.12 ms ±10.87% 6.01 ms 8.20 ms
Elixir.RandUniq.TakeRandom 87.59 11.42 ms ±12.70% 11.59 ms 14.92 ms
Elixir.RandUniq.Stream 77.42 12.92 ms ±7.04% 12.91 ms 15.04 ms
Elixir.RandUniq.Control 0.0458 21839.19 ms ±0.00% 21839.19 ms 21839.19 ms
Comparison:
Elixir.RandUniq.Shuffle 163.33
Elixir.RandUniq.TakeRandom 87.59 - 1.86x slower +5.29 ms
Elixir.RandUniq.Stream 77.42 - 2.11x slower +6.79 ms
Elixir.RandUniq.Control 0.0458 - 3567.03x slower +21833.07 ms
##### With input 10000 / 10000 #####
Name ips average deviation median 99th %
Elixir.RandUniq.Shuffle 152.63 6.55 ms ±15.19% 6.35 ms 11.46 ms
Elixir.RandUniq.TakeRandom 99.66 10.03 ms ±13.20% 9.72 ms 13.49 ms
Elixir.RandUniq.Stream 26.56 37.65 ms ±11.88% 36.73 ms 55.84 ms
Elixir.RandUniq.Control 0.0420 23822.66 ms ±0.00% 23822.66 ms 23822.66 ms
Comparison:
Elixir.RandUniq.Shuffle 152.63
Elixir.RandUniq.TakeRandom 99.66 - 1.53x slower +3.48 ms
Elixir.RandUniq.Stream 26.56 - 5.75x slower +31.10 ms
Elixir.RandUniq.Control 0.0420 - 3635.98x slower +23816.10 ms
Кажется, RandUniq.Stream
, наш чемпион по варианту 1, остается самым быстрым, пока count
не достигнет 60-70 % от max
(по крайней мере, в этом масштабе). Затем RandUniq.Shuffle
, основываясь на ответе @ zwipp ie, выходит на первое место.