Мой опыт показывает, что когда в таких приложениях возникает проблема производительности, поиски ее решения следует начинать не с изучения структур данных и алгоритмов. Время отклика больше всего зависит от количества взаимодействий между удаленными процессами (interprocess communications, IPC), осуществляемых в ответ на воздействие. Да, встречаются и другие локальные узкие места, но число взаимодействий между удаленными процессами обычно имеет решающее значение. Каждое взаимодействие между удаленными процессами добавляет некоторую ненулевую задержку в общее время отклика, и эти отдельные добавки суммируются, особенно если возникают последовательно.
Каноническим примером служит пульсирующая нагрузка в приложениях, использующих объектно-реляционное отображение (ORM). Пульсирующая нагрузка описывает последовательное выполнение множественных обращений к базе данных для чтения данных, необходимых для построения графа объектов (см. шаблон Lazy Load[15] в книге Мартина Фаулера «Patterns of Enterprise Application Architecture»,[16] Addison-Wesley Professional). Когда клиентом базы данных является сервер приложений промежуточного уровня (middle-tier), который компонует веб-страницу, обращения к базе данных обычно происходят последовательно в едином потоке. Их периоды ожидания суммируются, образуя суммарное время отклика. Даже если каждое обращение к базе данных длится всего 10 мс, страница, требующая 1000 обращений (что не редкость), появится с задержкой не менее чем в 10 секунд. Другими примерами могут служить обращение к веб-службам, HTTP-запросы веб-браузера, вызов распределенных объектов, шаблон связи «запрос-ответ» и взаимодействие с таблицей данных по специальным сетевым протоколам. Чем больше удаленных IPC требуется для ответа на воздействие, тем большим будет время отклика.
Существует ряд относительно очевидных и широко известных стратегий сокращения количества взаимодействий между удаленными процессами в расчете на одно воздействие. Одна из таких стратегий заключается в применении принципа бережливости — мы можем оптимизировать интерфейс между процессами, чтобы передавались только те данные, которые нужны непосредственно, а объем взаимодействия был минимален. Другая стратегия — как можно более широкое распараллеливание связей между процессами, чтобы общее время отклика определялось в основном IPC с наибольшей задержкой. Третья стратегия — кэшировать результаты предшествующих IPC, чтобы в будущем вместо IPC использовать обращение к локальному кэшу.
Проектируя приложение, следите за количеством взаимодействий между процессами, происходящих в ответ на каждое воздействие. Анализируя приложения с низкой производительностью, я часто сталкивался с тем, что отношение количества IPC к воздействию составляет 1000:1 и больше. Сокращение этого отношения путем кэширования, распараллеливания или других приемов даст значительно больший эффект, чем изменение структуры данных или модификация алгоритма сортировки.
Сборка должна быть чистой
Йоханнес Бродуолл
Приходилось ли вам видеть список предупреждений компилятора (warnings) размером с очерк на тему, как не стоит писать код, и думать при этом: «Конечно, с этим надо что-то делать… только сейчас у меня нет на это времени»? И наоборот, случалось ли вам увидеть единственное предупреждение, появившееся во время компиляции, и тут же исправить его?
Когда я начинаю новый проект с нуля, нет никаких предупреждений, нет беспорядка, нет проблем. Но объем кода растет, и, если не принять меры, не исключено, что беспорядок, хлам, предупреждения и проблемы начнут постепенно накапливаться. В большом потоке «шума» значительно тяжелее отыскать действительно важное предупреждение среди сотен других, которые мне не интересны.
Чтобы предупреждения снова стали полезными, я стараюсь придерживаться политики полной недопустимости предупреждений во время сборки. Даже если предупреждение несущественно, я его устраняю. Если оно не критично, но все же относится к делу, я исправляю код. Если компилятор сообщает об опасности исключения с нулевым указателем, я исправляю источник этой опасности, даже если «знаю», что в реальной обстановке эта проблема никогда не возникнет. Если встроенная в код документация (Javadoc или ее аналог) ссылается на параметры, которые удалены или переименованы, я исправляю документацию.