Изменения в моей библиотеке сделали это намного медленнее. Профилирование мне не помогает. В чем может быть причина замедления? - PullRequest
9 голосов
/ 31 января 2012

Моя проблема, кратко

Я внес изменения в свою библиотеку, теперь она намного медленнее, но я не могу понять, где она проводит все это дополнительное время. Профилирование отчетов не помогает. Пожалуйста, помогите мне выяснить причину.

Некоторый контекст

Я создал клиентскую библиотеку Redis под названием Hedis и у меня есть программа для тестирования. Теперь я сделал некоторые внутренние изменения в библиотеке, чтобы очистить архитектуру. Это привело к снижению производительности (в запросах Redis в секунду, измеряемой указанным тестом) примерно в 2,5 раза.

Тест открывает 50 сетевых подключений к серверу Redis на локальном хосте. Соединения обрабатываются по-разному между двумя версиями:

  • Быстрая версия использует один поток на соединение (таким образом, в тесте производительности одновременно работает 50 потоков). Он читает из сокета обрабатывать с помощью unsafeInterleaveIO (я опишу свой подход в широком штрихи в сообщении в блоге здесь ). Я была немного несчастна с архитектурой, следовательно, я изменил вещи на
  • медленная версия , которая использует три потока на соединение. Они связываются через два Chan s (150 потоков, работающих в тесте производительности).

Еще немного информации, которая может иметь отношение к делу:

  • Составлено с GHC 7.2.2.
  • Программа сравнения не меняется между двумя версиями, поэтому сеть трафик такой же.
  • Обе версии используют однопоточную среду выполнения (скомпилировано без -threaded).
  • Все темы создаются путем вызова forkIO. не дороже forkOS.

Результаты профилирования

Профилирование не дает мне четкой причины падения производительности. Согласно отчету о профилировании, обе версии проводят более 99% своего времени в System.IO.hFlush и Data.ByteString.hGetSome. Количество вызовов hFlush и hGetSome одинаково в обеих версиях. Поскольку сетевой трафик в обоих случаях одинаков, эти функции не могут быть причиной замедления.

Единственное существенное различие, которое я могу измерить между двумя версиями, заключается в том, что time (утилита Unix) говорит мне: медленная версия (с трехкратным числом потоков) тратит значительно больше времени на "sys" вместо "user" ", по сравнению с быстрой версией. Флаг GHC +RTS -s сообщает об этом как о сниженной производительности.

Вот выходные данные программы для обеих версий с флагом +RTS -s:

Тест быстрой версии

$ time ./dist/build/hedis-benchmark/hedis-benchmark +RTS -s -p
ping                   33305.29 Req/s
get                    25802.92 Req/s
mget                   18215.94 Req/s
ping (pipelined)      268994.36 Req/s
   5,118,163,904 bytes allocated in the heap
     185,075,608 bytes copied during GC
       4,084,384 bytes maximum residency (39 sample(s))
         916,544 bytes maximum slop
              10 MB total memory in use (0 MB lost due to fragmentation)

                                    Tot time (elapsed)  Avg pause  Max pause
  Gen  0      7416 colls,     0 par    0.38s    0.40s     0.0001s    0.0003s
  Gen  1        39 colls,     0 par    0.03s    0.03s     0.0007s    0.0009s

  INIT    time    0.00s  (  0.00s elapsed)
  MUT     time    7.93s  ( 12.34s elapsed)
  GC      time    0.41s  (  0.43s elapsed)
  RP      time    0.00s  (  0.00s elapsed)
  PROF    time    0.00s  (  0.00s elapsed)
  EXIT    time    0.00s  (  0.00s elapsed)
  Total   time    8.33s  ( 12.76s elapsed)

  %GC     time       4.9%  (3.3% elapsed)

  Alloc rate    645,587,554 bytes per MUT second

  Productivity  95.1% of total user, 62.1% of total elapsed


real    0m12.772s
user    0m8.334s
sys     0m4.424s

Тест медленной версии

$ time ./dist/build/hedis-benchmark/hedis-benchmark +RTS -s -p
ping                   11457.83 Req/s
get                    11169.64 Req/s
mget                    8446.96 Req/s
ping (pipelined)      130114.31 Req/s
   6,053,055,680 bytes allocated in the heap
   1,184,574,408 bytes copied during GC
       9,750,264 bytes maximum residency (198 sample(s))
       2,872,280 bytes maximum slop
              26 MB total memory in use (0 MB lost due to fragmentation)

                                    Tot time (elapsed)  Avg pause  Max pause
  Gen  0      9105 colls,     0 par    2.11s    2.14s     0.0002s    0.0006s
  Gen  1       198 colls,     0 par    0.23s    0.24s     0.0012s    0.0093s

  INIT    time    0.00s  (  0.00s elapsed)
  MUT     time   10.99s  ( 27.92s elapsed)
  GC      time    2.34s  (  2.38s elapsed)
  RP      time    0.00s  (  0.00s elapsed)
  PROF    time    0.00s  (  0.00s elapsed)
  EXIT    time    0.00s  (  0.00s elapsed)
  Total   time   13.33s  ( 30.30s elapsed)

  %GC     time      17.6%  (7.8% elapsed)

  Alloc rate    550,656,490 bytes per MUT second

  Productivity  82.4% of total user, 36.3% of total elapsed


real    0m30.305s
user    0m13.333s
sys     0m16.964s

Есть ли у вас какие-либо идеи или подсказки, откуда могло бы появиться это дополнительное время?

Ответы [ 2 ]

3 голосов
/ 01 февраля 2012

Согласно отчету о профилировании, большую часть времени проводят в hFlush и hGetSome. Согласно time, медленная версия занимает намного больше системного времени. Итак, моя гипотеза состоит в том, что много времени тратится заблокированным и ожидающим, ожидает ли он большего ввода или блокирует и разблокирует потоки.

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

2 голосов
/ 31 января 2012

My думаю, может быть связано с накладными расходами Chan.

Моей первой мыслью было увеличение времени GC, но это совсем не так. Поэтому моя вторая мысль заключается в том, что, возможно, проблема заключается во всех блокировках и разблокировках, связанных с использованием Chan (который реализован поверх MVar). Но это только предположение.

Вы могли бы вместо этого попробовать TChan (то есть, STM) и посмотреть, имеет ли это малейшее различие. (Возможно, вы могли бы написать небольшой скелет, чтобы сравнить их и посмотреть, не в этом ли проблема, а не переопределить ваш «настоящий» код.)

Кроме этого, у меня нет идей.

...