Pull to refresh

Comments 18

«На каждый запрос расходуется не менее 256Кб памяти на стек процесса»
что такое стек процесса? откуда вообще этот вывод?

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

«Паразитные нагрузки в 2 раза выше»
почему в два? не три, не четыре? выше чем что?
1. Спасибо конечно же стек потоков. Подправил. Стек потоку необходим для передачи параметров в методы, сохранение состояний объектов для возврата из других методов, их виртуальных таблиц.
2. Как я и сделал чтобы получить общее время, с так называемыми паразитными нагрузками, которые расходуются на запуск-завершение потоков, синхронизацию между ними.
3. Как вы видите при однопотоковом формируется формула (mockSpeed+1)*N, где +1 можно отнести к паразитным нагрузкам, тогда же как в многопоточном эта формула 0.5*N+(mockSpeed+1.5)*N или (mockSpeed+2)*N, где уже +2 — значение паразитных нагрузок.
Кстати забыл заострить внимание на одном-единственном комментарии в программах. Хоть мы и выделили всего 64Кб памяти, поток сожрал 256Кб. Я где-то читал что 256Кб минимальный возможный объем памяти выделяемый под стек в Vista. В .Net такая же фигня твориться и на XP :)
я немного переписал ваш тест. добавил Interlocked.increment и сделал несколько итераций сразу, убрал counter.

сам код здесь: codepaste.ru/2018/
результат здесь: codepaste.ru/2019/

получилось по ~180 байт на поток. явное указание максимального размера стека для потока никак на результате не отразилось. меряется общее время прогона, отдельно для инициализации потоков — нет. время работы в MT не зависит от количества запросов при num_of_threads >> mockSpeed.

ну и естественно все это мало имеет смысла при количестве процессоров равным 2.

Kernel version: Microsoft Windows XP, Multiprocessor Free
Service pack: 3
Processors: 2
Processor speed: 2.9 GHz
Processor type: Intel® Pentium® 4 CPU
Physical memory: 2046 MB
явное указание максимального размера стека для потока никак на результате не отразилось
Все дело в том, что вы смотрите на изменение размера кучи, а не на изменение объема Private Bytes текущего процесса.
время работы в MT не зависит от количества запросов при num_of_threads >> mockSpeed. А вот тут поподробнее, у меня «паразитные нагрузки» вашего кода — огромные…
в случае виртуальной машины не так важно Private Bytes. джава, например, освобождает память только при необходимости.

сложно говорить о паразитных нагрузках на тестовом примере. в реальности я бы использовал тредпул. время работы можно посмотреть в выложенном логе.
взять хотя бы вашу формулу: 0.5*N+(mockSpeed+1.5)*N. если num_of_threads >> mockSpeed (кстати, тут неточность, вы ждете инкремента в 1200, тогда num_of_treads > 1200), то достаточно одного вызова в каждой нитке, а тогда формула должна превращается в С*N + mockSpeed, где C — какая-то константа.
Вот как раз в случае с виртуальной машиной Private Bytes очень важно. Виртуальная машина работает не с памятю, а кучей, размер которой вы получили при помощи GC.GetTotalMemory. Разница же между Private Bytes и GC.GetTotalMemory и есть затраты виртуальной машины. Вас не смутило то, что при МТ я performance.RawValue получал в залоченной области, иначе бы могло быть закрыто несколько потоков и соответственно освобождена память мгновенно, на уровне менеджера памяти защищенного режима самой ОС.

О, я понял о чем вы. У нас разное понятие производительности. Вы оцениваете затраты физического (астрономического) времени, я же оцениваю затраты в «астрономических» тактах процессора. Т.е. на вашей машинке бы «астрономические» паразитные затраты были бы в 2 раза меньше, т.к. у вас 2 3хГц процессора(ядра). Здесь я наверное допустил самую большую ошибку. Мне стоило писать сразу в тактах процессора… Хорошая мысля как говориться…
Как насчет использовать Erlang или GHC для таких целей? У них обоих хорошо с concurrency. И даже лучше с абстракцией, поэтому вам не придется даже ничего сильно переписывать.
Зачем нужно 1200 потоков, синхронизация и переключения сожрут производительность.
Создайте 2*кол-во ядер и подкидывайте им задачи по мере освобождения.
ThreadPool это некий компромис. Он хорош тогда когда потоки в основном живут в sleep. Когда же их основная задача — обработать запрос и умереть, то ThreadPool можно рассматривать как несколько однопоточных обработчиков, синхронизированных между собой общей очередью. При этом мы выигрываем в производительности и несильно проигрываем в памяти. Но от основной проблемы однопоточного обработчика, зависимости общего времени основных потоков от времени выполнения всех запросов, мы не избавляемся.
Так не должны они умирать.

Обработал запрос и заснул. Ждём следующего.

Пул потоков сам доктор прописал.
Каждый поток пула находится в спящем состоянии до тех пор, пока очередь задач пула пуста. В этом случае процессорное время не расходуется. Как только в пул поступает задача, один из потоков просыпается и начинает её обрабатывать. Если во время обработки поступила ещё одна задача, она передаётся следующему потоку и т.д. Если все потоки заняты, задача ставится в очередь, и первый освободившийся поток без промедления забирает её себе. Т.о. если у вас будет большой поток запросов, то оверхеда практически нет. Ну а если поток запросов маленький, то и волноваться не стоит, что потоки переходят в спящий режим.
Кстати, помимо Interlocked-операций советую покопать в сторону атрибута [ThreadStatic].
Смотрите. Есть у нас 1200 запросов и 4 обработчика в ThreadPool. Легкой операцией деления мы получаем по 300 запросов на поток. Увеличим поток запросов в 4 раза и получаем 4 засинхронизированных однопоточных обработчика, рассмотренных в первой части поста. Единственное что мы получим — полная загрузка всех ядер и процессоров системы при минимальных затратах памяти…
У меня сейчас на работе аналогичная задача, пишу сервис. Но я решил каждую обработку выполнять асинхронно. Естесственно потоки будут браться из .Net ThreadPool. Не знаю, как это скажется на производительности. Но уже был опыт аналогичный, писал web handler (IAsyncHttpHandler). По сравнению с однопоточной страницей производительность на той же машине выросла на 15-20%. Использование памяти смотрел правда по диспетчеру задач. Время замерял немного по другому. сначала перед созданием объекта ответа, конечная точка — полностью сформированный объект ответа, готовый для выплёвывания Response.Write(). Время на обработку запроса уменьшилось.
выиграл в производительности — проиграл в памяти, все ок)
Именно. При этом не всегда алгоритм распараллеливания обработчиков может привести к увеличению производительности, т.к. простои из-за паразитного времени на выделение потоков бывает больше, чем сама обработка потока.
Sign up to leave a comment.

Articles

Change theme settings