Главная страница
Навигация по странице:

  • Что такое производительность

  • Вид ресурса Операций в секунду Пропускная способность (Bandwitch)

  • Как измерить производительность

  • Показатели, которые следует мерить для нагрузочного тестирования

  • Как повысить производительность

  • Ускорение выполнения задачи

  • Как мы выбираем - что кешировать, а что - нет На какой срок

  • Как выбираем размер кеша

  • Как определяем права доступа на кешированные данные

  • Как проверяем “свежесть” данных

  • Целостность данных в кеше

  • Статистика использования кеша Количество попаданий/промахов, для каждого класса данных Предварительный прогрев кеша

  • Persistent cache(сохраняет данные даже после рестарта)

  • Предварительные вычисления (Precompute)

  • Предварительная инициализация Пример: Пул соединений

  • Параллелизм Когда выгодно (НЕ)использовать параллелизм

  • Инструменты повышения производительности. Инструменты повышения производительности программного обеспечени. Инструменты повышения производительности программного обеспечения. Требования к производительности


    Скачать 263.42 Kb.
    НазваниеИнструменты повышения производительности программного обеспечения. Требования к производительности
    АнкорИнструменты повышения производительности
    Дата29.03.2023
    Размер263.42 Kb.
    Формат файлаdocx
    Имя файлаИнструменты повышения производительности программного обеспечени.docx
    ТипДокументы
    #1023450


    Инструменты повышения производительности программного обеспечения.

    Требования к производительности

    Что значит - “быстро”?

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

    Для какой нагрузки (для какого количества параллельных задач)?

    Собираемся мы обслуживать нашим серверным приложением 300 одновременных пользователей? 1000? 10 000?

    C какой скоростью?

    Пример 1:

    страница “просмотр заявки” в веб приложении в среднем случае должна загружаться быстрее 3-х секунд

    Пример 2:

    Процесс обнаружения подозрительной активности в банковских транзакциях должен проверять 100 000 транзакций в сутки

    Что такое производительность?

    Две ключевые метафоры для понимания производительности:

    • “труба” (pipe)

    • “конвейер задач”

    Обслуживание пользователя - это всегда последовательность задач.

    Возьмем для примера, WEB приложение, которое получило запрос пользователя (например “открыть приходную накладную номер 123”). Конвейер задач выглядит так:

    1. HTTP POST request

    2. Find and load Session

    3. Check Permissions

    4. Try load object from Cache

    5. Load object from Database

    6. Write to usage tracking log

    7. Send Request back to client.



    Каждая задача имеет “длину”, т.е. продолжительность задачи в миллисекундах.

    Каждая задача имеет “ширину”, т.е. затраты процессорного времени/памяти/производительности дискового ввода-вывода.



    Суммарная продолжительность задач при выполнении одного запроса  - это скорость с которой приложение отвечает пользователю (Response time, оно же Latency). Это то, что конечный пользователь называет производительностью.

    Количество запросов, которое приложение способно выполнить в минуту (час/день), обслуживая необходимое для бизнеса количество пользователей(скажем 10 000) -

    это пропускная способность  (Bandwitch). Именно ее называет производительностью Заказчик/Бизнес-спонсор проекта.

    Пример: “1 миллион просмотров страницы товара в сутки, при количестве одновременных пользователей до 10 000”.

    От чего зависит пропускная способность?

    • От “пространства”, которое приложению требуется, чтобы обслужить одни запрос, т.е. произведения ширины и длины задач

    Например, чем больше CPU time(RAM/Network bandwitch,и т.д.) приложению требуется для  задачи “Проверить наличие товара на складе”(или чем ДОЛЬШЕ оно эту задачу выполняет) - тем меньше его остается для обслуживания других пользователей.

    • От пропускной способности “трубы” (pipe) в которой запросы выполняются. Больше места в “трубе” - больше запросов можно обслужить одновременно. Ее определяют прежде всего аппаратное обеспечение(hardware), потом OS и  Middleware.



    Виды ресурсов и фундаментальные ограничения

    Вид ресурса

    Операций в секунду

    Пропускная способность (Bandwitch)

    Скорость ответа(Latency)

    Процессор

    2.0 - 3.5 GHz  =>

       2,000,000,000 - 3,500,000,000 ops/sec

    N/A

    N/A

    Диск(HDD)

    350 IOPs =>

         350 ops / sec

        50 - 120 MB / sec

    10 - 20 ms

    Диск(SDD)

      100,000 - 130,000 IOPs

              100,000 - 130,000 ops/sec

      200 - 6,000 MB /sec

    0.1 ms

    Сеть (проводной Enternet)

    100 - 1000 Mbit / sec,

     Minimum TCP Packet 20 Bytes (160bits)  => 

              600,000 - 6,000,000 ops/sec

    LAN:

      100Mbit - 1,000 Mbit  =>

        12 - 125 MB/sec

    WAN:

       2Mbit - 20 Mbit=>

         0.25 - 2.5 MB/sec

    LAN

      0.1 - 1 ms

     

    WAN 

     20 - 300 ms

    Оперативная память

    Operational Freq: 166 - 255 MHz =>

        100,000,000 - 210,000,000 / sec 

     8,500 - 17,000  MB / sec

    0.000,010 - 0.000,02 ms

    Примечание: Таблица составлена для реального “железа”. Виртуальное(cloud), естественно добавляет свои  накладные расходы. Однако не меняет ситуацию принципиально. Никто не проектирует программное обеспечение отдельно для виртуальных машин и для реальных - и производители виртуальных платформ это понимают. В специфических случаях виртуальное “железо” может оказаться даже быстрее реального, например запись на виртуальный HDD может быть полностью буферизована в RAM внутри виртуальной машины и, естественно, будет в разы быстрее.
    Как измерить производительность?

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

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

    • Если система имеет высокую производительность на максимальном количестве пользователей - это не означает что она не имеет проблем на среднем.

    • Нужно знать, достигнут ли “потолок”, насколько большой запас производительности имеется в системе.

    Показатели, которые следует мерить для нагрузочного тестирования:

    1. Скорость ответа (далее Latency) - какая средняя скорость ответа? Какая максимальная и минимальная?

    2. Пропускная способность (далее Bandwitch) - сколько запросов в минуту выполнено?

    3. Количество ошибок (далее Error rate) - сколько запросов приложение не смогло выполнить?

    Подробнее о третьем показателе. Когда система подвергается большей нагрузке, чем способна обработать(т.е., когда мы пытаемся закачать в “трубу” больше задач, чем она может через себя пропустить), у нее есть 3 варианта поведения:

    1. Правильный. Поставить входящие запросы в очередь и обработать их когда освободятся ресурсы

      • Пример: Наш электронный магазин предоставляет HTTP REST API партнерам, позволяя им закупать у нас товары оптом. Наш сервер API способен обслуживать 40 одновременных запросов. Что произойдет, когда мы получим одновременно 41 запрос? Нам придется поставить один запрос в очередь, и заставить его ждать, пока не освободится какой то из потоков, которые обслуживают сейчас предыдущих 40 пользователей. А если мы получим 60 одновременных запросов? Поставим в очередь 20 из них.

    2. Правильный. Если количество запросов настолько велико, что их невозможно обработать за требуемое время - отбрасывать запросы

      • Пример: Продолжая пример выше, что если мы получим 240 запросов? Мы конечно можем поставить 200 запросов в очередь- но чем больше у нас ожидающих в очереди, тем хуже средняя скорость ответа. Представьте, что вы стоите в огромной очереди за билетами на спектакль, который показывают сегодня последний раз и вы явно не успеваете до закрытия кассы. Нет смысла стоять в такой очереди. С точки зрения пользователя, система которая “зависла”, т.е.,отвечает слишком медленно, ничем не лучше системы которая “сломана”, т.е., не отвечает вообще.  (Да, с точки зрения инженера, который обслуживает системы и знает их изнутри, разница большая - но заказчика это не интересует). Система, которая сразу сигнализирует о том, что перегружена/получила больше работы, чем может выполнить в срок, намного лучше, чем та которая молча заставляет клиентов ждать слишком долго/вечно. Нужно поставить лимит на длину очереди и отбрасывать (например через HTTP 500) запросы которые не уместились в нее.



    Вопрос на проверку: если наш SLA(Service Level Agreement) “запрос должен быть обработан не больше чем за 6 секунды”  и скорость выполнения запроса у нас 3 секунды - какая максимальная длина очереди имеет смысл? Ответ - 40. Более длинная очередь не позволит выполнить SLA.


    1. Неправильный. Пытаться сделать больше работы, чем система способна выполнить. Это приведет лишь к неконтролируемому снижению производительности(из-за swopping’а памяти, перегрузки CPU/HDD/IO, и т.д.)  

    2. Неправильный. Все остальное. Повредить данные, выдавать пользователю мусор вместо требуемого HTML/XML, не выдавать никакого ответа и так далее.


    Типичный график нагрузочного тестирования. Система способна обработать не больше  100 запросов в секунду. Запихивание в систему большего объема работы (300 запросов) только создает длинную очередь из 200 ждущих запросов.


    Тестирование, естественно, должно быть автоматизированным (как иначе имитировать работу сотен пользователей). Есть хороший выбор инструментов тестирования (Jmeter, Grinder, Load  Runner).

    Тестирование должно быть повторяемым (любой тест можно воспроизвести и сравнить производительность приложения до оптимизаций и после).

    Любой участник команды(разработчик или тестировщик) должен иметь возможность  провести тест на выбранном стенде в DEV(Developer) или QA(Quality Assurance) или на собственном компьютере - чтобы провести эксперимент или ревью.

    Следовательно:

    • Тестовые скрипты, их настройки, тестовые данные, скрипты генерации тестовых данных - в Version Control System

    • конфигурационные файлы приложения, OC и middleware (MongoDB, Elastic Search/ и т.д.) в Version Control System.

    • Инструмент тестирования - любой разработчик может легко скачать и установить его на свой компьютер (нужны лицензии или инструмент должен быть open source).

    • Отчет о тестировании должен показывать:

    • Производительность по каждой тестируемой задаче (“полнотекстовый поиск”, “просмотр накладной” и т.д.) по результатам последнего теста.

    • Тренд в разрезе задача/производительность/количество пользователей за период, чтобы отследить, как недавние изменения в продукте повлияли на производительность

    Необходимо отдельно тестировать производительность в режиме “кеширование включено” и “кеширование выключено”. Иначе мы можем получить завышенные результаты. Кеширование - может сильно повысить производительность и ,поэтому, используется, и в нашем продукте и во всех лежащих в его фундаменте Middleware(Веб сервер, СУБД,и т.д.). Но, в зависимости от того, как пользователи используют наш продукт, кеширование может дать как значительный выигрыш, так и нулевой и даже отрицательный (т.е., снизить производительность). Например, функция веб сайта “пользователь просматривает свой приватный профиль” не выигрывает от использования кеширования:

    • пользователь не будет просматривать свой профиль несколько раз подряд,

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

    • в целом этой функцией редко пользуются.

    Нагрузочное тестирование для такой функции покажет ЗАВЫШЕННУЮ производительность. Потому что тест выполнит много запросов подряд, и , естественно, кеширование увеличит производительность - в тесте, но не в реальной жизни! Решение - выключить кеширование, прежде чем тестировать такую функцию ( в настройках нашего приложения, веб сервера, базы данных, и т.д.).

    В ходе тестировании желательно фиксировать показатели нагрузки на железо/middleware. Это может помочь найти узкие места, препятсвующие движению данных (далее bottleneck) в системе.

    Типовой набор метрик:

    • CPU User time

    • CPU System time (Может  показывать на неэффективный ввод-вывод, т.е. слишком большое количество мелких операций)

    • HDD Number of reads

    • HDD Number of writes

    • HDD bytes readed

    • HDD bytes written

    • Network Number of reads

    • Network number of writes

    • Network bytes readed

    • Network bytes written

    • RAM Private space used by application

    • RAM Shared space used by application

    • RAM Number of page faults (насколько локализованы данные приложения в памяти?)


    Когда начинать нагрузочное тестирование: как только получена первая сборка приложения.

    Мощность и конфигурация тестового стенда должны быть идентичны продукции. (Для сокращения издержек, в случае использования облака можно выключать тестовые сервера либо снижать их мощность между нагрузочными тестами )

    Типичная ошибка - запуск теста несколько раз без “холодного запуска” тестируемого ПО (завышает результаты из-за кеширования).

    Как повысить производительность?

    Чтобы получить высокую производительность:

    • Уменьшайте длину задач(т.е. Latency), но не за счет излишнего увеличения их “ширины”.

    Например, слишком агрессивное кеширование памяти в задаче “сгенерировать веб страницу” ускорит ее выполнение, но заодно увеличит расход памяти на каждого пользователя - и ,в результате, сократит Bandwitch

    • Уменьшайте ширину задач в конвейере

    • Уменьшайте количество задач в конвейере

    Есть 3 способа достичь этого:

    Ускорение выполнения задачи

    Основные способы:

    • Кеширование

    • Предварительная калькуляция

    • Предварительная инициализация

    • Пакетные операции

    Кеширование

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

    Пример 1:

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

    Пример 2: У нас сервера в Европе, а клиент - в США. Проблема - network latency. Мы устанавливаем кеширующий сервер в CША, поближе к клиенту.

    Как мы выбираем - что кешировать, а что - нет? На какой срок? 

    Не имеет смысла кешировать данные, которые не будут повторно востребованы. Такое кеширование только понизит производительность - из за накладных расходов на помещение данных в кеш/поиск в кеше.

    Пример: Кеширует ли  Google результаты поиска (вот этот набор из 100 000 ссылок по поисковой фразе “database performance”)? Предположу, что если и кеширует, то на короткий промежуток времени. Вероятность того, что много разных людей будут искать одно и то же - невелика. Вероятность, что они сформулируют свой поисковый запрос одинаково, слово в слово - еще меньше. В каком-нибудь поисковом сервисе для интранет - я бы не кешировал совсем.

    Решение принимаем по каждому виду данных/бизнес сущности отдельно. Ищем баланс с учетом размера данных, стоимости их вычисления/чтения с HDD, вероятности их повторной востребованности, размера, который они занимают в кеше/частоты изменения данных/того, насколько “болезненно” будет для пользователя получение “устаревших” данных из кеша.

    Как выбираем размер кеша

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

    Как очищаем кеш

    Предположим, прочитали мы из медленного HDD/СУБД, что Иванов - начальник транспортного цеха. И закешировали в RAM на 15 минут. А через минуту пришел сотрудник HR и перевел Иванова на должность начальника цеха готовой продукции.

    Пока все тривиально, наше приложение должно:

    • Обновить данные об Иванове в СУБД

    • Удалить устаревшие данные  об Иванове из кеша

    Что если у вас есть внешняя система, которая тоже может менять данные?

    Например, данные департамента HR импортируются из другого филиала?

    Кеш должен позволять очистить данные о любой выбранной бизнес-сущности/все данные:

    • Посредством API (которое “будет вызвано” процессом импорта данных)

    • Посредством commandline tool или пользовательского интерфейса, доступного только администратору - для упрощения сопровождения системы

    Распределенные системы с Мастер-Мастер архитектурой добавляют новую сложность. Представьте, что у вас два серверных центра, в Москве и в Питере, и оба одновременно читают/изменяют одни и те же данные(master-master database replication). Необходимо реплицировать между центрами не только измененные данные, но и извещения наподобие “уважаемый Другой Дата Центр, сбросьте Ваш кеш о пользователе 123, его данные поменялись”. В качестве транспорта для “сбрось кеш” извещений, вероятно, следует использовать тот же канал, что и для репликации самих данных.

    Как определяем права доступа на кешированные данные

    Предположим, наши клиенты расположены далеко от дата центра. Network latency варьирует от 0.1 ms (миллисекунды) в LAN до 100 ms в интернет. Есть смысл установить кеширующий сервер, поближе к ним, и кешировать данные в нем. Что если данные, к которым есть доступ у клиента X, не должны быть доступны клиенту Y? Типичные решения:

    • Кеширующий сервер делает запрос “разрешено ли Y смотреть данные с ID 17?” в центр данных (что, к сожалению, добавляет Network latency)

    • Клиент Y предварительно получает в центре данных тикет, в котором, в зашифрованном виде, содержится описание его прав доступа(и у кеширующего сервера есть ключ дешифрования)

    Как проверяем “свежесть” данных

    Продолжая пример с network latency, как кеширующий сервер проверит, доступны  ли,обновились ли данные в дата центре?

    Прямолинейный способ - загрузить их заново, более эффективный - загрузить их только если контрольная сумма данных изменилась. Пример из спецификации HTTP - http://en.wikipedia.org/wiki/HTTP_ETag.

    Целостность данных в кеше

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

    Зависимости между кешированным данными - когда мы удаляем из кеша устаревшие данные, нужно удалить и те, которые от них зависят.

    Говоря об имплементации, потребуется

    • спецификация для имен ключей в кеше

    • спецификация формата данных в кеше

    • спецификация зависимостей (“когда чистите в кеше Пользователя, вычистите и его Профиль”)

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

    Статистика использования кеша

    Количество попаданий/промахов, для каждого класса данных

    Предварительный прогрев кеша

    Может потребоваться при большой зависимости производительности приложения от кеширования/большом объеме данных. Чтобы избежать резкого “проседания” производительности после полного сброса кеша/перезапуска приложения.  

    Persistent cache(сохраняет данные даже после рестарта)

    Может потребоваться, если стоимость вычисления данных/извлечения из HDD/СУБД высока

    Специфика кеширования запросов в СУБД/ORM

    Базы данных используют кеширование двояко:

    1. Низкоуровневый кеш блоков данных

    2. Кеш запросов (где ключом является “SELECT FistName,LastName from Users Where ID=123“, а значением - прочитанный набор записей)

    Кеш запросов хорошо помогает при READ-ONLY доступе к небольшому объему данных(чтобы полностью уместился в кеше). Следует понимать, это не замена полноценному кешированию на уровне приложения.

    Дело в том, что СУБД “не знает” бизнес-логики приложения и оперирует только текстами SQL запросов. Это создает проблемы c дубликацией/запихиванием слишком большого объема данных в кеш/излишне агрессивном сбросе кеша.

    Например, база данных получила SQL два запроса подряд:

    1. SELECT * from user_profile where ID=123;

    2. UPDATE user_profile SET compensation_coeffcient=1.5 WHERE employee_grade=4;


    Устарели ли результаты “SELECT …”? С точки зрения приложения - нет, потому что поле compensation_coeffcient им не используется, либо пользователь с ID==123 не имеет “employee_grade==4”. Но СУБД либо не может этого знать либо не может эффективно отследить.

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

    Предварительные вычисления (Precompute)

    Когда стоимость вычисления данных велика, вероятность повторного обращения к тем же данным значительна и частота изменений данных мала - имеет смысл предварительно вычислить их и сохранить в persistent storage.

    Пример: 

    Проблема - Чтение из СУБД неких редко изменяемых,но часто требуемых данных выполняется медленно из-за сложных SQL запросов со множеством JOIN’ов. Изменить структуру СУБД мы не хотим(другие бизнес-процессы требуют именно такой структуры).

    Решение - Выполнять чтение и преобразование данных фоновым процессом, который сохраняет уже обработанные данные в “кеширующей таблице” в СУБД.

    Пакетные операции

    Объединение задач в “пакет” (batch) позволяет экономить на накладных расходах.

    Типичная ошибка - не использовать SQL Batch при вставке большого количества записей в СУБД. Вставка данных требует от СУБД обработать входные данные/проанализировать SQL запрос/записать транзакцию в WAL(http://en.wikipedia.org/wiki/Write-ahead_logging)/перестроить индекс таблицы.

    Вставка десяти записей означает - вы платите десять раз. При использовании Batch insert (пример для MySQL http://dev.mysql.com/doc/refman/5.0/en/insert.html) - платите накладные расходы один раз.

    Предварительная инициализация

    Пример: Пул соединений

    Типичное серверное приложение извлекает, по запросу пользователя, данные из СУБД(Сервера поиска/внешней системы/и т.д.)

    Установка нового соединения съедает время(network latency/инициализация нового потока на стороне СУБД). Пул заранее открытых соединений к серверу СУБД решает эту проблему. С другой стороны, больше открытых соединений - больший расход ресурсов.

    • Когда применять: Небольшие объемы данных, частые запросы к ним.

    • Когда НЕ применять: Большие объемы данных(время на установление соединения пренебрежимо мало по сравнению с общей продолжительностью пересылки данных),  редкие запросы (т.е., открытое соединение будет простаивать, разбазаривая ресурсы )

    Потенциальные проблемы:

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

    1. Приложение получило задачу на выполнение

    2. Открыло соединение (новое, “чистое”)

    3. Послало запрос

    4. Получило ответ

    5. Закрыло соединение (если в приложении до этого произошел сбой, СУБД сама закроет, когда сработает timeout )

    Типовые проблемы при  использовании пула соединений:

    • Шаг №2 может надолго заблокировать поток. Например, приложение содержит ошибку и  не всегда возвращает соединение в пул. В результате все незанятые соединения в пуле оказались исчерпаны. И  реализация метода “openConnection” в пуле ждет, когда освободится одно из занятых сейчас соединений, пока не сработает таймаут(или вечно...)

    • Полученное на шаге №2 соединение уже использовалось ранее и могло “унаследовать” от предыдущего потока целую россыпь проблем (НЕзакрытая транзакция/НЕснятые блокировки/мусорные настройки Transaction Isoloation Level|Charset conversion/мусор в server side SQL variables).

    • Соединение может быть уже закрыто сервером СУБД из-за ошибок, либо длительной неактивности.

    • Полученный на шаге №4 ответ может быть вовсе не ответом на посланный в №3 запрос. Это может быть ответ на запрос, посланный предыдущим “пользователем” соединения.

    Поэтому использование persistent соединений требуют поддержки на уровне реализации пула/протокола/драйвера БД/СУБД.

    • Сброс состояния соединение при возвращении в пул - СУБД должно предоставлять такую возможность и драйвер в приложении должен ею пользоваться.

    • Идентификация на уровне драйвера/протокола - к какому посланному ранее запросу относится полученный драйвером от СУБД ответ.

    • Отслеживание и четкое разграничение драйвером ошибок на сетевом уровне(timeout/connection closed) от ошибок уровня приложения (SQL query has error). Первые делают невозможным использование соединения/требуют выбросить его из пула. Вторые позволяют продолжить его использование.

    • Необходим лимит на количество в пуле (не больше, чем сервер приложений может обработать параллельно).

    • Лимит на начальное количество соединений в пуле.

    • Настройка - Сколько добавлять в пул за раз, когда не хватает.

    • Timeout на извлечение из пула. Добавление нового соединения может занять от доли секунды до часов(перегруженный сервер СУБД/сбой или неправильная настройка в файерволе или load balancer’е). Последнее, чего мы хотим - толпа потоков в сервере приложений, заблокированных навечно, потому что в пуле закончились соединения и он заблокировался при добавлении нового.

    • Timeout на соединение с СУБД  - см выше.

    • Timeout на получение данных из СУБД - см выше.

    • Heartbeat  - пул должен регулярно делать “ping” на каждом соединении, чтобы СУБД не закрыло его за неактивность .

    • Перезапуск пула без перезапуска приложения(см выше список проблем из-за которых перезапуск может понадобится). Перезапуск крупного server side приложения может потребовать несколько минут прежде чем работоспособность восстановиться полностью ( “прогрев” кешей/инициализация/установка network соединений).

    • Реконфигурация пула без перезапуска приложения(см выше).

    Параллелизм

    Когда выгодно (НЕ)использовать параллелизм

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

    Например, у нас есть сервер - калькулятор. Пользователи посылают ему арифметические выражения, например “2+2” и сервер вычисляет результат - в нашем примере “4”. Аппаратное обеспечение - 1 (один) CPU, с одним ядром. Сколько пользователей сервер может обслужить параллельно?Ответ - только одного! Потому что используемый ресурс - только CPU и RAM. При этом единственное ядро CPU является “бутылочным горлышком”.  

    Представьте, что для выполнения одного запроса серверу требуется одна секунда времени CPU. (Это конечно невероятно много для того, чтобы сложить “2 и 2”, но  в нашем учебном примере так нагляднее).

    Представьте, что к серверу обратились 60 пользователей одновременно.

    Сколько времени нужно, чтобы их обслужить используя один поток? Одна секунда * 60 пользователей = одна минута.  Одного пользователя сервер обслужит за 1 секунду, второму придется подождать 2, самому невезучему - 60. Хотя бы 30 пользователей будут обслужены быстрее чем за 30 секунд.  

    Если мы распараллелим  работу внутри сервера между 60 потоками, каждый обслуживает один запрос, все запроcы обрабатываются строго параллельно - сколько времени пользователи будут ждать ответа? 60 секунд. Причем, ВСЕ ПОЛЬЗОВАТЕЛИ будут ждать по 60 секунд. Многопоточность/параллелизм только ухудшают производительность, когда мы пытаемся запихнуть в систему больше работы, чем она способна выполнить.

     

    Другой пример - WEB сервер, раздающий HTML/JS/CSS файлы. Аппаратное обеспечение - 4 ядра CPU.  Сколько пользователей сервер может обслужить параллельно? Намного больше 4-х, потому используемые ресурсы - не столько CPU/RAM, сколько Disk I/O. Дисковой ввод-вывод намного медленнее CPU и поэтому является “бутылочным горлышком”. Серверу следует создать намного больше 4-x потоков - они все равно будут ждать, когда завершится чтение с диска. Если потоков будет слишком мало -тогда уже ИХ КОЛИЧЕСТВО станет “бутылочным горлышком”.

    Вывод

    Ключевая метафора для понимания производительности - это “конвейер задач”. Которые прокачиваются через “трубу”. Количество и последовательность задач в конвейере определяются требованиями заказчика и техническими решениями программиста.

    Пропускная способность трубы определяется ее шириной (Bandwitch) и длиной(Latency). Которые в свою очередь определены лежащим в фундаменте системы Hardware/Software.

    Пропускная способность системы в целом определяется тем, сколько задач можно прокачать через трубу в единицу времени. Здесь важны т.е., “ширина”(объем используемого CPU/RAM/IO) и “длина”(продолжительность выполнения) задач.

    Производительность может быть повышена следующими способами:

    • Сокращением “длины” и/или “ширины” задач

    • Выполнением задач параллельно, ЕСЛИ в “трубе” есть для этого место.

    • Исключением задач из конвейера - выполнением их асинхронно.



    написать администратору сайта