2015-05-12 4 views
0

Я играю с настройками ets и специально с read_concurrency. Я написал простой тест, чтобы оценить, как эта настройка влияет на производительность чтения. Варианты испытаний: here и there.Ets read concurrency tweak

Вкратце, этот тест последовательно создает три [public, set] ETs таблицы с различными read_concurrency опциями (без каких-либо ухищрений, с {read_concurrency, true} и с {read_concurrency, false}). После создания одной таблицы тестовые прогоны N читателей (N - мощность 2 от 4 до 1024). Затем читатели выполняют произвольные чтения в течение 10 секунд и сообщают, сколько операций чтения они выполнили.

Результат для меня довольно удивителен. Нет абсолютно никакой разницы между 3 этими тестами. Вот результат теста.

Non-tweaked table 4 workers: 26610428 read operations 8 workers: 26349134 read operations 16 workers: 26682405 read operations 32 workers: 26574700 read operations 64 workers: 26722352 read operations 128 workers: 26636100 read operations 256 workers: 26714087 read operations 512 workers: 27110860 read operations 1024 workers: 27545576 read operations Read concurrency true 4 workers: 30257820 read operations 8 workers: 29991281 read operations 16 workers: 30280695 read operations 32 workers: 30066830 read operations 64 workers: 30149273 read operations 128 workers: 28409907 read operations 256 workers: 28381452 read operations 512 workers: 29253088 read operations 1024 workers: 30955192 read operations Read concurrency false 4 workers: 30774412 read operations 8 workers: 29596126 read operations 16 workers: 24963845 read operations 32 workers: 29144684 read operations 64 workers: 29862287 read operations 128 workers: 25618461 read operations 256 workers: 27457268 read operations 512 workers: 28751960 read operations 1024 workers: 28790131 read operations

Так мне интересно, как я должен реализовать свой тест, чтобы увидеть разницу и понять, USECASE для этой оптимизации?

Я запустить этот тест на следующих установках:

  1. 2-ядро, 1 физический процессор, Эрланга/ОТП 17 [ГЭР-6.1] [64-битный] [SMP: 2: 2] [асинхронными -threads: 10] [hipe] [kernel-poll: false] (пример тестового вывода из этого прогона)
  2. 2-ядерный, 1 физический процессор, Erlang/OTP 17 [erts-6.1] [64-разрядный ] [smp: 2: 2] [async-threads: 10] [hipe] [kernel-poll: true]
  3. 8-ядерный физический процессор, Erlang/OTP 17 [erts-6.4] [источник] [64- бит] [smp: 8: 8] [async-threads: 10] [hipe] [kernel-poll: false]
  4. 8-ядерный 1 физический процессор, Erlang/OTP 17 [erts-6.4] [источник] [64-бит] [smp: 8: 8] [async-threads: 10] [hipe] [kernel-poll: true]
  5. 64-ядерный 4-ядерный процессор, Erlang/OTP 17 [erts-6.3] [источник] [64-разрядный] [smp: 64: 64] [async-threads: 10] [hipe] [kernel-poll: false ]
  6. 64-ядерный 4-ядерный процессор, Erlang/OTP 17 [erts-6.3] [источник] [64-бит] [smp: 64: 64] [async-threads: 10] [hipe] [kernel-poll: true]

Существует все то же (кроме, конечно, абсолютных значений измерений). Так может кто-нибудь сказать мне ПОЧЕМУ? И что мне делать, чтобы увидеть какую-либо разницу?

UPD По ответ Фреда, я обновил my test, чтобы избежать рабочих обмолота почтового ящика. К сожалению, существенных изменений в результатах не произошло.

UPD One another реализация в соответствии с @Pascal советом. Теперь все рабочие правильно высевают свои случайные генераторы. Снова такие же результаты.

+0

Одно замечания об использовании 'случайного: мундир/1': как вы нерест отдельных процессов без инициализации случайных семян (а значение, хранящееся молча в словаре процесса), все ваши процессы будут выполнять одну и ту же последовательность, но, возможно, это специально: o) – Pascal

+0

Смешная попытка =) Я попытался выровнять случайный генератор для каждого рабочего с помощью «erlang: now/1» , но никаких изменений снова нет. –

+0

Если вы вызываете random: seed (Seed) в каждом процессе, это работает. erlang: now() гарантирует возврат разных значений при каждом вызове, будьте осторожны, чтобы не вызывать его до его появления. – Pascal

ответ

1

Возможно, основной удар вашей работы проверяет возможности планирования узла - почти половина работы, выполненной в вашем тесте, проверяет ваш почтовый ящик, чтобы узнать, нужно ли выходить. Обычно это требует, чтобы виртуальная машина переключала каждый процесс, помещала его в очередь, запускала других, проверяла их почтовые ящики и т. Д. Это дешево, но так же читается ETS. Вполне возможно, что вы создаете много шума.

Альтернативный подход - попросить всех работников прочитать до N миллионов раз в таблице и подсчитать, сколько времени потребуется, пока они не будут выполнены. Это уменьшило бы количество не-ETS-работы на вашем узле и вместо этого сосредоточилось бы только на чтении только из таблицы.

