Случайны выбор дневника Раскрыть/свернуть полный список возможностей


Найдено 1257 сообщений
Cообщения с меткой

производительность - Самое интересное в блогах

Следующие 30  »
rss_rss_hh_new

[Перевод] Ранний подъем — не залог высокой продуктивности. Вот, что вам нужно

Четверг, 18 Августа 2016 г. 10:38 (ссылка)

Один из величайших мифов о продуктивности таков: рано просыпаясь по утрам, вы повышаете свою производительность.



image



Вставая рано, вы не повышаете собственную производительность. Между социально-экономическим положением «жаворонков» и «сов» нет никакой разницы. Реальную пользу приносит тщательное планирование своей жизни и работы — в том числе и утреннего режима.



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



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



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



Всем нравится идея тренировок, обладания рельефным прессом и сыроядения. Но на практике подобные перемены подразумевают множество мелких ежедневных жертв, которые, кажется, приносят больше неудобств, чем пользы.



Чтобы добиться серьезных изменений, нужно зачастую плыть против течения, борясь с цунами крошечных привычек. Поэтому стоит сначала задаться вопросом о том, зачем вам эти перемены, а затем, приняв решение, тщательно продумать свои действия.



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



Эта же концепция касается и утреннего распорядка. Если некоторые жаворонки и продуктивнее сов, то причина заключается в том, что они планируют свое пробуждение и следующую за ним активность. Одежда для пробежки заранее подготовлена (при условии, что они в ней не спят), для запуска кофеварки достаточно лишь нажать на кнопку, а утренняя газета уже дожидается у двери.



Независимо от того, встаете вы в 5:30 или в 8:30, у вас все равно есть 16 часов на то, чтобы сделать свой день успешным и полезным.



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



P.S. Рекомендуем ещё одну полезную статью по теме работы над собой — 15 людей, которые доказывают, что вам не придется просыпаться рано, чтобы быть успешным.



Автор перевода — Давиденко Вячеслав, основатель компании TESTutor.

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/307986/

Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Супермедленный и супербыстрый бенчмарк

Воскресенье, 07 Августа 2016 г. 11:24 (ссылка)

В недавней статье про производительность Java разгорелась дискуссия на тему измерения производительности. Глядя на неё, с грустью приходится сознавать, что многие люди до сих пор не понимают, насколько сложно правильно измерить время выполнения того или иного кода. Кроме того, люди вообще не привыкли, что один и тот же код в разных условиях может выполняться существенно разное время. К примеру, вот одно из мнений:



Если мне надо узнать, "какой язык быстрее для меня на моей задаче", то я прогоню самый примитивный бенчмарк в мире. Если разница будет существенной (скажем, на порядок) — то скорее всего и на пользовательской машине все будет примерно также.


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



Скажем, мы увидели Java 8 Stream API и хотим проверить, насколько быстро оно работает для простой математики. Например, для простоты возьмём стрим из целых чисел от 0 до 99999, возведём каждое в квадрат с помощью операции map, ну и на этом всё, больше ничего делать не будем. Мы же просто производительность замерить хотим, да? Но даже беглого просмотра API хватит, чтобы увидеть, что стримы ленивы и IntStream.range(0, 100_000).map(x -> x * x) по факту ничего не выполнит. Поэтому мы добавим терминальную операцию forEach, которая как-нибудь использует наш результат. Например, увеличит его на единичку. В итоге мы получим вот такой тест:



static void test() {
IntStream.range(0, 100_000).map(x -> x * x).forEach(x -> x++);
}


Отлично. Как измерить, сколько он работает? Все знают: взять время в начале, время в конце и посчитать разницу! Добавим метод, который производит замер времени и возвращает результат в наносекундах:



static long measure() {
long start = System.nanoTime();
test();
long end = System.nanoTime();
return end - start;
}


Ну а теперь просто выведем результат. На моём не самом быстром Core i7 и Open JDK 8u91 64bit я в разных запусках получаю число примерно в районе от 50 до 65 миллионов наносекунд. То есть 50-65 миллисекунд. Сто тысяч возведений в квадрат за 50 миллисекунд? Это чудовищно! Это всего два миллиона раз в секунду. Двадцать пять лет назад компы и то быстрее в квадрат возводили. Java безбожно тормозит! Или нет?



На самом деле первое использование лямбд и Stream API в приложении всегда добавит задержку на 50-70 мс на современных компьютерах. Ведь за это время надо сделать немало вещей:




  • Загрузить классы для генерации рантайм-представлений лямбд (см. LambdaMetafactory) и всё, что с ними связано.

  • Загрузить классы самого Stream API (их там немало)

  • Для лямбд, что используются в нашем коде (в нашем случае одна) и в коде Stream API (а вот там их немало) сгенерировать рантайм-представление.

  • JIT-компилировать это всё добро хотя бы как-нибудь.



Всё это требует немало времени и на самом деле даже удивительно, что удаётся уложиться в 50 мс. Но всё это нужно ровно один раз.



Лирическое отступление



Вообще с наличием динамической загрузки и кэширования чего бы то ни было становится очень сложно понять, что же мы измерили. Это касается не только Java. Простой библиотечный вызов может инициировать подгрузку с жёсткого диска и инициализацию разделяемой библиотеки (а представьте, что жёсткий диск ещё и в спящий режим ушёл). В результате вызов может занять гораздо больше времени. Париться по этому поводу или нет? Иногда приходится. Например, во времена Windows 95 загрузка разделяемой библиотеки OLE32.DLL занимала существенное время и в тормозах бы объявили первую программу, которая бы попыталась загрузить OLE32. Это вынуждало разработчиков по возможности не загружать OLE32 как можно дольше, чтобы виноватыми стали другие программы. Кое-где в других библиотеках даже реализованы функции, дублирующие некоторые функции OLE32, как раз с целью избежать загрузки OLE32. Подробнее об этой истории читайте у Рэймонда Чена.


Итак, мы поняли, что наш бенчмарк супермедленный, потому что в процессе делается много вещей, которые надо сделать ровно один раз после загрузки. Если наша программа планирует работать больше секунды, скорее всего нас это сильно не волнует. Поэтому давайте "прогреем JVM" — произведём этот замер 100 тысяч раз и выведем результат последнего замера:



for (int i = 100000; i >= 0; i--) {
long res = measure();
if(i == 0)
System.out.println(res);
}


Эта программа завершается быстрее, чем за секунду, и печатает на моей машине 70-90 наносекунд. Это супер! Значит, на одно возведение в квадрат приходится 0.7-0.9 пикосекунд? Java возводит в квадрат больше триллиона раз в секунду? Java супербыстрая! Или нет?



Уже на второй итерации многое из вышеприведённого списка выполнится и процесс ускорится раз в 100. Дальше JIT-компилятор будет постепенно докомпилировать разные куски кода (его внутри Stream API немало), собирая профили выполнения и оптимизируя всё больше. В конечном итоге JIT оказывается достаточно умён, чтобы заинлайнить всю цепочку лямбд и понять, что результат умножения нигде не используется. Наивная попытка использовать его через инкремент JIT-компилятор не обманула: побочного эффекта у этой операции всё равно нет. JIT-компилятору не хватило сил выкосить вообще весь стрим целиком, но он смог выкосить внутренний цикл, фактически сделав производительность теста не зависящей от количества итераций (замените IntStream.range(0, 100_000) на IntStream.range(0, 1_000_000) — результат будет тот же).



Кстати, на таких временах оказывается существенным время выполнения и гранулярность nanoTime(). Даже на одинаковом железе но в разных OS вы можете получить существенно разный ответ. Подробнее об этом — у Алексея Шипилёва.



Итак, мы написали "самый примитивный бенчмарк". Сперва он оказался супермедленным, а после небольшой доработки — супербыстрым, почти в миллион раз быстрее. Мы хотели измерить, как быстро с помощью Stream API выполняется возведение в квадрат. Но в первом тесте эта математическая операция потонула в море других операций, а во втором тесте просто не выполнялась. Опасайтесь делать поспешные выводы.



Где же правда? Правда в том, что этот тест не имеет ничего общего с реальностью. Он не производит видимых эффектов в вашей программе, то есть фактически он ничего не делает. В реальности вы редко пишете код, который ничего не делает, и уж конечно, он вряд ли приносит вам деньги (хотя бывают и исключения). Пытаться ответить на вопрос, сколько времени на самом деле выполняется возведение в квадрат внутри Stream API, вообще малоосмысленно: это очень простая операция и в зависимости от окружающего кода JIT-компилятор может очень по-разному скомпилировать цикл с умножением. Помните, что производительность не аддитивна: если A выполняется x секунд, а B выполняется y секунд, то совсем не факт, что выполнение A и B займёт x+y секунд. Может оказаться абсолютно не так.



Если вы хотите лёгких ответов, то в реальных программах правда будет где-то посередине: накладные расходы на стрим на 100000 целых чисел, которые возводятся в квадрат, составят примерно в 1000 раз больше, чем супербыстрый результат, и примерно в 1000 раз меньше, чем супермедленный. Но в зависимости от множества факторов может быть и хуже. Или лучше.



На прошлогоднем Joker'е я рассматривал несколько более интересный пример замера производительности Stream API и копнул глубже, что там происходит. Ну и обязательная ссылка на JMH: он поможет не наступать на простые грабли при измерении производительности JVM-языков. Хотя, конечно, даже JMH волшебным образом все ваши проблемы не решит: думать всё равно придётся.


Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/307268/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
Stepan_Sikora

