Базовая производительность для .NET на AWS Lambda

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

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

Лямбда-функция Minimal

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

using System.IO;
using System.Threading.Tasks;

namespace Benchmark.Minimal {

    public sealed class Function {

        //--- Methods ---
        public async Task<Stream> ProcessAsync(Stream request)
            => Stream.Null;
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Данные бенчмарка для .NET 6 на x86-64

Данные наглядно показывают, что фаза INIT примерно одинакова для всех конфигураций памяти ниже порога в 3 008 МБ. Как упоминалось в статье Анатомия жизненного цикла AWS Lambda, фаза INIT всегда выполняется на полной скорости.

Холодная фаза INVOKE примерно в 10 раз медленнее для 128 МБ, чем для 1 024 МБ. Однако сумма всех теплых фаз INVOKE всего в ~3 раза медленнее. Тем не менее, стоимость улучшенной производительности выше менее чем на 5%.

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

Размер памяти Начальная Используемая холодная Всего холодный старт Всего использовано теплой (100) Стоимость (мк$)
128 МБ 235.615 620.921 856.536 365.519 22.25509
256 МБ 238.296 315.731 554.027 150.124 22.14107
512MB 241.193 136.89 378.083 124.686 22.37980
1024MB 239.972 60.804 300.776 115.53 23.13891
1769MB 241.005 37.623 278.628 116.322 24.63246
5120MB 218.112 37.009 255.121 119.559 33.24730

Полноразмерное изображение

Полноразмерное изображение

Минимальная продолжительность холодного запуска для .NET 6

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

Более примечательно, что архитектура ARM64 оказалась медленнее при сопоставимых конфигурациях памяти, чем архитектура x86-64.

Архитектура Размер памяти Многоуровневый Ready2Run PreJIT Init Использованный холодный старт Всего Холодный старт
arm64 5120MB да нет нет 211.006 30.165 241.171
x86_64 1024 МБ да нет нет 213.085 33.173 246.258
x86_64 1769МБ да нет нет 215.754 24.164 239.918
x86_64 5120MB да нет нет 198.771 24.094 222.865

Полноразмерное изображение

Минимальная стоимость выполнения для .NET 6

Еще одним неудивительным результатом является то, что архитектура ARM64 обеспечивает самую низкую стоимость выполнения, поскольку ее цена за единицу продукции на 20% ниже. Аналогично, конфигурация памяти находится в нижней части — всего 256 МБ.

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

Архитектура Размер памяти Многоуровневая Ready2Run PreJIT Init Используемая холодная память Всего использовано тепло (100) Стоимость (мк$)
arm64 256 МБ нет нет нет 266.026 378.676 158.064 21.98914228
arm64 256 МБ нет нет да 288.025 371.274 161.529 21.97601788
arm64 256 МБ нет да нет 264.304 361.657 164.619 21.95426344
arm64 256 МБ нет да да 287.762 361.285 160.248 21.93844936

Полноразмерное изображение

Что насчет .NET Core 3.1?

Я сомневался, стоит ли упоминать об этом, поскольку .NET Core 3.1 выходит из эксплуатации в декабре 2022 года, но разница в производительности для базового варианта просто ошеломляющая.

Лямбда-функция, использующая .NET Core 3.1 с 512 МБ, на 40% быстрее при холодном запуске, чем функция, использующая .NET 6 с 5120 МБ!

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

Архитектура Размер памяти Многоуровневый Ready2Run PreJIT Init Использованный холодный старт Всего Холодный старт
x86_64 512 МБ да нет нет 150.129 6.903 157.032
x86_64 1024 МБ да нет нет 148.376 6.081 154.457
x86_64 1769МБ да нет нет 148.338 5.972 154.31

Полноразмерное изображение

Аналогично, стоимость выполнения ниже в .NET Core 3.1, но не так значительно. Тем не менее, для .NET 6 было всего 4 конфигурации, которые достигли стоимости ниже 22 мк$. Для .NET Core 3.1 существует 39 конфигураций со стоимостью ниже 21 мк$!

Интересно, что 4 конфигурации с наименьшей стоимостью следуют аналогичной схеме: ARM64, 128 МБ, отсутствие многоуровневой компиляции, а также выбор ReadyToRun и PreJIT.

Архитектура Размер памяти Многоуровневая компиляция Ready2Run PreJIT Init Используемая холодная память Всего использовано тепло (100) Стоимость (мк$)
arm64 128 МБ нет нет нет 162.366 102.693 110.096 20.55465044
arm64 128 МБ нет нет да 186.627 98.641 112.327 20.55161642
arm64 128 МБ нет да нет 161.989 88.677 110.391 20.53178133
arm64 128 МБ нет да да 185.923 85.289 117.811 20.53850086

Полноразмерное изображение

Заключение

На основе контрольных примеров мы можем установить эти нижние границы.

Для .NET 6:

  • Продолжительность холодного старта: 223 мс
  • Стоимость выполнения: 21,94 мк$

Для .NET Core 3.1:

  • Продолжительность холодного запуска: 154 мс
  • Стоимость выполнения: 20,53µ$

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

Что дальше

В следующем посте я собираюсь провести сравнительный анализ сериализаторов JSON. В частности, популярную библиотеку Newtonsoft JSON.NET, встроенное пространство имен System.Text.Json и новые генераторы исходников JSON в .NET 6.

Оцените статью
devanswers.ru
Добавить комментарий