У меня нет никаких гарантий, но я бы поставил на то, что таблицы с более параллелизмом будут быстрее работать.

+0

Да, спасибо. Я обновил свой тестовый пакет, как вы рекомендовали. Но результаты одинаковы. –

1

Я сделал новую версию вашего кода, в этом я добавил логический параметр для выполнения или пропуска доступа к ets. Без сомнения, большая часть времени тратится на других вещах, чем етсь читать:

[править]

После @Viacheslav замечания, я теперь не инициализирует таблицу ... почти никакого эффекта.

код:

-module(perf). 

-export ([tests/0]). 

-define(TABLE_SIZE, 100). 
-define(READS_COUNT, 5000000). 

read_test(Doit,WkCount,NbRead,TableOpt) -> 
    Table = ets:new(?MODULE, TableOpt), 
    [ ets:insert(Table, {I, something}) || I <- lists:seq(1, ?TABLE_SIZE)], 
    L = [erlang:now() || _ <- lists:seq(1,WkCount)], 
    F = fun() -> spawn_readers(Doit,WkCount,NbRead,Table,L) end, 
    {T,_} = timer:tc(F), 
    ets:delete(Table), 
    T. 
table_types() -> 
    [[public, set, {read_concurrency, false}],[public, set, {read_concurrency, true}],[public, set]]. 

spawn_readers(Doit,WkCount, NbRead, Table, L_init) -> 
    [spawn_monitor(fun() -> reader(Doit,NbRead, Table, X) end) || X <- L_init], 
    reap_workers(WkCount). 

reader(Doit,NbRead, Table, Seed) -> 
    random:seed(Seed), 
    reader_loop(Doit,NbRead,Table). 

reader_loop(_,0,_Table) -> 
    ok; 
reader_loop(true,ToRead,Table) -> 
    Key = random:uniform(?TABLE_SIZE), 
    ets:lookup(Table, Key), 
    reader_loop(true,ToRead-1, Table); 
reader_loop(false,ToRead,Table) -> 
    _Key = random:uniform(?TABLE_SIZE), 
    reader_loop(false,ToRead-1, Table). 

reap_workers(0) -> 
    ok; 
reap_workers(Count) -> 
    receive 
     {'DOWN', _, process, _, _} -> 
      reap_workers(Count-1) 
    end. 

tests() -> 
    [[{X,number_proc,Y,read_test(true,Y,?READS_COUNT div Y,X),read_test(false,Y,?READS_COUNT div Y,X)} 
    || X <- table_types()] 
    || Y <- [1,10,100,1000,10000]]. 

и результаты:

8> perf:tests(). 
[[{[public,set,{read_concurrency,false}], 
    number_proc,1,2166000,1456000}, 
    {[public,set,{read_concurrency,true}], 
    number_proc,1,2452000,1609000}, 
    {[public,set],number_proc,1,2513000,1538000}], 
[{[public,set,{read_concurrency,false}], 
    number_proc,10,1153000,767000}, 
    {[public,set,{read_concurrency,true}], 
    number_proc,10,1180000,768000}, 
    {[public,set],number_proc,10,1181000,784000}], 
[{[public,set,{read_concurrency,false}], 
    number_proc,100,1149000,755000}, 
    {[public,set,{read_concurrency,true}], 
    number_proc,100,1157000,747000}, 
    {[public,set],number_proc,100,1130000,749000}], 
[{[public,set,{read_concurrency,false}], 
    number_proc,1000,1141000,756000}, 
    {[public,set,{read_concurrency,true}], 
    number_proc,1000,1169000,748000}, 
    {[public,set],number_proc,1000,1146000,769000}], 
[{[public,set,{read_concurrency,false}], 
    number_proc,10000,1224000,832000}, 
    {[public,set,{read_concurrency,true}], 
    number_proc,10000,1274000,855000}, 
    {[public,set],number_proc,10000,1162000,826000}]] 
+0

+1 например, лаконичный и чистый код. Нравится. Я определенно должен улучшить свой codestyle. Одна вещь, ваш тест, читающий пустую таблицу. Но даже если таблица не пуста (я добавил строку, заполняющую таблицу перед нерестовыми работниками), все не меняется драматично. С одной стороны, я потратил столько времени на не-ets-связанные вещи. Но с другой стороны, я даже не могу представить себе, как это сделать, когда мой узел должен читать только ets и не делать ничего другого, чтобы увидеть реальную производительность impcat. –

+0

Re Oooops ,, Я забыл добавить инициализацию во время рефакторинга! Кстати, я сделал это только для того, чтобы избежать макросов eunit, пытаясь уменьшить потенциальные накладные расходы: очевидно, я уменьшил его до много: o) – Pascal

+0

также я отправил свой вопрос в список erlang-questions (поток начинается здесь http: /erlang.org/pipermail/erlang-questions/2015-May/084578.html) Теперь жду. Я не чувствую себя слишком свободным, чтобы врываться в внутренние органы, но желаю начать более глубокое исследование. –