Как увеличить производительность компьютера? (видео)

Вторник, 26 Июля 2016 г. 20:21 (ссылка)
fenixslovo.com/ru/others/11783

Пожалуй, все пользователи ПК рано или поздно сталкиваются с ситуацией, когда компьютер начинает «тормозить». Более того, согласно законам Мерфи, это с...
Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
NetFact

100 способов ускорить работу компьютера, о которых должен знать каждый / Д. Макарский (2016) PDF » NetFact.Ru: Скачать бесплатно – Популярная Интернет Библиотека

Четверг, 07 Июля 2016 г. 14:39 (ссылка)
netfact.ru/computer/3019-10...6-pdf.html


100 способов ускорить работу компьютера, о которых должен знать каждый / Д. Макарский (2016) PDF




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



Вы узнаете, как увеличить объем свободного места на дисках, правильно организовать хранение документов, музыки, изображений и других файлов. Научитесь находить и удалять дубликаты файлов, неиспользуемые приложения и другую ненужную информацию, разберетесь, как работать с приложениями, которые помогут ускорить работу компьютера. Вы легко разберетесь, какие настройки операционной системы могут увеличить производительность машины, научитесь самостоятельно переустанавливать операционную систему и даже заменять детали компьютера, сможете оптимизировать работу Интернета и электронной почты.



Название: 100 способов ускорить работу компьютера, о которых должен знать каждый

Автор: Дмитрий Макарский

Издательство: Эксмо

Год: 2016

Язык: русский

Формат: PDF

Страниц: 224

Размер: 10,59 Мб



Скачать: 100 способов ускорить работу компьютера, о которых должен знать каждый / Д. Макарский (2016) PDF



Скачать | Download | TurboBit.net

http://turbobit.net/81qz6yedhtks/100_sposobov_uskorit_computer.rar.html



Скачать | Download | HitFile.net

http://www.hitfile.net/iH37eJh/100_sposobov_uskorit_computer.rar.html



Скачать | Download | Файлообменник.рф

http://файлообменник.рф/yssmbeaqebl1/100_sposobov_uskorit_computer.rar.html



Скачать | Download | BornCash.org

http://borncash.org/load/1734001089&name=100_sposobov_uskorit_computer.rar



Скачать | Download | StartFiles.org

http://startfiles.org/load/1734001089&name=100_sposobov_uskorit_computer.rar



Скачать | Download | GoldFiles.org

http://goldfiles.org/load/1734001089&name=100_sposobov_uskorit_computer.rar



Скачать | Download | File-Space.org

http://file-space.org/files/get/GFrBhN1IlJ/100-sposobov-uskorit-computer.rar.html

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Перевод] Язык Go, микросервисы и DevOps – хорошая компания?

Пятница, 24 Июня 2016 г. 15:18 (ссылка)

Привет, Хабр!



Напоминаем, что все желающие по-прежнему могут приобрести отличную книгу Сэма Ньюмена "Создание микросервисов". Поскольку наши ожидания эта тема более чем оправдала, мы продолжаем искать связанную с ней литературу и не так давно обратили внимание на книгу о программировании микросервисов на языке Go







Интересную статью с обоснованием этого подхода мы нашли в блоге Agile Maverick, и ее перевод размещаем под катом.



Приятного чтения!







Сейчас все говорят о микросервисах и DevOps. Поставьте эти словечки себе в профиль – и вас сразу начнут осаждать рекрутеры. Я побывал в Мюнхене на нескольких интересных митапах по микросервисам, и меня наиболее удивило, что эта тема пользуется наибольшим интересом в сообществах Java и Scala. Удивило потому, что Java и Scala – очень насыщенные языки, в которых есть из чего выбирать.



Когда рассуждают о микросервисах, часто говорят, что сервисы должны быть маленькими ради уменьшения сложности и зависимостей при реализации. Но чем насыщеннее язык программирования, тем разнообразнее там зависимости. Это особенно актуально в Java, где наследуется масса кода.



Я понимаю микросервисную архитектуру в более целостном ключе. Я бы сказал, что и вся экосистема должна быть проще и компактнее. Реализация – лишь одна сторона медали. Другая сторона – это время исполнения и сопутствующие фреймворки. Здесь мы подходим к теме DevOps – философии, стремящейся увязать друг с другом две эти стороны.



Виртуальная машина Java оптимизирована для работы с долгоиграющими приложениями, в ней действует одна из наиболее выверенных и затейливых систем сборки мусора. Используется в боевых условиях уже более 10 лет. Тем не менее, когда мне доводится видеть современные высокодоступные архитектуры – сразу напрашивается вопрос: а нужны ли долгоиграющие приложения для реализации абсолютного большинства существующих сервисов?



Приведу пример. Я участвовал в разработке приложения для кодировки видео, и это приложение как назло должно было работать круглосуточно с минимальными задержками. Мы думали, остановиться ли на стабильном языке программирования вроде Java или написать приложение на Go, где использовались бы имеющиеся библиотеки на C для кодирования и декодирования, однако такой проект мог обернуться утечками в памяти. Наконец, мы решили разделить приложение на различные процессы; статический бэкенд почти не изменился, поскольку передавал информацию по практически не изменившемуся протоколу, а еще у нас была функционально богатая клиентская часть, где существовал риск утечек. Обе части использовали разделяемую память. Оказалось, что вариант хороший. Поскольку Go стартует быстро, мы перезапускали клиентскую часть раз в десять секунд. Оказалось, что проблема – не в утечках памяти, а в оперативных обновлениях.



За много лет в Java сложилось много нетривиальных решений – например, фреймворк log4j для логирования. На примере контейнерных решений вроде OpenShift можно убедиться, что теперь снова принято работать с stdout и stderr. Нет необходимости внедрять изощренные решения для логирования на уровне языка. Этот пример позволяет судить, как DevOps и новые среды времени выполнения меняют правила игры.



Типичный docker-образ на Go docker имеет размер около 15 MB; сравните его с образом для JVM на Java, размер которого — около 300 MB. Разница 1 к 10. Java JVM оптимизирована под экономный расход памяти, но все равно требует примерно в 10 раз больше памяти, чем Go.



В Go не так много унаследованных фреймворков, поэтому и зависимостей обычно мало, а код зависимостей входит в состав бинарного файла. Поэтому отпадает необходимость в таких сложных инструментах как Maven. В контейнерной среде релиз нового образа необходим всякий раз, когда меняется одна из зависимостей в цепочке. А значит, на Java Java мы должны обновлять такие контейнеры достаточно часто. Хуже того, зависимости обычно запрятаны где-то глубоко.



Java и Scala – это языки для объектно-ориентированного программирования. Но при работе в сравнительно простых предметных областях такие решения кажутся мне довольно затратными. «Гибкий» аспект философии Go позволяет организовать разработку не только не хуже, но и гораздо понятнее.



Java известен своим огромным конвейером и множеством инструментов для непрерывной интеграции вроде Jenkins, которые развились вокруг этого конвейера. В Go конвейеры получаются гораздо короче, проще и быстрее – ведь мы получаем бинарные файлы, уже готовые к исполнению.



В 1990-е был настоящий бум серверов приложений Java – считалось, что они обеспечат независимость разработки от операционной системы и аппаратного обеспечения. Читая спецификацию JEE, мы также рассчитывали на простоту удаленных взаимодействий и компонент-ориентированную разработку. Когда я вижу контейнер docker, на котором работают Java-приложения, всегда вспоминаю о новой версии EJB. В принципе, стек Java не упростился, но теперь он упакован в контейнер. Такая упаковка даром не дается, поскольку добавляется еще один уровень сложности; вы с ним познакомитесь, как только попробуете отладить сеть такого docker-контейнера.



Go docker – вариант для масштабирования сервисов, но сложную среду времени исполнения он не спасает. Если у вас всего один простой сервис, то простые бинарные файлы Go можно выполнять прямо на хосте. Если же речь идет о более сложном приложении, то сервисы можно положить, например, в контейнер и запускать их в PaaS-среде вроде OpenShift. Чтобы протестировать сервис на ноутбуке разработчика, контейнер не нужен, всяческая связанная с ним магия – тоже.



Go прост, и обучаешься ему достаточно быстро. Основные концепции Go усваиваются всего за неделю-две. Поэтому новые коллеги могут с успехом изучить реализации сервисов, а разработчики или операторы могут быстрее фиксить баги, не отвлекаясь при этом от основной работы, даже если не они реализовывали эти сервисы. Автономные команды – дело хорошее, но если у вас всего трое коллег, то обеспечить круглосуточную доступность становится реально сложно.



Go, где вариантов не так много, помогает быстрее освоиться с решением тем разработчикам, которые сами его не писали. Не требуется углубляться в философию разработки. Разумеется, всегда можно реализовывать сервисы и в более простом стиле на более насыщенных языках, например, на Java или Scala, но в данном случае нужно научиться самоограничению, обсуждать все детали с командой – соответственно, микросервисная архитектура обрастает огромной документацией.



Мне кажется, Go идеально подходит для реализации микросервисов. Почему же этот язык так медленно усваивается в сообществе разработчиков?



