Замедление выполнения ets select - PullRequest
5 голосов
/ 31 октября 2019

Я провел несколько тестов на предмет эффективности выбора из таблиц ets и заметил странное поведение. Например, у нас есть простая таблица ets (без каких-либо конкретных опций), в которой хранится ключ / значение - случайная строка и число:

:ets.new(:table, [:named_table])

for _i <- 1..2000 do
    :ets.insert(:table, {:crypto.strong_rand_bytes(10) 
    |> Base.url_encode64 
    |> binary_part(0, 10), 100})
end

и одна запись с известным ключом:

:ets.insert(:table, {"test_string", 200})

Теперь есть простая глупая функция бенчмарка, которая пытается несколько раз выбрать test_string из таблицы ets и измерить время каждого выбора:

test_fn = fn() ->
  Enum.map(Enum.to_list(1..10_000), fn(x) ->
    :timer.tc(fn() ->
      :ets.select(:table, [{{:'$1', :'$2'}, 
                           [{:'==', :'$1', "test_string"}], 
                           [:'$_']}])
    end)
  end) |> Enum.unzip
end

Теперь, если я посмотрю на максимальное время с помощью Enum.max(timings) он вернет значение, которое примерно в 10 раз больше, чем почти все другие варианты выбора. Так, например:

iex(1)> {timings, _result} = test_fn.()
....
....
....
iex(2)> Enum.max(timings)
896
iex(3)> Enum.sum(timings) / length(timings)
96.8845

Здесь мы можем видеть, что максимальное значение почти в 10 раз превышает среднее значение.

Что здесь происходит? Это как-то связано с GC, временем для выделения памяти или чем-то вроде этого? Есть ли у вас какие-либо идеи, почему выборка из таблицы ets может иногда приводить к таким замедлениям или как ее профилировать?

UPD.

вот график распределения таймингов:enter image description here

1 Ответ

3 голосов
/ 05 ноября 2019

match_spec, второй аргумент select/2 делает его медленнее.

В соответствии с ответом на этот вопрос
Erlang: позволяет выбрать и сопоставить производительность

In trivial non-specific use-cases, select is just a lot of work around match.  
In non-trivial more common use-cases, select will give you what you really want a lot quicker.

Кроме того, если вы работаете с таблицами типа set или ordered_set, чтобы получить значение на основе ключа, используйте взамен lookup / 2, так как это намного быстрее.
На моем компьютере следующий код

  def lookup() do
    {timings, _} = Enum.map(Enum.to_list(1..10_000), fn(_x) ->
      :timer.tc(fn() ->
        :ets.lookup(:table, "test_string")
      end)
    end) |> Enum.unzip
    IO.puts Enum.max(timings)
    IO.puts Enum.sum(timings) / length(timings)
  end

напечатан

0 
0.0

Пока ваш напечатан

16000
157.9

Если вам интересно, здесь вы можете найти NIF Cкод для ets: выберите.
https://github.com/erlang/otp/blob/9d1b3bb0db87cf95cb821af01189f6d6be072f79/erts/emulator/beam/erl_db.c

...