Думаю, просто никто не любит резких изменений. Мы попробовали изменить всего одно измерение в многомерном мире программирования. Это измерение – размер сервиса. По моему опыту, изменить одно измерение еще недостаточно, чтобы пошла эволюция. Поскольку все измерения взаимосвязаны, они влияют друг на друга. Решив упростить приложение, переделав его в виде микросервисов, мы должны также упростить и исполняющую среду, и язык программирования, с которым работаем. Иначе получим лишь новую головную боль – например, придется управлять сразу множеством JVM, которые занимают кучу места в памяти и запускаются довольно медленно. Либо получим множество мелких объектно-ориентированных решений, которые будут распределенными, а значит – более сложными. Наконец, мы просто запутаемся со множеством сервисов, образующих огромное дерево зависимостей.



По-моему, не меняя всех измерений сразу, мы словно пересаживаемся с лошади на машину, но берем с собой седло и шпоры.



Давайте не просто писать микросервисы, но и адаптировать под них всю архитектуру. Go отлично для этого подходит. Если предметная область сервиса расширится, вы всегда сможете дополнительно воспользоваться Java или Scala.




Актуальность книги






































Проголосовало 24 человека. Воздержалось 9 человек.





Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.


Original source: habrahabr.ru.

https://habrahabr.ru/post/304040/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Сравнение прозводительности D и Go для веб

Суббота, 19 Июня 2016 г. 02:29 (ссылка)

Доброго времени суток, хабр!



Так как мне скоро предстоит разрабатывать веб-приложение, а писать на интерпретирумых языках как-то нет желания, тем более, что есть такие ЯП как D и Go, возникло желание сравнить их производительность при работе с веб (в сети не нашёл тестов, которые были бы свежими). Для D это vibe.d, а для Go, как я понял, не используются фреймворки. Так как Go я знаю менее чем «никак» решил не выпендриваться: тестовые приложения просто отдают страничку с некоторым текстом (ни баз данных, ни сложного роутинга, ни изображений).



Нагрузка давалась с помощью Apache Benchmark.







Приложение на D это стандартное vibe приложение, нас будут интересовать только

source/app.d
import vibe.d;

shared static this()
{
auto settings = new HTTPServerSettings;
settings.options |= HTTPServerOption.distribute; // без этой настройки vibe однопоточен
settings.port = 8101;
settings.bindAddresses = ["::1", "127.0.0.1"];

listenHTTP(settings, &index);
}

void index(HTTPServerRequest req, HTTPServerResponse res)
{
auto var = "hello habr";
res.render!("index.dt",var);
}
и
views/index.dt
html
head
body
h1 Привет, Хабр!

p D is a systems programming language with C-like syntax.

= var
сборка: dub build --build=release



В Go приложении нас интересуют соответствующие файлы

site_test_go.go
package main

import (
"html/template"
"net/http"
)

type Page struct {
Var string
}

func indexViewer(w http.ResponseWriter, r *http.Request) {
p := Page{Var:"hello habr"}
t, _ := template.ParseFiles("index.html")
t.Execute(w, p)
}

func main() {
http.HandleFunc("/", indexViewer);
http.ListenAndServe(":8100", nil);
}


и
index.html

Привет, Хабр!



Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.




{{.Var}}



сборка: go build site_test_go.go



Нагрузка: ab -c200 -n50000 http://localhost:8101/ (8100 для site_test_go)



Начнём


Apache Benchmark


Немного различный код index файлов был сделан для соответствия Document Length (видимо vibe передаёт что-то в заголовке ответа)










dlang golang
Server Software: vibe.d/0.7.28

Server Hostname: localhost

Server Port: 8101



Document Path: /

Document Length: 182 bytes



Concurrency Level: 200

Time taken for tests: 4.481 seconds

Complete requests: 50000

Failed requests: 0

Total transferred: 16450000 bytes

HTML transferred: 9100000 bytes

Requests per second: 11157.90 [#/sec] (mean)

Time per request: 17.925 [ms] (mean)

Time per request: 0.090 [ms] (mean, across all concurrent requests)

Transfer rate: 3584.91 [Kbytes/sec] received



Connection Times (ms)

min mean[+/-sd] median max

Connect: 0 8 33.2 7 1009

Processing: 2 10 3.7 9 207

Waiting: 1 8 3.5 7 205

Total: 3 18 33.4 17 1020



Percentage of the requests served within a certain time (ms)

50% 17

66% 18

75% 18

80% 18

90% 19

95% 23

98% 29

99% 30

100% 1020 (longest request)



Server Software:

Server Hostname: localhost

Server Port: 8100



Document Path: /

Document Length: 182 bytes



Concurrency Level: 200

Time taken for tests: 4.263 seconds

Complete requests: 50000

Failed requests: 0

Total transferred: 14950000 bytes

HTML transferred: 9100000 bytes

Requests per second: 11728.36 [#/sec] (mean)

Time per request: 17.053 [ms] (mean)

Time per request: 0.085 [ms] (mean, across all concurrent requests)

Transfer rate: 3424.59 [Kbytes/sec] received



Connection Times (ms)

min mean[+/-sd] median max

Connect: 0 8 14.9 7 1011

Processing: 2 9 2.3 9 211

Waiting: 1 7 2.4 7 210

Total: 10 17 15.1 16 1020



Percentage of the requests served within a certain time (ms)

50% 16

66% 17

75% 18

80% 18

90% 19

95% 20

98% 21

99% 22

100% 1020 (longest request)







Память


Замер производился с помощью gnome-system-monitor

















dlang golang
до первого запроса memory: 680.0KiB

virtual: 907.5MiB

resident: 10.2MiB

writable: 343.3MiB

shared: 9.5MiB

memory: 888.0KiB

virtual: 110.8MiB

resident: 5.2MiB

writable: 35.7MiB

shared: 4.4MiB

после запуска ab memory: 19.2MiB

virtual: 9.5GiB

resident: 28.7MiB

writable: 9.0GiB

shared: 9.6MiB

memory: 6.5MiB

virtual: 1.3GiB

resident: 12.5MiB

writable: 1.0GiB

shared: 5.9MiB





Загрузка процессора


Замер производился с помощью gnome-system-monitor










dlang golang




Версии ПО


apr-util-1.5.4-1.fc22.x86_64



DMD64 D Compiler v2.071.0

DUB version 0.9.25, built on May 22 2016

vibe 0.7.28



go version go1.5.4 linux/amd64



Выводы


Производительность инструментов практически не различается (чему я удивлён, на самом деле).

Огорчило потребление памяти у D: практически в 3 раза больше чем у Go.

Исходя из графиков загруженности процессора, можно сделать вывод: планировщик заданий в Go устроен лучше — сразу распределяет нагрузку на ядра поровну, но в среднем загрузка ЦП у D ниже.

Стоит отметить, что приложение на D компилируется дольше (для веб разработки это может быть неприятным моментом).



PS: это мой первый эксперимент с производительностью веб (вообще пока не хорошо с веб знаком), так что буду очень рад, если вы укажите на ошибки в способе измерения и/или начальных условиях =)




Original source: habrahabr.ru.

https://habrahabr.ru/post/303590/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Сравнение прозводительности D и Go для веб

Суббота, 19 Июня 2016 г. 02:29 (ссылка)

Доброго времени суток, хабр!



Так как мне скоро предстоит разрабатывать веб-приложение, а писать на интерпретирумых языках как-то нет желания, тем более, что есть такие ЯП как D и Go, возникло желание сравнить их производительность при работе с веб (в сети не нашёл тестов, которые были бы свежими). Для D это vibe.d, а для Go, как я понял, не используются фреймворки. Так как Go я знаю менее чем «никак» решил не выпендриваться: тестовые приложения просто отдают страничку с некоторым текстом (ни баз данных, ни сложного роутинга, ни изображений).



Нагрузка давалась с помощью Apache Benchmark.







Приложение на D это стандартное vibe приложение, нас будут интересовать только

source/app.d
import vibe.d;

shared static this()
{
auto settings = new HTTPServerSettings;
settings.options |= HTTPServerOption.distribute; // без этой настройки vibe однопоточен
settings.port = 8101;
settings.bindAddresses = ["::1", "127.0.0.1"];

listenHTTP(settings, &index);
}

void index(HTTPServerRequest req, HTTPServerResponse res)
{
auto var = "hello habr";
res.render!("index.dt",var);
}
и
views/index.dt
html
head
body
h1 Привет, Хабр!

p D is a systems programming language with C-like syntax.

= var
сборка: dub build --build=release



В Go приложении нас интересуют соответствующие файлы

site_test_go.go
package main

import (
"html/template"
"net/http"
)

type Page struct {
Var string
}

func indexViewer(w http.ResponseWriter, r *http.Request) {
p := Page{Var:"hello habr"}
t, _ := template.ParseFiles("index.html")
t.Execute(w, p)
}

func main() {
http.HandleFunc("/", indexViewer);
http.ListenAndServe(":8100", nil);
}


и
index.html

Привет, Хабр!



Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.




{{.Var}}



сборка: go build site_test_go.go



Нагрузка: ab -c200 -n50000 http://localhost:8101/ (8100 для site_test_go)



Начнём


Apache Benchmark


Немного различный код index файлов был сделан для соответствия Document Length (видимо vibe передаёт что-то в заголовке ответа)










dlang golang
Server Software: vibe.d/0.7.28

Server Hostname: localhost

Server Port: 8101



Document Path: /

Document Length: 182 bytes



Concurrency Level: 200

Time taken for tests: 4.481 seconds

Complete requests: 50000

Failed requests: 0

Total transferred: 16450000 bytes

HTML transferred: 9100000 bytes

Requests per second: 11157.90 [#/sec] (mean)

Time per request: 17.925 [ms] (mean)

Time per request: 0.090 [ms] (mean, across all concurrent requests)

Transfer rate: 3584.91 [Kbytes/sec] received



Connection Times (ms)

min mean[+/-sd] median max

Connect: 0 8 33.2 7 1009

Processing: 2 10 3.7 9 207

Waiting: 1 8 3.5 7 205

Total: 3 18 33.4 17 1020



Percentage of the requests served within a certain time (ms)

50% 17

66% 18

75% 18

80% 18

90% 19

95% 23

98% 29

99% 30

100% 1020 (longest request)



Server Software:

Server Hostname: localhost

Server Port: 8100



Document Path: /

Document Length: 182 bytes



Concurrency Level: 200

Time taken for tests: 4.263 seconds

Complete requests: 50000

Failed requests: 0

Total transferred: 14950000 bytes

HTML transferred: 9100000 bytes

Requests per second: 11728.36 [#/sec] (mean)

Time per request: 17.053 [ms] (mean)

Time per request: 0.085 [ms] (mean, across all concurrent requests)

Transfer rate: 3424.59 [Kbytes/sec] received



Connection Times (ms)

min mean[+/-sd] median max

Connect: 0 8 14.9 7 1011

Processing: 2 9 2.3 9 211

Waiting: 1 7 2.4 7 210

Total: 10 17 15.1 16 1020



Percentage of the requests served within a certain time (ms)

50% 16

66% 17

75% 18

80% 18

90% 19

95% 20

98% 21

99% 22

100% 1020 (longest request)







Память


Замер производился с помощью gnome-system-monitor

















dlang golang
до первого запроса memory: 680.0KiB

virtual: 907.5MiB

resident: 10.2MiB

writable: 343.3MiB

shared: 9.5MiB

memory: 888.0KiB

virtual: 110.8MiB

resident: 5.2MiB

writable: 35.7MiB

shared: 4.4MiB

после запуска ab memory: 19.2MiB

virtual: 9.5GiB

resident: 28.7MiB

writable: 9.0GiB

shared: 9.6MiB

memory: 6.5MiB

virtual: 1.3GiB

resident: 12.5MiB

writable: 1.0GiB

shared: 5.9MiB





Загрузка процессора


Замер производился с помощью gnome-system-monitor










dlang golang




Версии ПО


apr-util-1.5.4-1.fc22.x86_64



DMD64 D Compiler v2.071.0

DUB version 0.9.25, built on May 22 2016

vibe 0.7.28



go version go1.5.4 linux/amd64



Выводы


Производительность инструментов практически не различается (чему я удивлён, на самом деле).

Огорчило потребление памяти у D: практически в 3 раза больше чем у Go.

Исходя из графиков загруженности процессора, можно сделать вывод: планировщик заданий в Go устроен лучше — сразу распределяет нагрузку на ядра поровну, но в среднем загрузка ЦП у D ниже.

Стоит отметить, что приложение на D компилируется дольше (для веб разработки это может быть неприятным моментом).



PS: это мой первый эксперимент с производительностью веб (вообще пока не хорошо с веб знаком), так что буду очень рад, если вы укажите на ошибки в способе измерения и/или начальных условиях =)
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/303590/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Перевод] Производительность без цикла событий

Пятница, 06 Мая 2016 г. 12:58 (ссылка)





Эта статья основана на материалах презентации, которую я представил в этом году на конференции OSCON. Я отредактировал текст, чтобы он был более лаконичным, а заодно учёл ту обратную связь, что я получил после своего выступления.



Про Go часто говорят, что он хорош для серверов: здесь есть статические бинарники (static binaries), развитый concurrency, высокая производительность. В этой статье мы поговорим о двух последних пунктах: о том, как язык и среда выполнения (runtime) ненавязчиво позволяют Go-программистам создавать легко масштабируемые серверы и не беспокоиться из-за управления потоками (thread) или блокирующих операций ввода/вывода.



Аргумент в пользу производительности языка



Но прежде чем перейти к техническим деталям, я хочу сделать два утверждения, характеризующие рыночный сегмент языка Go.



Закон Мура







Согласно часто ошибочно цитируемому закону Мура, количество транзисторов на единице площади кристалла удваивается примерно каждые 18 месяцев. Однако рабочие частоты, зависящие от совершенно других свойств, перестали расти уже с десяток лет назад, с выходом Pentium 4, и с тех пор понемногу снижаются.



От пространственных ограничений к энергетическим





Sun Enterprise e450 — размером примерно с барный холодильник и потребляет примерно столько же электричества



Это Sun e450. Когда моя карьера только начиналась, эти компьютеры были рабочими лошадками индустрии. Они были массивны. Если поставить один на другой три штуки, то они займут целую 19-дюймовую стойку. При этом каждый потреблял всего лишь около 500 Вт.



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



Эффект от энергетических ограничений проявился:




  • на макроуровне — мало кто может обеспечить работу стойки с 1200-ваттными 1U-серверами;

  • на микроуровне — все эти сотни ватт рассеиваются на маленьком кремниевом кристалле в виде тепла.





С чем связан такой рост энергопотребления?





КМОП-инвертор



Это инвертор, один из простейших логических вентилей. Если на вход А подаётся высокий уровень, то на выход Q подаётся низкий, и наоборот. Вся современная потребительская электроника построена на КМОП-логике (КМОП — комплементарная структура металл-оксид-полупроводник). Ключевое слово здесь «комплементарная». Каждый логический элемент внутри процессора реализован с помощью пары транзисторов: когда один включается, другой выключается.



Когда на выходе инвертора высокий или низкий уровень, то от Vss к Vdd ток не течёт. Но во время переключений есть короткие периоды, когда оба транзистора проводят ток, создавая короткое замыкание. А потребление энергии — следовательно, и рассеяние тепла — прямо пропорционально количеству переключений в секунду, то есть тактовой частоте процессора.



Потребление энергии КМОП связано не только с короткими замыканиями при переключениях. Свой вклад вносит зарядка выходной ёмкости затвора. К тому же ток утечки затвора возрастает с уменьшением размера транзистора. Подробнее об этом можно почитать по ссылкам: раз, два.



Уменьшение размеров элементов процессора направлено в первую очередь на снижение энергопотребления. Это нужно не только ради экологии, основная цель — удерживать тепловыделение на приемлемом уровне, чтобы не допускать повреждения процессоров.



Вопреки снижению тактовых частот и энергопотребления, рост производительности в основном связан с улучшениями микроархитектуры и эзотерическими векторными инструкциями, которые не особо полезны для общих вычислений. В результате каждая микроархитектура (5-летний цикл) превосходит предыдущее поколение не более чем на 10%, а в последнее время едва дотягивают до 4–6%.



«Халява кончилась»



Надеюсь, теперь вы понимаете, что железо не становится быстрее. Если вам важны производительность и масштаб, то вы согласитесь со мной, что решать проблемы силами одного лишь оборудования уже не удастся, по крайней мере в общепринятом смысле. Как сказано у Герба Саттера — «Халява кончилась».



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



Аргумент в пользу параллельного языка программирования



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







Одновременная многопоточность, или Hyper-Threading, как это называет Intel, позволяет одному ядру параллельно выполнять несколько потоков инструкций благодаря добавлению небольшой аппаратной обвязки. Intel применяет технологию Hyper-Threading для искусственного сегментирования рынка процессоров, в то время как Oracle и Fujitsu активнее используют её в своей продукции, доводя количество аппаратных потоков выполнения до 8 или 16 на каждое ядро.



Двухпроцессорные материнские платы появились в конце 1990-х, когда вышел Pentium Pro. Сегодня это стандартное решение, большинство серверов поддерживают двух- или четырёхпроцессорные конфигурации. Увеличение плотности транзисторов позволило даже размещать несколько ядер на одном кристалле. Двухъядерные процессоры обосновались в мобильном сегменте, четырёхъядерные — в настольном, ещё больше ядер в серверном сегменте. По сути, сегодня количество ядер в сервере ограничено лишь вашим бюджетом.



И чтобы воспользоваться преимуществами всех этих ядер, вам нужен язык программирования с развитым параллелизмом.



Процессы, потоки выполнения и горутины



В основе параллелизма Go лежат так называемые горутины (goroutine). Давайте немного отвлечёмся и вспомним историю их возникновения.



Процессы



На заре времён, при пакетной модели обработки, компьютеры могли выполнять в один отрезок времени только одну задачу. Стремление к более интерактивным формам вычисления привело в 1960-х к разработке многопроцессных операционных систем, или систем, работающих в режиме разделения времени (time sharing). В 1970-х эта идея проникла в серверы, FTP, Telnet, rlogin, а позднее и в CERN httpd Тима Бернерса-Ли. Обработка всех входящих сетевых соединений сопровождалась порождением (forking) дочерних процессов.



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



Переключение контекста







У переключения контекста есть три основные статьи расходов:




  • Ядро должно сохранять содержимое всех регистров процессора сначала для одного процесса, потом восстанавливать значения для другого. Поскольку переключение между процессами может произойти в любой момент, ОС должна хранить содержимое всех регистров, потому что она не знает, какие из них сейчас используются. Конечно, это крайне упрощённое описание. В ряде случае ОС может избегать сохранения и восстановления часто используемых архитектурных регистров, запуская процесс в таком режиме, при котором доступ к floating-point или MMX/SSE-регистрам вызовет прерывание (fault). В таких ситуациях ядро понимает, что процесс будет использовать эти регистры и их нужно сохранять и восстанавливать.

  • Ядро должно очистить кеш соответствия виртуальных адресов памяти физическим (TLB, буфер ассоциативной трансляции). В некоторых процессорах используется так называемый tagged TLB. В этом случае ОС может приказывать процессору присваивать конкретным записям буфера идентификаторы, полученные из ID процесса, а не обрабатывать каждую запись как глобальную. Это позволяет избежать удаления записей из кеша при каждом переключении процессов, если нужный процесс быстро возвращается в то же ядро.

  • Накладные расходы ОС на переключение контекста, а также накладные расходы функции-планировщика при выборе следующего процесса для обеспечения процессора работой.



Все эти расходы относительно постоянны с точки зрения оборудования, но от объёма выполненной между переключениями работы зависит, оправданны ли эти расходы. Слишком частые переключения сводят на нет количество выполненной работы между ними.



Потоки



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



Тем не менее стоимость переключения контекста у потоков достаточно высока. Необходимо сохранять немало информации о состоянии. Горутины фактически являются дальнейшим развитием идеи потоков.



Горутины



Вместо того чтобы возлагать на ядро обязанности по управлению временем их выполнения, горутины используют кооперативную многозадачность. Переключение между ними происходит только в чётко определённые моменты, при совершении явных вызовов runtime-планировщиком Go. Основные ситуации, в которых горутина вернёт управление планировщику:




  • отправка и приём из канала, если они приведут к блокировке;

  • вызов инструкции go func(...), хотя нет гарантии, что переключение на новую горутину произойдёт немедленно;

  • возникновение блокирующих системных вызовов, например операций с файлами или сетевых операций;

  • после остановки выполнения для прогона цикла сборки мусора.





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



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



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



Всё это приводит к тому, что на каждый процесс Go приходится относительно немного потоков ОС. Среда выполнения Go заботится о том, чтобы назначать готовые к исполнению горутины в свободные потоки ОС.



Управление стеком



В предыдущем разделе мы говорили о том, как горутины снижают накладные расходы на управление многочисленными — иногда достигающими сотен тысяч — параллельными потоками. У истории с горутинами есть и другая сторона — управление стеком.



Адресное пространство процесса







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







Если куча и стек перепишут друг друга, это будет катастрофой. Поэтому ОС выделяет между ними буферную зону недоступной памяти. Она называется сторожевой страницей (guard page) и фактически ограничивает размер стека процесса, обычно в пределах нескольких мегабайтов.



Стеки потока







Потоки используют одно общее адресное пространство. Каждому потоку нужен свой стек с отдельной сторожевой страницей. Поскольку трудно спрогнозировать потребности каждого из потоков, под каждый стек приходится резервировать большой объём памяти. Остаётся только надеяться, что этого будет достаточно и мы никогда не попадём на сторожевую страницу.



Недостаток этого подхода в том, что с увеличением количества потоков в программе уменьшается объём доступного адресного пространства.



Управление стеком горутин



В ранней версии модели процессов программисты могли считать, что куча и стек достаточно велики, чтобы не переживать об этом. Однако моделирование подзадач становилось сложным и дорогим.



С внедрением потоков ситуация немного улучшилась. Но программисты должны угадывать наилучший размер стека. Будет слишком мало — программа вылетит, слишком много — закончится виртуальное адресное пространство.



Мы уже видели, что планировщик Go выполняет большое количество горутин внутри небольшого количества потоков. А что насчёт требований к размеру стеков этих горутин?



Рост стека горутин







Изначально у каждой горутины есть маленький стек, выделенный из кучи. Его размер менялся в зависимости от версии языка, в Go 1.5 по умолчанию выделяется по два килобайта. Вместо использования сторожевой страницы компилятор Go вставляет проверку, которая является частью вызова каждой функции.



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



Благодаря этому механизму можно делать очень маленькие начальные стеки горутин. В свою очередь, это позволяет рассматривать горутины как дешёвый ресурс. Предусмотрен также механизм уменьшения размера стека, если достаточная его часть остаётся неиспользуемой. Процедура уменьшения выполняется в ходе сборки мусора.



Интегрированный сетевой поллер (network poller)



В 2002 году Дэн Кегель (Dan Kegel) опубликовал статью «Проблема c10k». Говоря простым языком, она была посвящена написанию серверного ПО, способного обрабатывать не менее 10 000 ТСР-сессий на недорогом оборудовании, доступном в то время. После написания этой статьи возникло расхожее мнение, что высокопроизводительные серверы нуждаются в нативных потоках. Позднее их место заняли циклы событий (event loops).



С точки зрения стоимости планирования и потребления памяти потоки имеют высокие издержки. У циклов событий ситуация получше, но у них есть собственные требования в связи со сложным, основанным на callback'ах принципом работы.



Go взял всё самое лучшее из этих двух подходов.



Ответ Go на проблему c10k



Системные вызовы в Go обычно являются блокирующими операциями. К таким вызовам относятся в том числе чтение и запись в файловые дескрипторы. Планировщик Go обрабатывает эти случаи, находя свободный поток или создавая новый, чтобы можно было продолжить обслуживать горутины, пока заблокирован исходный поток. В жизни это хорошо подходит для файловых операций, потому что небольшое количество блокирующих потоков может быстро исчерпать пропускную способность локального ввода/вывода.



Однако с сетевыми сокетами всё не так просто. В каждый момент времени почти все ваши горутины будут заблокированы в ожидании выполнения сетевых операций ввода/вывода. В случае примитивной реализации потребуется делать по одному потоку на каждую такую горутину, и все они окажутся заблокированы в ожидании сетевого трафика. Справиться с этой ситуацией помогает интегрированный сетевой поллер Go, обеспечивающий взаимодействие между средой выполнения языка и пакетом net.



В старых версиях Go сетевым поллером выступала одна горутина, которая с помощью kqueue или epoll запрашивала уведомления о готовности. Такая горутина общалась с ожидающими горутинами через канал. Это позволяло избегать выделения потока для каждого системного вызова, но приходилось использовать обобщённый механизм пробуждения посредством записи в канал. Это означает, что планировщик не был осведомлён об источнике или важности пробуждения.



В текущих версиях Go сетевой поллер интегрирован в саму среду выполнения (в runtime). Поскольку среда знает, какая горутина ожидает готовности сокета, то она может по прибытии пакета продолжить выполнять горутину на том же ядре процессора, уменьшая задержку и увеличивая пропускную способность системы.



Горутины, управление стеком и интегрированный сетевой поллер



Подведём итоги. Горутины являются мощной абстракцией, благодаря которой вы можете не беспокоиться о пулах потоков или циклах событий.



Стек горутины растёт по мере надобности, и вам не придётся волноваться об изменении размера стеков или пулов потоков.



Интегрированный сетевой поллер позволяет избегать использования витиеватых схем на основе callback'ов. При этом задействуется наиболее эффективная логика выполнения операций ввода/вывода, какую только можно получить от ОС.



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



И все эти возможности совершенно прозрачны для Go-программиста.

Original source: habrahabr.ru.

https://habrahabr.ru/post/283038/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Перевод] C/C++: как измерять процессорное время

Суббота, 24 Апреля 2016 г. 01:02 (ссылка)

image

КДПВ



От переводчика:

Большинство моих знакомых для измерения времени в разного вида бенчмарках в С++ используют chrono или, в особо запущенных случаях, ctime. Но для бенчмаркинга гораздо полезнее замерять процессорное время. Недавно я наткнулся на статью о кроссплатформенном замере процессорного времени и решил поделиться ею тут, возможно несколько увеличив качество местных бенчмарков.



P.S. Когда в статье написано "сегодня" или "сейчас", имеется ввиду "на момент выхода статьи", то есть, если я не ошибаюсь, март 2012. Ни я, ни автор не гарантируем, что это до сих пор так.

P.P.S. На момент публикации оригинал недоступен, но хранится в кэше Яндекса



Функции API, позволяющие получить процессорное время, использованное процессом, отличаются в разных операционных системах: Windows, Linux, OSX, BSD, Solaris, а также прочих UNIX-подобных ОС. Эта статья предоставляет кросс-платформенную функцию, получающую процессорное время процесса и объясняет, какие функции поддерживает каждая ОС.



Как получить процессорное время



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



Разные инструменты, такие как ps в POSIX, Activity Monitor в OSX и Task Manager в Windows показывают процессорное время, используемое процессами, но часто бывает полезным отслеживать его прямо из самого процесса. Это особенно полезно во время бенчмаркинга алгоритмов или маленькой части сложной программы. Несмотря на то, что все ОС предоставляют API для получения процессорного времени, в каждой из них есть свои тонкости.



Код



Функция getCPUTime( ), представленная ниже, работает на большинстве ОС (просто скопируйте код или скачайте файл getCPUTime.c). Там, где это нужно, слинкуйтесь с librt, чтобы получить POSIX-таймеры (например, AIX, BSD, Cygwin, HP-UX, Linux и Solaris, но не OSX). В противном случае, достаточно стандартных библиотек.



Далее мы подробно обсудим все функции, тонкости и причины, по которым в коде столько #ifdef'ов.



getCPUTime.c
/*
* Author: David Robert Nadeau
* Site: http://NadeauSoftware.com/
* License: Creative Commons Attribution 3.0 Unported License
* http://creativecommons.org/licenses/by/3.0/deed.en_US
*/
#if defined(_WIN32)
#include

#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
#include
#include resource.h>
#include times.h>
#include

#else
#error "Unable to define getCPUTime( ) for an unknown OS."
#endif

/**
* Returns the amount of CPU time used by the current process,
* in seconds, or -1.0 if an error occurred.
*/
double getCPUTime( )
{
#if defined(_WIN32)
/* Windows -------------------------------------------------- */
FILETIME createTime;
FILETIME exitTime;
FILETIME kernelTime;
FILETIME userTime;
if ( GetProcessTimes( GetCurrentProcess( ),
&createTime, &exitTime, &kernelTime, &userTime ) != -1 )
{
SYSTEMTIME userSystemTime;
if ( FileTimeToSystemTime( &userTime, &userSystemTime ) != -1 )
return (double)userSystemTime.wHour * 3600.0 +
(double)userSystemTime.wMinute * 60.0 +
(double)userSystemTime.wSecond +
(double)userSystemTime.wMilliseconds / 1000.0;
}

#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
/* AIX, BSD, Cygwin, HP-UX, Linux, OSX, and Solaris --------- */

#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0)
/* Prefer high-res POSIX timers, when available. */
{
clockid_t id;
struct timespec ts;
#if _POSIX_CPUTIME > 0
/* Clock ids vary by OS. Query the id, if possible. */
if ( clock_getcpuclockid( 0, &id ) == -1 )
#endif
#if defined(CLOCK_PROCESS_CPUTIME_ID)
/* Use known clock id for AIX, Linux, or Solaris. */
id = CLOCK_PROCESS_CPUTIME_ID;
#elif defined(CLOCK_VIRTUAL)
/* Use known clock id for BSD or HP-UX. */
id = CLOCK_VIRTUAL;
#else
id = (clockid_t)-1;
#endif
if ( id != (clockid_t)-1 && clock_gettime( id, &ts ) != -1 )
return (double)ts.tv_sec +
(double)ts.tv_nsec / 1000000000.0;
}
#endif

#if defined(RUSAGE_SELF)
{
struct rusage rusage;
if ( getrusage( RUSAGE_SELF, &rusage ) != -1 )
return (double)rusage.ru_utime.tv_sec +
(double)rusage.ru_utime.tv_usec / 1000000.0;
}
#endif

#if defined(_SC_CLK_TCK)
{
const double ticks = (double)sysconf( _SC_CLK_TCK );
struct tms tms;
if ( times( &tms ) != (clock_t)-1 )
return (double)tms.tms_utime / ticks;
}
#endif

#if defined(CLOCKS_PER_SEC)
{
clock_t cl = clock( );
if ( cl != (clock_t)-1 )
return (double)cl / (double)CLOCKS_PER_SEC;
}
#endif

#endif

return -1; /* Failed. */
}


Использование



Чтобы замерить процессорное время алгоритма, вызовите getCPUTime( ) до и после запуска алгоритма, и выведите разницу. Не стоит предполагать, что значение, возвращенное при единичном вызове функции, несет какой-то смысл.



double startTime, endTime;

startTime = getCPUTime( );
...
endTime = getCPUTime( );

fprintf( stderr, "CPU time used = %lf\n", (endTime - startTime) );


Обсуждение



Каждая ОС предоставляет один или несколько способов получить процессорное время. Однако некоторые способы точнее остальных.








































































OS clock clock_gettime GetProcessTimes getrusage times
AIX yes yes yes yes
BSD yes yes yes yes
HP-UX yes yes yes yes
Linux yes yes yes yes
OSX yes yes yes
Solaris yes yes yes yes
Windows yes


Каждый из этих способов подробно освещен ниже.



GetProcessTimes( )



На Windows и Cygwin (UNIX-подобная среда и интерфейс командной строки для Windows), функция GetProcessTimes( ) заполняет структуру FILETIME процессорным временем, использованным процессом, а функция FileTimeToSystemTime( ) конвертирует структуру FILETIME в структуру SYSTEMTIME, содержащую пригодное для использования значение времени.



typedef struct _SYSTEMTIME
{
WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME;


Доступность GetProcessTimes( ): Cygwin, Windows XP и более поздние версии.



Получение процессорного времени:



#include 
...

FILETIME createTime;
FILETIME exitTime;
FILETIME kernelTime;
FILETIME userTime;
if ( GetProcessTimes( GetCurrentProcess( ),
&createTime, &exitTime, &kernelTime, &userTime ) != -1 )
{
SYSTEMTIME userSystemTime;
if ( FileTimeToSystemTime( &userTime, &userSystemTime ) != -1 )
return (double)userSystemTime.wHour * 3600.0 +
(double)userSystemTime.wMinute * 60.0 +
(double)userSystemTime.wSecond +
(double)userSystemTime.wMilliseconds / 1000.0;
}


clock_gettme( )



На большинстве POSIX-совместимых ОС, clock_gettime( ) (смотри мануалы к AIX, BSD, HP-UX, Linux и Solaris) предоставляет самое точное значение процессорного времени. Первый аргумент функции выбирает "clock id", а второй это структура timespec, заполняемая использованным процессорным временем в секундах и наносекундах. Для большинства ОС, программа должна быть слинкована с librt.



Однако, есть несколько тонкостей, затрудняющих использование этой функции в кросс-платформенном коде:




  • Функция является опциональной частью стандарта POSIX и доступна только если _POSIX_TIMERS определен в значением больше 0. На сегодняшний день, AIX, BSD, HP-UX, Linux и Solaris поддерживают эту функцию, но OSX не поддерживает.

  • Структура timespec, заполняемая функцией clock_gettime( ) может хранить время в наносекундах, но точность часов отличается в разных ОС и на разных системах. Функция clock_getres( ) возвращает точность часов, если она вам нужна. Эта функция, опять-таки, является опциональной частью стандарта POSIX, доступной только если _POSIX_TIMERS больше нуля. На данный момент, AIX, BSD, HP-UX, Linux и Solaris предоставляют эту функцию, но в Solaris она не работает.

  • стандарт POSIX определяет имена нескольких стандартных значений "clock id", включая CLOCK_PROCESS_CPUTIME_ID, чтобы получить процессорное время процесса. Тем не менее, сегодня BSD и HP-UX не имеют этого id, и взамен определяют собственный id CLOCK_VIRTUAL для процессорного времени. Чтобы запутать все ещё больше, Solaris определяет оба этих, но использует CLOCK_VIRTUAL для процессорного времени потока, а не процесса.
































ОС Какой id использовать
AIX CLOCK_PROCESS_CPUTIME_ID
BSD CLOCK_VIRTUAL
HP-UX CLOCK_VIRTUAL
Linux CLOCK_PROCESS_CPUTIME_ID
Solaris CLOCK_PROCESS_CPUTIME_ID



  • Вместо того, чтобы использовать одну из констант, объявленных выше, функция clock_getcpuclockid( ) возвращает таймер для выбранного процесса. Использование процесса 0 позволяет получить процессорное время текущего процесса. Однако, это ещё одна опциональная часть стандарта POSIX и доступна только если _POSIX_CPUTIME больше 0. На сегодняшний день, только AIX и Linux предоставляют эту функцию, но линуксовские include-файлы не определяют _POSIX_CPUTIME и функция возвращает ненадёжные и несовместимые с POSIX результаты.

  • Функция clock_gettime( ) может быть реализована с помощью регистра времени процессора. На многопроцессорных системах, у отдельных процессоров может быть несколько разное восприятие времени, из-за чего функция может возвращать неверные значения, если процесс передавался от процессора процессору. На Linux, и только на Linux, это может быть обнаружено, если clock_getcpuclockid( ) возвращает не-POSIX ошибку и устанавливает errno в ENOENT. Однако, как замечено выше, на Linux clock_getcpuclockid( ) ненадежен.



На практике из-за всех этих тонкостей, использование clock_gettime( ) требует много проверок с помощью #ifdef и возможность переключиться на другую функцию, если она не срабатывает.



Доступность clock_gettime( ): AIX, BSD, Cygwin, HP-UX, Linux и Solaris. Но clock id на BSD и HP-UX нестандартные.



Доступность clock_getres( ): AIX, BSD, Cygwin, HP-UX и Linux, но не работает Solaris.



Доступность clock_getcpuclockid( ): AIX и Cygwin, не недостоверна на Linux.



Получение процессорного времени:



#include 
#include
...

#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0)
clockid_t id;
struct timespec ts;
#if _POSIX_CPUTIME > 0
/* Clock ids vary by OS. Query the id, if possible. */
if ( clock_getcpuclockid( 0, &id ) == -1 )
#endif

#if defined(CLOCK_PROCESS_CPUTIME_ID)
/* Use known clock id for AIX, Linux, or Solaris. */
id = CLOCK_PROCESS_CPUTIME_ID;
#elif defined(CLOCK_VIRTUAL)
/* Use known clock id for BSD or HP-UX. */
id = CLOCK_VIRTUAL;
#else
id = (clockid_t)-1;
#endif
if ( id != (clockid_t)-1 && clock_gettime( id, &ts ) != -1 )
return (double)ts.tv_sec +
(double)ts.tv_nsec / 1000000000.0;
#endif


getrusage( )



На всех UNIX-подобных ОС, функция getrusage( ) это самый надежный способ получить процессорное время, использованное текущим процессом. Функция заполняет структуру rusage временем в секундах и микросекундах. Поле ru_utime содержит время проведенное в user mode, а поле ru_stime — в system mode от имени процесса.



Внимание: Некоторые ОС, до широкого распространения поддержки 64-бит, определяли функцию getrusage( ), возвращающую 32-битное значение, и функцию getrusage64( ), возвращающую 64-битное значение. Сегодня, getrusage( ) возвращает 64-битное значение, аgetrusage64( ) устарело.



Доступность getrusage( ): AIX, BSD, Cygwin, HP-UX, Linux, OSX, and Solaris.



Получение процессорного времени:



#include resource.h>
#include times.h>
...

struct rusage rusage;
if ( getrusage( RUSAGE_SELF, &rusage ) != -1 )
return (double)rusage.ru_utime.tv_sec +
(double)rusage.ru_utime.tv_usec / 1000000.0;


times( )



На всех UNIX-подобных ОС, устаревшая функция times( ) заполняет структуру tms с процессорным временем в тиках, а функция sysconf( ) возвращает количество тиков в секунду. Поле tms_utime содержит время, проведенное в user mode, а поле tms_stime — в system mode от имени процесса.



Внимание: Более старый аргумент функции sysconf( ) CLK_TCK устарел и может не поддерживаться в некоторых ОС. Если он доступен, функция sysconf( ) обычно не работает при его использовании. Используйте _SC_CLK_TCK вместо него.



Доступность times( ): AIX, BSD, Cygwin, HP-UX, Linux, OSX и Solaris.



Получение процессорного времени:



#include 
#include times.h>
...

const double ticks = (double)sysconf( _SC_CLK_TCK );
struct tms tms;
if ( times( &tms ) != (clock_t)-1 )
return (double)tms.tms_utime / ticks;


clock( )



На всех UNIX-подобных ОС, очень старая функция clock( ) возвращает процессорное время процесса в тиках, а макрос CLOCKS_PER_SEC количество тиков в секунду.



Заметка: Возвращенное процессорное время включает в себя время проведенное в user mode И в system mode от имени процесса.



Внимание: Хотя изначально CLOCKS_PER_SEC должен был возвращать значение, зависящее от процессора, стандарты C ISO C89 и C99, Single UNIX Specification и стандарт POSIX требуют, чтобы CLOCKS_PER_SEC имел фиксированное значение 1,000,000, что ограничивает точность функции микросекундами. Большинство ОС соответствует этим стандартам, но FreeBSD, Cygwin и старые версии OSX используют нестандартные значения.



Внимание: На AIX и Solaris, функция clock( ) включает процессорное время текущего процесса И и любого завершенного дочернего процесса для которого родитель выполнил одну из функций wait( ), system( ) или pclose( ).



Внимание: В Windows, функция clock( ) поддерживается, но возвращает не процессорное, а реальное время.



Доступность clock( ): AIX, BSD, Cygwin, HP-UX, Linux, OSX и Solaris.



Получение процессорного времени:



#include 
...

clock_t cl = clock( );
if ( cl != (clock_t)-1 )
return (double)cl / (double)CLOCKS_PER_SEC;


Другие подходы



Существуют и другие ОС-специфичные способы получить процессорное время. На Linux, Solarisи некоторых BSD, можно парсить /proc/[pid]/stat, чтобы получить статистику процесса. На OSX, приватная функция API proc_pidtaskinfo( ) в libproc возвращает информацию о процессе. Также существуют открытые библиотеки, такие как libproc, procps и Sigar.



На UNIX существует несколько утилит позволяющих отобразить процессорное время процесса, включая ps, top, mpstat и другие. Можно также использовать утилиту time, чтобы отобразить время, потраченное на команду.



На Windows, можно использовать диспетчер задач, чтобы мониторить использование CPU.



На OSX, можно использовать Activity Monitor, чтобы мониторить использование CPU. Утилита для профайлинга Instruments поставляемая в комплекте с Xcode может мониторить использование CPU, а также много других вещей.



Downloads




  • getCPUTime.c реализует выше описанную функцию на C. Скомпилируйте её любым компилятором C и слинкуйте с librt, на системах где она доступна. Код лицензирован под Creative Commons Attribution 3.0 Unported License.



Смотри также



Связанные статьи на NadeauSoftware.com





Статьи в интернете




  • Процессорное время на википедии объясняет, что такое процессорное время.

  • CPU Time Inquiry на GNU.org объясняет как использовать древнюю функцию clock( ).

  • Determine CPU usage of current process (C++ and C#) предоставляет код и объяснения для получения процессорного времени и другой статистики на Windows.

  • Posix Options на Kernel.org объясняет опциональные фичи и константы POSIX, включая _POSIX_TIMERS и _POSIX_CPUTIME.







Как вы замеряете время в своих бенчмарках?


























































Проголосовало 13 человек. Воздержалось 12 человек.




Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.





Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/282301/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Из песочницы] Увеличение производительности Magento

Среда, 20 Апреля 2016 г. 13:03 (ссылка)

…или правильная работа с коллекциями.



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



В этой статье рассказано о Magento 1.*, но описанное так же подходит и для Magento 2.*.



Практически на каждом проекте, где есть проблемы с производительностью, можно встретить что-то вроде такого:



$temp = array();
$collection = Mage::getModel('catalog/product')->getCollection()->addAttributeToSelect('*');
foreach ($collection as $product) {
$product = $product->load($product->getId());
$temp[] = $product->getSku();
}
Неправильно



вместо



$temp = array();
$collection = Mage::getModel('catalog/product')->getCollection()->addAttributeToSelect('sku');
foreach ($collection as $product) {
$temp[] = $product->getSku();
}
Правильно



Причины такого очень просты:




  1. После загрузки нет необходимых атрибутов

  2. Так делают «программисты» в интернете

  3. Загрузка лишних атрибутов по принципу «хуже не будет»



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




  1. Eav/Flat таблицы

  2. Cache

  3. Правильная работа с коллекциями



И конечно же выводы.






EAV/Flat таблицы



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



В Magento к EAV сущностям относятся: продукты, категории, кастомеры и кастомер адреса. Сами же атрибуты хранятся в eav_attribute таблице.



Всего типов значений атрибутов в Magento 5: text, varchar, int, decimal и datetime. Есть еще 1 тип – static, он отличается от остальных 5ти тем, что находится в таблице с сущностью.



В таблице атрибутов указано в какой таблице или какого типа является тот или иной атрибут и Magento уже знает куда его писать и откуда читать.



Такое хранение значений позволяет иметь достаточно просто реализуемые атрибут сеты ( когда каждая сущность может иметь свой атрибут или не иметь его вовсе), добавление нового атрибута это всего лишь еще 1 строчка в БД. Добавил новое значение для 1 атрибута для другого стора – новая строчка в таблице значений этого атрибута.



Как это хранится в БД
Entity:

Product – catalog_product_entity,

Category – catalog_category_entity,

Customer – customer_entity,

Customer address – customer_address_entity



Attribute:

eav_attribute

catalog_eav_attribute

customer_eav_attribute



Value:

*_text

*_varchar

*_int

*_decimal

*_datetime



Flat — это привычный нам всем подход, где все лежит в 1 месте и никакие дополнительные таблицы нам не нужны, чтобы получить товар и все его атрибуты без лишней работы – SELECT * FROM табличка WHERE id = какой то ид и все.



Из EAV сущностей, Flat представление можно использовать только для категорий и для товаров.



Как это хранится в БД
Product:

catalog_product_flat_1 // *_N store_view

Category:

catalog_category_flat_1 // *_N store_view



Для того, чтобы включить атрибут во Flat таблицу и вообще включить использование Flat таблиц необходимо проделать следующее
В админке Catalog > Attributes > Manage attributes



Magento добавит атрибут во Flat таблицу если у атрибута выставлено 1 из ниже указанных значений.







В админке System > Configuration > Catalog



Magento будет использовать Flat таблицы для сущностей указанных ниже.







Обратите внимание на следующие факты:




  1. Flat таблицы используются ТОЛЬКО на страницах категорий, списке продуктов в составе Group product, да и вообще везде, где используется коллекция. Они не используются на странице товаров, в админке, при использовании метода load у модели.

  2. После включения Flat таблиц необходимо произвести реиндексацию, иначе Magento будет и дальше использовать только EAV таблицы

  3. После включения Flat таблиц Magento все равно продолжает использовать EAV, но так же начинает копировать изменения во Flat таблицу при сохранении изменений



Зачем же все вот это надо и почему бы не использовать везде Flat подход? Посмотрите на сводную таблицу плюсов и минусов
EAV:

+ Более гибкая система чем Flat

+ При добавлении нового атрибута нет необходимости реиндексировать данные

+ Практически не ограниченное количество атрибутов

+ Всегда доступны все атрибуты

+ Статик атрибуты (sku, created_at, updated_at) всегда присутствуют в выборке, даже если их не указывать специально

— Fatal error: Call to a member function getBackend() при выборке/фильтрации по не существующему атрибуту

— Производительность



Flat:

+ Производительность

+ В выборку/фильтрацию могут быть применены только существующие атрибуты, которые добавлены во Flat таблицу

— Ограничение на размер строки (до 65,535 байт, т.е. 85 varchar 255) и количеству столбцов (InnoDB до 1000, некоторые до 4096)

— Используется только при работе с коллекциям (при загрузке всегда используется EAV)

— Результат отличается от выдачи запроса при EAV (отсутствуют статик атрибуты)

— После включения требуется реиндексация, в противном случае будут использованы EAV таблицы

— При добавлении нового атрибута необходимо реиндексировать Flat таблицы






Cache



Конечно каждый из вас может мне сказать, что зачем нам разбираться как ускорить запросы в БД и вообще как работают коллекции если кэш нас спасет и все будет закэшировано. Отвечу коротко – кэш вас не спасет. Ни 1 из кэшей представленных в Magento либо не кэширует коллекции автоматически либо не работает в ваших кастомных контроллерах и моделях, которые вы используете, скажем, при импорте данных или подсчете чего-то. Да и к тому же до того, как оно попадет в кэш, ведь надо это как-то туда положить и быстренько показать пользователю.



Типы кэшей в Magento 1.*:








  • Configuration – кэширует конфигурационные файлы

  • Layout – кэширует layout файлы

  • Block HTML output – кэширует phtml шаблоны. По умолчанию используется на фронтенде только в top menu и footer.

  • Translations – кэширует csv транслейт файлы

  • Collections data – кэширует коллекции, которые используют ->initCache(…) метод. По умолчанию кэширует только core_store, core_store_group, core_website коллекции при инициализации.

  • EAV types and attributes – должен кэшировать eav атрибуты, НО не кэширует. Используется в 1 методе, который никогда не вызывается начиная с Magneto CE 1.4

  • Web services cache – кэширует api.xml файлы

  • Page Cache (FPC) – кэширует весь HTML, кэширует только CMS, Category, Product страницы. Игнорируется если протокол https, гет параметр ?no_cache=1, куки NO_CACHE

  • DDL Cache (Скрытый) – кэширует DESCRIBE вызовы к БД, используется в операциях записи



…и ни 1 не кэширует коллекции автоматически.




Правильная работа с коллекциями



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



Тестовый стенд:

OS X 10.10

3.1 GHz Intel Core i5 (4 cores)

8GB



Magento конфигурация:

Magento EE 1.14.0

MySQL 5.5.38

PHP 5.6.2



Контент:

3 Categories

2000 Products

2000 CMS pages



Процесс:

Для тестов был создан экстеншен с 1 контроллером и 1 экшеном, каждый тест проводился 5 раз, потом считалось среднее время. Все результаты указаны в секундах.



class Test_Test_IndexController extends Mage_Core_Controller_Front_Action
{
public function indexAction()
{
$temp = array();
$start = microtime(true);
Init values

Loop start
$temp[] = $product->getSku();
Loop end
Or
Some code snippet

$stop = microtime(true);
echo $stop - $start;
}
}


Псевдо код

Тесты




  1. EAV/Flat с перезагрузкой моделей и без

  2. Кэширование коллекций

  3. Правильное использование count() и getSize()

  4. Правильное использование getFirstItem и setPage(1,1)



EAV/Flat с перезагрузкой моделей и без



Цикл по коллекции. С load (перезагрузка) моделей внутри цикла:



$temp = array();
$collection = Mage::getModel('catalog/product')->getCollection()->addAttributeToSelect(...);
foreach ($collection as $product) {
$product = $product->load($product->getId());
$temp[] = $product->getSku();
}


Цикл по коллекции. Без load моделей внутри:



$temp = array();
$collection = Mage::getModel('catalog/product')->getCollection()->addAttributeToSelect(...);
foreach ($collection as $product) {
$temp[] = $product->getSku();
}


3 вида выборки данных:




  1. addAttributeToSelect(‘*’); // все атрибуты

  2. addAttributeToSelect(‘sku’); // 1 статик атрибут

  3. addAttributeToSelect(‘name’); // 1 стандартный атрибут



Результаты




Как вы видимо заметили, время без перезагрузки моделей В РАЗЫ меньше, чем когда вы перезагружаете модельки. Так же время еще меньше, когда Flat таблицы включены (т.е. нет лишних джойнов и юнионов) и мы выбираем только необходимые атрибуты.



В 1ом случае мы выполняем загрузку с кучей джойнов… а потом делаем это снова, но для модельки и так 2000 раз.



2ой раз мы делаем это для статик атрибута (он находится в той же табличке, что и сам продукт) и Magento не надо делать джойны. Поэтому время меньше.



3ий раз Magento нужно приджойнить еще табличку где хранится этот атрибут.



С Flat таблицами все аналогично, а в 2ух случаях вcе идентично – это потому что оба атрибута находятся в 1 таблице, отсюда и время идентичное.



Думаю цифры говорят сами за себя.





Кэширование коллекций



Без кэша:



$collection = Mage::getModel('catalog/product')->getCollection()
->addAttributeToSelect('*');


Используя метод initCache:



$collection = Mage::getModel('catalog/product')->getCollection()
->addAttributeToSelect('*')
->initCache(Mage::app()->getCache(),'our_data',array('SOME_TAGS'));


Кастомная реализация кэширования:



$cache = Mage::app()->getCache();
$collection = $cache->load('our_data');
if(!collection) {
$collection = Mage::getModel('collection/product')->getCollection()->addAttributeToSelect('*')->getItems();
$cache->save(serialize($collection),'our_data',array(Mage_Core_Model_Resource_Db_Collection_Abstract::CACHE_TAG));
} else {
$collection = unserialize($collection);
}


Рассмотрим выборку без использования кэша, с использованием метода, который нам предлагает Magento и с костылем, который я нигде не видел… сам сваял, основанный на методах модельки кэша. Обратите внимание, что для всех тестов после составления запроса я производил загрузку данных и преобразование коллекции к массиву объектов.



Результаты




Без кэша собственно ничего удивительного…все как обычно.



А вот используя маджентовский кэш я лично удивился, когда увидел, что время стало больше. А про EAV кэширование вообще глупой затеей, потому что EAV коллекция грузит сначала сущности из таблицы продуктов (именно вот это и кэшируется), а потом отдельным запросом выбирает значения атрибутов и заполняет объекты. Во Flat там все из 1 таблицы гонится. Но тем не менее время больше уходится на работу с кэшем чем с БД (тестировал я причем как с файловой системой, так и с redis – отличия 4ая цифра после запятой…т.е. на 2к сущностях ее нет). Суть InitCache метода заключается в том, что он сначала соберет все данные в коллекцию сам (пагинация, фильтры, events и так далее), создаст хеш из sql запроса и его будет искать в кэше, а если там что-то есть, то он это ансерелизует, а потом происходит запуск всех events и последующих методов. Это самая медленная процедура во всем процессе, именно вот тут выходит что кэш медленнее чем простой запрос в БД. Но зато не шлет запрос в БД… что не так и страшно уже.



Отдельно стоит пример с кэшем, написанным мной на коленке, там мы кэшируем конечный результат коллекции, причем минуя все events и дозагрузку атрибутов. Это работает для EAV и для Flat коллекций.



Правильное использование count() и getSize()



getSize()



$size = Mage::getModel('catalog/product')->getCollection()
->addAttributeToSelect('*')
->getSize();


count()



$size = Mage::getModel('catalog/product')->getCollection()
->addAttributeToSelect('*')
->count();


Результаты




Разница методов заключается в том, что count() производит загрузку всех объектов коллекции, а потом обычным пхпшным count’ом подсчитывает количество объектов и возвращает нам число. getSize же не производит загрузку коллекции, а генерирует еще 1 запрос в БД, где нет лимитов, ордеров и списка выбираемых атрибутов, есть только COUNT(*).



Пример использования обоих методов такой:



Если вам надо знать, есть ли вообще значения в БД или сколько их – используйте getSize, если же вам в любом случае коллекция нужна загруженная, или уже загрузилась то используйте count() – он вернет вам число элементов, загруженных в коллекцию.



Правильное использование getFirstItem и setPage(1,1)



getFirstItem()



$product = Mage::getModel('catalog/product')->getCollection()
->getFirstItem();


setPage(1,1)



$product = Mage::getModel('catalog/product')->getCollection()
->setPage(1,1)
->getFirstItem();


load()



$product = Mage::getModel('catalog/product')->load(22);


Результаты




Проблема getFirstItem в том, что он загружает всю коллекцию целиком, а потом просто в foreach возвращает первый элемент, а если его нет то возвращает пустой объект.



setPage (он же $this->setCurPage($pageNum)->setPageSize($pageSize)) же ограничивает выборку ровно 1 записью, что как вы видите значительно ускоряет загрузку результата.



Даже load быстрее getFirstItem, но обратите внимание, что load медленнее оказался чем выборка из коллекции 1 элемента. Это связано с тем, что load всегда работает с EAV таблицами.






Выводы



Подводя итог всему выше написанному, хочу посоветовать всем людям, работающим с Magento:




  • Никогда не вызывайте повторно load метод у объектов, полученных из коллекции

  • Загружайте только необходимые атрибуты

  • Если применимо к проекту, используйте Flat таблицы

  • Используйте count для подсчета результатов загруженной коллекции и getSize для получения числа всех записей

  • Не используйте getFirstItem метод без setPage(1,1) или аналогичных методов



Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/282025/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество

Следующие 30  »

<производительность - Самое интересное в блогах

Страницы: [1] 2 3 ..
.. 10

LiveInternet.Ru Ссылки: на главную|почта|знакомства|одноклассники|фото|открытки|тесты|чат
О проекте: помощь|контакты|разместить рекламу|версия для pda