Как я подключил Sign in with Apple — Apple авторизацию. Подробный гайд

Салют, меня зовут Макс Нечаев, я занимаюсь продуктовой разработкой (iOS Developer). Сегодня решил сделать небольшую статью, которая расскажет, как подключить Apple Sign In в ваше iOS приложение. Плюс расскажу некоторые edge кейсы технологии.

UI часть задачи

Заходим в наше View. Первое, что нам нужно, это импортировать сервисы авторизации.

import AuthenticationServices

Вопреки расхожему мифу, можно сделать кастомную кнопку авторизации Apple, а не использовать ASAuthorizationAppleIDButton. Так я и поступил. Я взял главную кнопку, которая используется в приложении и добавил ей Apple стилистику по дизайну. После чего накинул на нее классический таргет.

private let appleButton = PrimaryButton(style: .appleSignIn) // appleButton.addTarget(self, action: #selector(loginWithApple), for: .touchUpInside)

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

appleButton.snp.makeConstraints {             $0.leading.equalToSuperview().offset(16)             $0.trailing.equalToSuperview().offset(-16)             $0.height.equalTo(56)             appleButtonBottomConstraint = $0.bottom                 .equalTo(view.safeAreaLayoutGuide.snp.bottom)                 .offset(-16)                 .constraint         }

Мы используем VIPER, следовательно всю логику Apple авторизации я решил вынести в отдельный сервис, доступ к этому сервису будет иметь Interactor. Вся задача View (в качестве View у нас ViewController), это поймать нажатие на кнопку и сказать Presenter об этом. Presenter, в свою очередь, оповещает Interactor, тот обращается к сервису, который уже обрабатывает всю логику. После чего приложение меняет свое состояние и выполняется обратная цепочка от Interactor к View.

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

Реализация сервиса авторизации

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

final class AppleAuthManager: NSObject {     static let shared = AppleAuthManager()

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

struct AppleAuthData {         let identityToken: String         let authorizationCode: String         let firstName: String?         let lastName: String?     }

Я пошел классическим путем обработки через completion handler. Соответственно создаем приватную переменную с enum Result, который будет возвращать либо нашу заполненную модель, либо ошибку.

// MARK: - Private properties private var completionHandler: ((Result<AppleAuthData, Error>) -> Void)?

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

// MARK: - Public methods      func login(completion: @escaping (Result<AppleAuthData, Error>) -> Void) { // сетим в наш хэндлер комплишн функции         self.completionHandler = completion  // создаем провайдер и запрос, говорим запросу тот скоуп данных, // которые он должен предоставить         let appleIDProvider = ASAuthorizationAppleIDProvider()         let request = appleIDProvider.createRequest()         request.requestedScopes = [.fullName, .email]  // сетим в контроллер авторизации наш запрос // подписываемся на делегат, который и даст нам то, что нужно, и делаем запрос         let authorizationController = ASAuthorizationController(authorizationRequests: [request])         authorizationController.delegate = self         authorizationController.performRequests()     } 

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

func authorizationController(         controller: ASAuthorizationController,         didCompleteWithError error: Error     ) {         completionHandler?(.failure(error))     }

Наконец мы дошли до самого интересного метода делегата didCompleteWithAuthorization. Именно здесь мы должны сначала через guard let получить данные входа (credential), а также необходимый нам identityTokenData, который следует привести к String формату через (encoding: .utf8). На словах может быть непонятно, поэтому конечно же предоставляю сниппет кода.

func authorizationController(         controller: ASAuthorizationController,         didCompleteWithAuthorization authorization: ASAuthorization     ) { // получаем и кастим данные         guard             let credential = authorization.credential as? ASAuthorizationAppleIDCredential,             let authorizationCodeData = credential.authorizationCode,             let identityTokenData = credential.identityToken,             let authorizationCode = String(data: authorizationCodeData, encoding: .utf8),             let identityToken = String(data: identityTokenData, encoding: .utf8)         else {             return         } // приводим их к нашему типу      let data = AppleAuthData(         identityToken: identityToken,         authorizationCode: authorizationCode,         firstName: credential.fullName?.givenName,         lastName: credential.fullName?.familyName     )  // вызываем комплишн и передаем в него данные      completionHandler?(.success(data)) }

Важные моменты

  • Apple предоставляет имя и фамилию пользователя только один раз. В самый первый раз, когда пользователь входит через Apple. Обязательно отлавливайте эти данные и передавайте их на сервер в базу данных.

  • При авторизации есть возможность скрыть свой email (”Hide my email”), в таком случае Apple сгенерирует кастомный email, который будет привязан к пользователю. То есть совсем без email вы не останетесь, просто будет выбор из двух: персональная почта или почта apple.

Концовка

Теперь вам останется только обратиться к сервису в Interactor (если используете VIPER), и в комплишн хэндлере обработать события. Если успешно — отправляете запрос на сервер с нужными данными авторизации, если нет, выводите алерт. Плюс ко всему этому можно подключить аналитику.

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

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

Хорошего дня!


ссылка на оригинал статьи https://habr.com/ru/post/696646/

PostgreSQL: пример использования диапазонного типа данных при расчете коэффициента возраст-стаж в ОСАГО

В этой статье рассматриваются преимущества такого редко используемого и, на мой взгляд, незаслуженно обойденного вниманием типа данных, как диапазон. Мы сначала спроектируем структуру базы для хранения коэффициента возраст-стаж при расчете стоимости полиса ОСАГО в рамках привычной многим MySQL. Затем перепроектируем под PostgreSQL и посмотрим, как выглядят sql запросы в обоих случаях. И в финале сравним, какие преимущества дает нам использование диапазонов.

Заметка адресована как пользователям MySQL, так и пользователям PostgreSQL, которые не работали с таким типом данных в своей практике. Если в вашей предметной области есть работа с диапазонами величин, то этот пост точно для вас.

Коэффициент страховых тарифов

Для начала немного погрузимся в предметную область. Схема расчета регламентируется документом «Указание Банка России от 19 сентября 2014 г. № 3384-У». При расчете стоимости полиса берется определенная сумма, называемая «Базовый тариф», и умножается на несколько различных коэффициентов, которые могут как повышать конечную стоимость полиса, так и понижать ее. Один из них  – коэффициент страховых тарифов в зависимости от возраста и стажа водителя (КВС). Зависимость представлена в таблице:

Здесь мы видим диапазоны чисел по входным критериям (стаж и возраст), что располагает к использованию диапазонного типа данных. Но представим, что у нас в качестве РСУБД идет MySQL и что этот тип нам недоступен. Как будет выглядеть база? Спроектируем!

Структура базы для mysql

Сразу же в голову приходят два варианта реализации:

  • использование одной таблицы, в строках которой хранятся все варианты связок возраст-стаж-коэффициент;

  • использование трех таблиц, две из которых хранят диапазоны (одна для возраста, другая для стажа), а третья содержит связь возраст-стаж и значение коэффициента.

Рассмотрим оба этих варианта и заполним таблицы данными. Но для начала зададим граничные условия. Судя по википедии, самая старшая живая жительница планеты Люсиль Рандонлет в 2022 году дожила до 118 (Список старейших людей в мире). Права на управление можно получить с 16 лет. Потенциально водительский стаж Люсиль мог бы составлять 102 года. Для заполнения базы данными будем исходить из расчета: возраст от 16 до 118 лет, стаж вождения от 0 до 102 лет.

Теперь создадим нужные таблицы для обоих вариантов.

Одна таблица

Структура таблицы:

create or replace table kvs (     age        int           not null comment 'Возраст',     experience int           not null comment 'Стаж',     value      decimal(4, 2) not null comment 'Значение КВС',     primary key (age, experience) ) comment 'Коэффициент Возраст-Стаж';

Производим вставку данных. В результате получаем 9314 записи:

Составной индекс по полям age-experience убережет нас от ошибочной вставки дублей при будущих изменениях коэффициента. Запрос на получение коэффициента для водителя 23 лет и стажем вождения 3 года в этом случае будет иметь вид:

SELECT value FROM kvs WHERE age = 22 AND experience = 3

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

Три таблицы

Структура таблицы:

create table kvs_age_range (     `id`    integer not null AUTO_INCREMENT,     `from`  integer not null comment "Начало диапазона",     `to`    integer not null comment "Окончание диапазона",          primary key (id) ) comment 'Диапазоны возраста';   create table kvs_experience_range (     `id`    integer not null AUTO_INCREMENT,     `from`  integer not null comment "Начало диапазона",     `to`    integer not null comment "Окончание диапазона",          primary key (id) ) comment 'Диапазоны стажа';    create table kvs_value (     age_id        int           not null,     experience_id int           not null,     value         decimal(4, 2) null comment 'Значение КВС',          constraint kvs_value_age_id_experience_id_uindex         unique (age_id, experience_id),     constraint kvs_age_id___fk         foreign key (age_id) references kvs_age_range (id)             on delete cascade,     constraint kvs_experience_id___fk         foreign key (experience_id) references kvs_experience_range (id) ) comment 'Величина КВС';

Производим вставку данных:

Количество записей сократилось, но сам запрос получения значения КСВ усложнился:

SELECT value FROM kvs_value AS v     INNER JOIN kvs_age_range AS age ON v.age_id = age.id     INNER JOIN kvs_experience_range AS experience ON v.experience_id = experience.id WHERE         (age.`from` <= 22 AND age.`to` >= 22)     AND         (experience.`from` <= 3 AND experience.`to` >= 3)

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

Структура базы для PostgreSQL

Для PostgreSQL воспользуемся схемой с одной таблицей:

create table if not exists kvs (     age        int4range not null,     experience int4range not null,     value      numeric(4, 2),          exclude using gist (age WITH &&, experience WITH &&) ); comment on table kvs is 'Коэффициент Возраст-Стаж'; comment on column kvs.age is 'Диапазоны возраста'; comment on column kvs.value is 'Величина КВС';

(О конструкции EXCLUDE USING GIST см. чуть ниже.) Производим вставку данных:

Синтаксис вставки диапазонов:

INSERT INTO kvs VALUES ('[16,21]', '[0,0]', 1.87)

Как видим, в этом случае получается 50 записей (их можно сократить до 20, предлагаю подумать и написать в комментариях как это сделать). Ограничение-исключения EXCLUDE USING GIST убережет нас от ошибочной вставки пересекающихся диапазонов. Например, если мы попытаемся добавить новый коэффициент для возраста 16-30 и стажа 5-6 лет, то получим ошибку:

Запрос на получение коэффициента имеет вид:

SELECT value FROM kvs WHERE age @> 22 AND experience @> 3

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

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

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

Запрос

Контроль «непересечения»

Визуальное удобство работы

Одна таблица

простой

да

среднее

Три таблицы

сложный

нет

сложное

Таблица с диапазонами

простой

да

простое

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


ссылка на оригинал статьи https://habr.com/ru/company/regru/blog/696628/

Как аккуратно записать гифку с консоли

Помните, как вы пытались записать демонстрацию CLI-инструмента? К старту нашего курса по DevOps делимся материалом о том, как записывать гифки с консоли кодом, чтобы тестировать сборки ПО и показывать ваши консольные инструменты.

Этот пример сгенерирован с помощью VHS (посмотреть исходный код).

Руководство

Для начала установим VHS и создадим файл .tape:

vhs new demo.tape

Откроем файл .tape в вашем любимом $EDITOR:

vim demo.tape

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

# Куда записать гифку? Output demo.gif  # Установим размер консоли 1200x600 и шрифт 46 px. Set FontSize 46 Set Width 1200 Set Height 600  # Введём команду в консоль. Type "echo 'Welcome to VHS!'"  # Выдержим паузу для пущей драмы… Sleep 500 ms  # Запустим команду нажатием Enter. Enter  # Немного полюбуемся тем, что получилось. Sleep 5 s

Сохраним файл по готовности и скормим его VHS:

vhs < demo.tape

Готово! Смотрим новый файл demo.gif (или как вы назвали его командой Output) в директории:

В директории examples/ есть и другие примеры.

Установка

Для работы VHS требуется установка ttyd и ffmpeg в вашу директорию PATH.

Воспользуемся менеджером пакетов:

# macOS или Linux brew install charmbracelet/tap/vhs ffmpeg brew install ttyd --HEAD  # Arch Linux (btw) yay -S vhs ttyd ffmpeg  # Nix nix-env -iA nixpkgs.vhs nixpkgs.ttyd nixpkgs.ffmpeg  # Debian/Ubuntu sudo mkdir -p /etc/apt/keyrings curl -fsSL https://repo.charm.sh/apt/gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/charm.gpg echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *" | sudo tee /etc/apt/sources.list.d/charm.list # ttyd ставим отсюда: https://github.com/tsl0922/ttyd/releases sudo apt update && sudo apt install vhs ffmpeg  # Fedora/RHEL echo '[charm] name=Charm baseurl=https://repo.charm.sh/yum/ enabled=1 gpgcheck=1 gpgkey=https://repo.charm.sh/yum/gpg.key' | sudo tee /etc/yum.repos.d/charm.repo # ttyd ttyd ставим отсюда: https://github.com/tsl0922/ttyd/releases sudo yum install vhs ffmpeg

Или запустим VHS напрямую из Docker’а вместе с зависимостями:

docker run ghcr.io/charmbracelet/vhs <cassette>.tape

Или скачаем:

Или установим командой go:

go install github.com/charmbracelet/vhs@latest

Сервер VHS

У VHS есть встроенный SSH-сервер! Когда вы запускаете VHS как отдельное приложение, доступ к нему можно получить, как к приложению, установленному локально. VHS будут доступны команды и программы на хосте, ставить их на свою машину нужды нет.

Запускаем сервер командой:

vhs serve

Получаем доступ к VHS с любой машины через ssh:

ssh vhs.example.com < demo.tape > demo.gif

Команды VHS

Посмотреть всю документацию по VHS в командной строке поможет команда vhs manual.

Вот несколько основных типов команд VHS:

  • Output <путь>: задать местоположение и формат вывода файла;
  • Set <настройки> Value: задать параметры записи;
  • Type "<символы>": имитация набора текста;
  • Left Right Up Down: клавиши со стрелками;
  • Backspace Enter Tab Space: специальные клавиши;
  • Ctrl+<char>: клавиша с зажатым ctrl;
  • Sleep <время>: выждать указанное время;
  • Hide: скрыть выводимые команды;
  • Show: не скрывать выводимые команды.

Output

Команда Output позволяет задать местоположение и формат вывода файла для
визуализации. В файл .tape можно записать более одного местоположения для вывода файлов.

Output out.gif Output out.mp4 Output out.webm Output frames/ # директория с кадрами, представленными последовательностью PNG

Set

Команда Set позволяет менять общие настройки консоли: параметры шрифта, настройки окна и директорию для вывода GIF-файлов.

Настройки задаются выше уровня файла .tape. Любые настройки (кроме скорости набора — TypingSpeed) применяются после игнорирования всех команд, кроме указания настроек (Set) и вывода (Output).

Set FontSize (размер шрифта)

Размер шрифта задаём командой Set FontSize <число>:

Set FontSize 10 Set FontSize 20 Set FontSize 40

Set FontFamily (семейство шрифтов)

Семейство шрифтов задаём командой Set FontFamily "<шрифт>":

Set FontFamily "Monoflow"

Set Width (ширина)

Ширину консоли задаём командой Set Width:

Set Width 300

Set Height (высота)

Высоту консоли — командой Set Height:

Set Height 1000

Set LetterSpacing (межзнаковый интервал)

Межзнаковый интервал (разреженный/уплотнённый) задаём командой Set LetterSpacing:

Set LetterSpacing 20

Set LineHeight (высота строк)

Высоту строк задаём командой Set LineHeight:

Set LineHeight 1.8

Set TypingSpeed (скорость набора)

Set TypingSpeed 500 ms # 500 ms Set TypingSpeed 1 s    # 1 s

Этот параметр задаёт скорость набора в секундах после нажатия клавиши. Например, при значении 0.1 пауза между набором символов будет 0.1 s (100 ms).

Настройку можно изменить командой с синтаксисом вида @<время>.

Set TypingSpeed 0.1 Type "100ms delay per character" Type@500ms "500ms delay per character"

Set Theme (оформление)

Оформление консоли задаём командой Set Theme. Оформление текста (foreground) и фона (background) задаётся строковой переменной формата JSON, которая ограничена 16 базовыми цветами.

Set Theme { "name": "Whimsy", "black": "#535178", "red": "#ef6487", "green": "#5eca89", "yellow": "#fdd877", "blue": "#65aef7", "purple": "#aa7ff0", "cyan": "#43c1be", "white": "#ffffff", "brightBlack": "#535178", "brightRed": "#ef6487", "brightGreen": "#5eca89", "brightYellow": "#fdd877", "brightBlue": "#65aef7", "brightPurple": "#aa7ff0", "brightCyan": "#43c1be", "brightWhite": "#ffffff", "background": "#29283b", "foreground": "#b3b0d6", "selectionBackground": "#3d3c58", "cursorColor": "#b3b0d6" }

Set Padding (длина пробела)

Длину пробела консоли (в пикселях) задаём командой Set Padding:

Set Padding 0

Set Framerate (частота кадров)

Частоту захвата кадров VHS — командой Set Framerate:

Set Framerate 60

Set PlaybackSpeed (скорость воспроизведения)

Скорость воспроизведения выходного файла задаём командой Set Framerate:

Set PlaybackSpeed 0.5 # Замедлим вывод в 2 раза Set PlaybackSpeed 1.0 # Вернём нормальную скорость(заданную по умолчанию) Set PlaybackSpeed 2.0 # Ускорим вывод в 2 раза

Type (имитация набора)

Команда Type позволяет имитировать нажатия клавиш. Type можно использовать для скриптов с вводом текста в консоль. Это удобно как для ввода команд, так и для взаимодействия с подсказками и текстовым пользовательским интерфейсом в консоли. Команда принимает строковый аргумент из символов, которые нужно набрать.

Стандартная скорость ввода задаётся командой Set TypingSpeed, но аргумент @time имеет приоритет над этой командой.

# Напечатаем что-нибудь Type "Whatever you want"  # Напечатаем что-нибудь очень медленно! Type@500ms "Slow down there, partner."

Клавиши

Команды для клавиш принимают необязательный атрибут @time и имеют необязательный счётчик повторений count для повторения нажатия клавиши через каждый интервал вида <время>:

Key[@<time>] [count]

Backspace

Нажатие клавиши backspace задаём командой Backspace:

Backspace 18

Ctrl

Вы можете получить доступ к модификатору ctrl и отправлять последовательности ctrl по командое Ctrl:

Ctrl+R

Enter

Нажатие клавиши enter задаём командой Enter:

Enter 2

Клавиши со стрелками

Нажатие одной из клавиш со стрелками задаём командой Up, Down, Left или Right:

Up 2 Down 2 Left Right Left Right Type "B" Type "A"

Tab

Нажатие клавиши tab задаём командой Tab:

Tab@500ms 2

Пробел

Нажатие пробела — командой Space:

Space 10

Sleep

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

Sleep 0.5   # 500 милисекунд Sleep 2     # 2 секунды Sleep 100 ms # 100 милисекунд Sleep 1 s    # 1 секунда

Hide (скрыть)

Команда Hide позволяет исключить из выходного файла любые команды:

Hide

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

Output example.gif  # Настройка Hide Type "go build -o example . && clear" Enter Show  # Запись... Type 'Running ./example' ... Enter  # Очистка кода Hide Type 'rm example'

Show (показать)

Команда Show позволяет указать, что следующие за ней команды нужно снова включить в выходной файл. Так как по умолчанию все команды являются «видимыми», эта команда может потребоваться только после использования Hide:

Hide Type "You won't see this being typed." Show Type "You will see this being typed."


Непрерывная интеграция

VHS можно соединить с вашим конвейером непрерывной интеграции (CI), чтобы ваши гифки обновлялись вместе с официальным VHS GitHub Action:

charmbracelet/vhs-action

VHS может использоваться и для тестирования интеграции. Используйте выходной формат .txt ли .ascii для генерации golden-файлов. Храните эти файлы в репозитории git, чтобы не было различий между отдельными запусками файла .tape:

Output golden.ascii

Подсветка синтаксиса

При работе с файлами .tape в редакторах с поддержкой Tree-sitter доступна «грамматика» Tree-sitter:

charmbracelet/tree-sitter-vhs

Она отлично работает в Neovim, Emacs и многом другом!

Обратная связь

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

А мы научим работать с Python и другими инструментами, чтобы вы прокачали карьеру или стали востребованным IT-специалистом:

Чтобы посмотреть все курсы, кликните по баннеру:


ссылка на оригинал статьи https://habr.com/ru/company/skillfactory/blog/696564/

Дайджест недели от Apple Pro Weekly News (24.10 – 30.10.22)

Вышли в релиз новые системы, обновления софта и облачных продуктов от Apple, сколько заработала компания и почему поднимают цены на сервисы, будет ли USB-C в новых iPhone, когда свежие мощные MacBook Pro и другие слухи. Прошедшая неделя вышла очень богатой на события из яблочного мира, приступим к новостям!

iOS 16.1, iPadOS 16.1, watchOS 9.1, macOS 13.0 и tvOS 16.1 – в релизе, а ещё обновления для старых устройств.

На прошлой неделе вышли долгожданные релизные обновления систем для всех пользователей. Для iPad и Mac это первые обновления в новом сезоне.

• iOS 16.1 (20B82)

• iPadOS 16.1 (20B82)

• tvOS 16.1 (20K71)

• HomePod OS 16.1 (20K71)

• watchOS 9.1 (20S75)

• macOS Ventura 13.0 (22A380)

Релиз iOS 16.1 ≠ iOS 16.1 RC, поэтому обновление требуется для всех.

Обновления систем Apple уже доступны к установке для всех пользователей
Обновления систем Apple уже доступны к установке для всех пользователей

А также вышли обновления для старых систем:

• iOS 15.7.1 (19H117)

• iPadOS 15.7.1 (19H117)

• macOS Monterey 12.6.1 (21G217)

• macOS Big Sur 11.7.1 (20G918)

Обновления доступны на всех поддерживаемых устройствах. В этих обновлениях закрывается ряд существенных уязвимостей и ошибок.

А например, в iOS 16.1 и macOS Ventura была исправлена серьезная уязвимость, позволявшая любому приложению с доступом к Bluetooth записывать ваши аудиозапросы Siri и аудио с функции диктовки клавиатуры при использовании наушников AirPods или Beats. Запись могла происходить без запроса разрешения доступа и следов того, что приложение использовало микрофон.

Кстати, имейте в виду, что это обновление не подписывается для установки или отката через образ ipsw для тех устройств, которым доступна iOS 16. А вот если у вас стоит iOS 15.7 – на новую сборку можно обновиться только по воздуху (OTA).
Кстати, имейте в виду, что это обновление не подписывается для установки или отката через образ ipsw для тех устройств, которым доступна iOS 16. А вот если у вас стоит iOS 15.7 – на новую сборку можно обновиться только по воздуху (OTA).

Одной из долгожданных фишек новых iOS 16.1 можно назвать обновлённый индикатор заряда и для устройств, которые его не получили ранее: iPhone XR/11/12mini/13mini, а ещё поддержка подписки Fitness+ для всех, даже если у пользователя не имеется яблочных часов. Более подробные описания новых систем можно прочесть в соответствующих публикациях:

iOS 16.1 – Что нового внутри?

iPadOS 16.1 – На каких iPad поддерживается и ради чего можно обновиться.

Новшества в watchOS 9.1.

macOS Ventura 13.0 – Новинка для Mac, что внутри?

Обновление для tvOS и HomePod до версии 16.1 и почему стоит обновиться.

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

Для пользователей Windows есть хорошая новость: Apple обновила приложение iTunes для Windows до версии 12.12.6, в новой версии добавлена поддержка новых моделей iPad и других новых устройств.

Маководам не понять!
Маководам не понять!

Новая очередь бета-версий систем от Apple и не только.

Тут же на прошлой неделе спустя сутки после релизов, стали доступны новые бета-версии для разработчиков:

• iOS 16.2 Beta 1 (20C5032e)

• iPadOS 16.2 Beta 1 (20C5032e)

• watchOS 9.2 Beta 1 (20S5331e)

• macOS Ventura 13.1 Beta 1 (22C5033e)

• tvOS 16.2 Beta 1 (20K5331f) 

• HomePod 16.2 Beta 1 (20K5331f)

Всё упомянутые системы выпущены также для участников программы публичного тестирования.

Бета-версии устанавливаются с помощью специального профиля для разработчиков и тестировщиков
Бета-версии устанавливаются с помощью специального профиля для разработчиков и тестировщиков

А вот что новое в бетах macOS Ventura 13.1, iOS 16.2 и iPadOS 16.2:

С этим обновлением стало доступно новое приложение Freeform – представляющее собой бесконечную доску для совместной работы. Оно включает в себя два новых вида кисти: тюбик с регулируемой толщиной линии и мелок нового вида.

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

 А в бете iPadOS 16.2 запущена поддержка внешнего монитора при использовании Stage Manager.

Stage Manager сможет работать на внешних мониторах c iPad даже в полноэкранном режиме, а не как ранее в формате 4:3 даже на самом широком внешнем дисплее.
Stage Manager сможет работать на внешних мониторах c iPad даже в полноэкранном режиме, а не как ранее в формате 4:3 даже на самом широком внешнем дисплее.

В обновлениях Apple также представила новую архитектуру приложения «Дом» с поддержкой стандарта Matter, как и его поддержка, начавшаяся в iOS 16.1 и других релизных обновлениях. Для обновления требуется, чтобы все устройства (включая хабы tv и HomePod) были на последних бетах.

Кстати, это первое упоминание от самой Apple, что HomePod работает под управлением tvOS
Кстати, это первое упоминание от самой Apple, что HomePod работает под управлением tvOS

Кстати, функция распознавания голоса пользователя для Siri и персонализации системы стала доступна на iPhone, iPad и tv, ранее распознавание голоса было доступны только у Siri на HomePod. А в watchOS 9.2 изменились аудио сигналы для «Рации». Кроме того, в iOS 16.2 beta были добавлены виджеты Здоровья для локскрина: «Сон» – со сведениями, расписанием и стадиями недавнего сна и «Лекарства», который появится в ближайшей бете. Из нового замечена также возможность настроить более частые обновления информации Live Activities в реальном времени, однако это увеличит энергопотребление.

Виджет «Сон» работает даже на Always-On Display
Виджет «Сон» работает даже на Always-On Display

По слухам, в релиз эта самая iOS 16.2 ожидается в середине декабря. И ещё стало известно о планах выпустить iOS 16.3 в феврале-марте 2023 года.

Помимо новых системных бета-версий, на неделе стала доступна и новая бета для сайта iCloud. Apple его тестирует публично и оценить новый дизайн портала iCloud могут все: beta.icloud.com

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

Обновления софта: новые версии приложений iWork и приложения Поддержки Apple

Давно Apple не совершенствовала свои приложения, вот и обновили приложение «Поддержка»:

Освежили интерфейс и доработали пункты выбора локации для обращения за поддержкой
Освежили интерфейс и доработали пункты выбора локации для обращения за поддержкой

• Теперь вам будет проще найти инструменты службы поддержки и получить помощь в использовании продуктов Apple;

• Интерфейс для резервирования встреч получил визуальные обновления, а также расширенные возможности сортировки и фильтрации;

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

А ещё свежие обновления получили iOS-приложения для офисной работы от Apple:

Также у приложений повысилась минимальная поддерживаемая версия системы до 15.0
Также у приложений повысилась минимальная поддерживаемая версия системы до 15.0

В обновлениях Pages, Numbers и Keynote содержатся новые опции, включая функционал для совместной работы по FaceTime или iMessage.

Стартовали продажи новых iPad. Уже есть интересные детали и подробности.

В странах первой волны стартовали продажи новых iPad 10-го поколения и iPad Pro с чипом M2. На просторах YouTube уже есть первые обзоры новых планшетов. А обои с этих устройств уже есть в нашем Telegram-канале:

Обои iPad 10-го поколения. Обои iPad Pro с чипом M2.

Тим Кук

CEO Apple

«Одна из многих вещей, которые мне нравятся в iPad – это творческий потенциал, который он вдохновляет, и то, какой огромный потенциал он раскрывает. Мне не терпится увидеть, что люди делают с iPad и iPad Pro следующего поколения — они уже доступны!»

Со стартом продаж стало известно о некоторых деталях новых iPad, например, у iPad Pro на M2 в цвете Space Gray корпус заметно светлее предшественников. Также у динамиков в очередной раз сократилось количество отверстий.

Отличить новинку можно теперь не только по гравировке на задней крышке, но и по отверстиям динамиков
Отличить новинку можно теперь не только по гравировке на задней крышке, но и по отверстиям динамиков

Ещё новые iPad Pro на чипе M2, как известно, умеют записывать видео ProRes, но не через стандартное приложение камеры – переключателя для этого даже нет в настройках. Запустить запись в ProRes можно пока только в сторонних приложениях, типа FiLMiC Pro. Вероятно, ситуация будет исправлена в ближайших обновлениях.

Функция есть, возможность есть, но пока в стороннем софте
Функция есть, возможность есть, но пока в стороннем софте

Также компания выпустила документ техподдержки про использование сетей Wi-Fi 6E на новых iPad Pro, в том числе как отключить 6GHz в случае возникновения проблем с подключением. А такие ситуации будут, в виду того, что стандарт не поддерживается на ряде устройств и аксессуаров.

Проблемы WiFi 6E они такие!
Проблемы WiFi 6E они такие!

iPad 10-го поколения также получил свои нюансы. Добавленный в него порт USB-C оказался практически просто для зарядки и утешения просьбам Еврокомиссии, обязывающей в ближайшие годы перейти на этот порт. Порт USB-C у iPad 10-го поколения ограничен скоростью передачи данных USB 2.0, сопоставимой Lightning. А вот как выглядит это в близком сравнении:

 • iPad Pro 2022: до 40 Gbps

 • iPad Air 5: до 10 Gbps

 • iPad mini 6: до 5 Gbps

 • iPad 10: до 480 Mbps

 • iPad 9: до 480 Mbps (Lightning)

Подключайте аксессуары по USB-C - говорит Apple, ага
Подключайте аксессуары по USB-C — говорит Apple, ага

Другая проблема этого базового iPad в виде процесса зарядки Pencil через переходник терпит фиаско уже на первой неделе продаж: в большинстве Apple Store были приостановлены продажи Pencil 1-го поколения пока не поступит следующая партия переходников с USB-C на Pencil.

Ещё никогда порисовать на iPad с помощью яблочного карандаша не была таким увлекательным квестом
Ещё никогда порисовать на iPad с помощью яблочного карандаша не была таким увлекательным квестом

Apple сообщила о результатах четвёртого финансового квартала 2022 года

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

Разбивка результатов за квартал по продуктам:

• Доход от iPhone: $42,6 млрд

• Сервисы: $19,2 млрд

• Mac: $11,5 млрд

• Носимые устройства/ умный дом: $9,7 млрд

• iPad: $7,2 млрд 

В прошедшем квартале (по 25.09) доходность у сервисов и iPad упала. Лучший показатель у iPhone и Mac.
В прошедшем квартале (по 25.09) доходность у сервисов и iPad упала. Лучший показатель у iPhone и Mac.

Общий доход компании за Q4 2022 составил: $90,1 миллиарда, что на 8% больше по сравнению с предыдущим годом.

Квартальная прибыль на акцию компании составила $1,29 (это на 4% больше, чем в прошлом году, но на 5% меньше, чем в прошлом квартале). Совет директоров Apple объявил дивиденды в размере $0,23/акция. Они будут выплачены 10 ноября акционерам, зарегистрированным по состоянию на 7 ноября.

Тим Кук

CEO Apple

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

Сервисы становятся более насыщенными и богатыми на разнообразие. Наша постоянная аудитория сервисов превысила 900 миллионов платных подписчиков!

Популярность iPhone 14 Pro настолько высока, что мы всё ещё не успеваем покрывать спрос: производство новинок получило дополнительные мощности в результате их перераспределения. При этом отмечу сильный рост продаж iPhone в Индии, Таиланде, Вьетнаме, Мексике и Индонезии.

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

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

Тим Кук, ты чего такой счастливый? Apple is doooomed же! 😅
Тим Кук, ты чего такой счастливый? Apple is doooomed же! 😅

Apple впервые в истории повышает цены на свои сервисы в США

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

А как вы думали они зарабатывают $19,2 млрд на сервисах? Вот.
А как вы думали они зарабатывают $19,2 млрд на сервисах? Вот.

Music:

Индивидуальный тариф: 

$9,99 ➡️ $10,99 /мес

Семейный тариф: 

$14,99 ➡️ $16,99 /мес

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

tv+

$4,99 ➡️ $6,99 /мес

В случае с tv+ причина в очень низкой цене на старте, потому 3 года спустя в виду обширной библиотеки контента, Apple решает поднять цену.

One

Индивидуальный тариф: 

$14,95 ➡️ $16,95 /мес

Семейный тариф: 

$19,95 ➡️ $22,95 /мес

Premier тариф: 

$29,95 ➡️ $32,95 /мес

Компания также продолжит добавлять инновационные функции, которые делают сервисы лучшими в мире. Остальных сервисов повышение цены в США не коснётся.

Apple обновила правила App Store для разработчиков приложений

На прошлой неделе обновлены правила яблочного магазина приложений для разработчиков, в которых компания добавила новые и ужесточила существующие пункты. Это самое масштабное изменение для разработчиков iOS/iPadOS за последнее время, охватывающее ряд новых сфер.

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

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

Также изменения коснулись приложений с NFT – ими разрешено торговать, но с условием, что с каждой продажи такой единицы виртуального токена, проведённой через внутреннюю систему платежей (есть варианты заключить сотрудничество с лицензированными биржами) – разработчик отчислит Apple комиссию 30%. Все покупки виртуального контента и услуг по его продвижению или даже распространению на внешних сайтах, при условии оформления внутри приложения – теперь также взимается комиссия 30% за каждую продажу услуги или единицы контента, оплаченной через систему платежей Apple. А ещё приложениям нельзя реализовывать свои собственные механизмы для разблокировки единиц виртуального контента или внутренней функциональности, таких как лицензионные ключи, маркеры дополненной реальности, QR-коды, криптовалютные ресурсы. 

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

Запущена новая программа Security Research от Apple, есть Bounty

Apple представила сайт Security Research, посвящённый совершенствованию методов, доступных исследователям в области безопасности. Сайт предлагает инструменты для отправки отчетов о безопасности Apple, получения обновлений статуса в режиме реального времени и общения с инженерами Apple.

На сайте также есть блог, в котором инженеры компании делятся последними достижениями в области безопасности, например, прогрессом программы Apple Security Bounty, по которой за последние 2,5 года исследователям было выплачено около $20 млн. 

До 30 ноября 2022 года Apple принимает заявки на участие в программе Apple Security Research Device Program 2023 года, по которой квалифицированным специалистам предоставляется iPhone на специальной версии iOS для упрощения поиска уязвимостей.

Слухи: iPad с экраном 16”, iPhone 15 Ultra без физических кнопок и когда новые MacBook Pro

Рубрика «Слухи» начинается в этот раз отнюдь не со слухов, а с реального заявления Грега Джозвяка, вице-президент компании по маркетингу. В интервью The Wall Street Journal он косвенно подтвердил, что Apple придётся подчиниться требованию ЕС и перевести iPhone и другие продукты на USB-C уже в ближайшие пару лет. Хотя в компании этому не очень рады.

Давно пора уходить от Lightning, технология достаточно устарела по всем параметрам
Давно пора уходить от Lightning, технология достаточно устарела по всем параметрам

Получается, что возможно USB-C будет одной из фишек iPhone 15, а вот другой фишкой новых iPhone 15 Ultra (а возможно и версии Pro), по слухам станет отсутствие физических кнопок на корпусе. Скорее всего, новинки получат новые кнопки громкости и блокировки – физические кнопки будут заменены на сенсорные панели с тактильной отдачей (наподобие кнопки «Домой», которая появилась в iPhone 7). Будет добавлено два дополнительных вибромотора Taptic Engine слева и справа для имитации физического нажатия на кнопки.

А что если таким образом Apple внедрит функцию Touch ID?
А что если таким образом Apple внедрит функцию Touch ID?

Помните слухи про iPad с экраном 20“? Apple планирует ещё выпустить iPad с экраном диагональю 16“ уже в четвертом квартале 2023 года.

Такая новинка с намного большим дисплеем отлично подойдёт для работы с той самой облегчённой версией macOS 14, которая по другим слухам будет адаптирована под iPad
Такая новинка с намного большим дисплеем отлично подойдёт для работы с той самой облегчённой версией macOS 14, которая по другим слухам будет адаптирована под iPad

Согласно последним слухам, выход новых MacBook Pro 14“ и 16“, а также Mac mini на новых процессорах M2 Pro и M2 Max откладывается до марта следующего года.

Всему виной кремниевый кризис при производстве чипов. Весной 2023 ожидается целый ряд новых компьютеров Mac. Будем ждать новинок, тем более, что имеются слухи о неком рекордно мощном Mac Pro на чипах Apple Silicon.

А пока же в продажу поступили восстановленные MacBook Air на M2. Выгода при их покупке до $180.

Восстановленные MacBook Air на чипе M2 доступны только в минимальных конфигурациях и не во всех цветах
Восстановленные MacBook Air на чипе M2 доступны только в минимальных конфигурациях и не во всех цветах

В Mac App Store вышла легендарная игра, анонсированная ещё на презентации

Во что сыграть на новых MacBook Air с чипом M2? Ответ уже в Mac App Store: «Resident Evil Village» уже доступна для Mac с чипами серии M. Цена игры в российском магазине – 3790₽. Дополнений пока мало, большую часть подвезут позднее. Напоминаем, что оплатить покупки в App Store можно при помощи баланса аккаунта Apple ID (пополняется с помощью подарочных карт) или с помощью привязки номера телефона (только МТС и билайн) как счёт оплаты в App Store.

Легендарная игра вышла на Mac с чипами Apple Silicon
Легендарная игра вышла на Mac с чипами Apple Silicon

Приятного времяпрепровождения тем, кто решится поиграть! А остальных приглашаем в наши социальные сети, где ещё больше новостей про Apple (и не только) хороших и разных:

Twitter / Telegram / VK


ссылка на оригинал статьи https://habr.com/ru/post/696652/

Как я сделал самый быстрый в мире файловый сервер


Задача — среди множества файлов найти на диске конкретный и отдать его по HTTP с заголовками «content-encoding», «mime-type» и «content-lenght». И сделать это как можно быстрее — на локальном хосте, чтобы не уткнуться в физические барьеры. Нас интересует скорость ради скорости.

В качестве веб-сервера будем использоваться Kestrel, .NET 7 RC 1, minimal API и F#. Финальная, оптимизированная версия есть и для C#.

Старт

let p =     (Directory.GetCurrentDirectory(), "wwwroot")     |> Path.Combine  WebApplicationOptions(WebRootPath = p) |> WebApplication.CreateBuilder |> function     | prod when prod.Environment.IsProduction() ->         prod.WebHost.UseKestrel(fun i ->             i.ListenAnyIP(5001)             i.AddServerHeader <- false)         |> ignore          prod.Logging.ClearProviders() |> ignore         prod      | dev ->         dev.Logging.AddConsole().AddDebug() |> ignore         dev  |> fun b -> b.Build() |> fun w ->     w.UseFileServer() |> ignore     w.Run() 

Самый простой способ запустить файловый сервер на дотнете — это использовать встроенный в kestrel файловый сервер. Код вы видите выше.

Для снятия дополнительных данных — используется BenchmarkDotnet с аппаратными счётчиками.

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

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

|          | K6   | Autocannon | Bombardier | |----------|------|:-----------|------------| | Avg. RPS | 4704 | 4596       | 6415.2     |

Для тестирования применялись файлы размером 0 байт, 3,5КБ, 22КБ, 31КБ, 130 и 417КБ в 1, 8 и 125 потоков.

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

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

|                   Method |      Mean |    Error |   StdDev | CacheMisses/Op | BranchMispredictions/Op | LLCReference/Op | |------------------------- |----------:|---------:|---------:|---------------:|------------------------:|----------------:| |                     Base | 170.10 us | 1.328 us | 1.243 us |            141 |                     328 |          18,083 |

Загружаем контент в оперативную память

image

type File =     { Bytes: byte array       ContentEncoding: StringValues       Mime: string }  let p =     (Directory.GetCurrentDirectory(), "wwwroot")     |> Path.Combine  let extractMime x = "image/webp" let extractFileName (x: string) = x.LastIndexOf "\\" + 1 |> x.Substring let dict = Dictionary()  p |> Directory.GetFiles |> Seq.iter (fun i ->     (extractFileName i,      { Bytes = File.ReadAllBytes i        ContentEncoding = StringValues "none"        Mime = extractMime i })     |> dict.Add)

Dictionary — самая быстрая коллекция в дотнете, поэтому её буду использовать для нахождения файлов. Загружаться байты будут во время старта приложения.

Простоты ради, Content-Type пусть будет — «image/webp», а Content-Encoding — “none”.

Закатываем всё это в рекорд и добавляем в Dictionary.

type FileResult(mime, encoding, stream: byte array) =     interface IResult with         member this.ExecuteAsync(ctx: HttpContext) =             ctx.Response.ContentType <- mime             ctx.Response.Headers.ContentEncoding <- encoding             ctx.Response.ContentLength <- stream.LongLength             ctx.Response.Body.WriteAsync(stream, 0, stream.Length)  let FileResult x = FileResult x :> IResult  type IResultExtensions with     member this.FileResult = FileResult   let file (i: string) =     match dict.TryGetValue i with     | false, _ -> Results.NotFound()     | true, x -> Results.Extensions.FileResult(x.Mime, x.ContentEncoding, x.Bytes)   // ......  |> fun b -> b.Build() |> fun w ->     w.MapGet("/{i}", Func<string, IResult>(fun i -> Files.file i))     |> ignore      w.Run()

Я сделал всё по гайду, заимплементил IResult, заэкстендил интерфейс, всё, как написано тут.

Максимальный RPS на файле размером в 0 байт вырос до 26852,44, файл размером 31КБ в один поток скачивается 5858.85 раз с общей скоростью 573,77МБ/с, и 13869.81 раз со скоростью 5,2ГБ/с в 125 потоков.

Это 4,6 гигабит в один поток и 41 гигабит соответственно.

|                   Method |      Mean |    Error |   StdDev | CacheMisses/Op | BranchMispredictions/Op | LLCReference/Op | |------------------------- |----------:|---------:|---------:|---------------:|------------------------:|----------------:| |                     Base | 170.10 us | 1.328 us | 1.243 us |            141 |                     328 |          18,083 | |                 InMemory |  51.67 us | 1.028 us | 1.142 us |            101 |                     193 |          16,888 |

Таблицы

image

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

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

Фундаментально, мало что изменилось, мы всё ещё обращаемся в разные места в памяти, просто адреса берём их из тела функции.

let mimes = files |> Array.map extractMime  let encodings =     Array.init p.Length (fun _ -> StringValues "none")  let bytes =     files |> Array.map File.ReadAllBytes  // ......  let file (i: string) =     match dict.TryGetValue i with     | false, _ -> Results.NotFound()     | true, idx -> Results.Extensions.FileResult(mimes.[idx], encodings.[idx], bytes.[idx])

RPS увеличился на 100 и 150 для 1 и 125 потоков соответственно.

|                   Method |      Mean |    Error |   StdDev | CacheMisses/Op | BranchMispredictions/Op | LLCReference/Op | |------------------------- |----------:|---------:|---------:|---------------:|------------------------:|----------------:| |                     Base | 170.10 us | 1.328 us | 1.243 us |            141 |                     328 |          18,083 | |                 InMemory |  51.67 us | 1.028 us | 1.142 us |            101 |                     193 |          16,888 | |                   Tables |  51.56 us | 0.931 us | 0.870 us |            101 |                     185 |          16,119 |

Не все методы одинаковы

▍ 1. Убираем расширение интерфейса

let FileResult x = FileResult x :> IResult  //type IResultExtensions with //    member this.FileResult = FileResult  let file (i: string) =     match dict.TryGetValue i with     | false, _ -> Results.NotFound()     //| true, idx -> Results.Extensions.FileResult(mimes.[idx], encodings.[idx], bytes.[idx])     | true, idx -> FileResult(mimes.[idx], encodings.[idx], bytes.[idx])

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

Максимальный RPS вырос до ещё на 271.2, до 27131.54. Пропускная способность в один поток не изменилась, зато раздача в 125 потоков ускорилась на 30МБ/с.

|                   Method |      Mean |    Error |   StdDev | CacheMisses/Op | BranchMispredictions/Op | LLCReference/Op | |------------------------- |----------:|---------:|---------:|---------------:|------------------------:|----------------:| |                     Base | 170.10 us | 1.328 us | 1.243 us |            141 |                     328 |          18,083 | |                 InMemory |  51.67 us | 1.028 us | 1.142 us |            101 |                     193 |          16,888 | |                   Tables |  51.56 us | 0.931 us | 0.870 us |            101 |                     185 |          16,119 | | Tables_WithoutInterfaces |  52.04 us | 0.640 us | 0.599 us |            108 |                     175 |          16,251 |

▍ 2. Task вместо IResult

let file (i: string) (ctx: HttpContext) =     match dict.TryGetValue i with     | false, _ ->         ctx.Response.StatusCode <- 404         Task.CompletedTask     | true, idx ->         ctx.Response.ContentType <- mimes.[idx]         ctx.Response.Headers.ContentEncoding <- encodings.[idx]         let b = bytes.[idx]         ctx.Response.ContentLength <- b.LongLength         ctx.Response.Body.WriteAsync(b, 0, b.Length)

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

Так мы ускоряемся ещё на 410.53 RPS, до 27542.07. Раздача в один поток ускорилась на 10МБ/с, а раздача в 125 потоков, на 16МБ/с.

|                   Method |      Mean |    Error |   StdDev | CacheMisses/Op | BranchMispredictions/Op | LLCReference/Op | |------------------------- |----------:|---------:|---------:|---------------:|------------------------:|----------------:| |                     Base | 170.10 us | 1.328 us | 1.243 us |            141 |                     328 |          18,083 | |                 InMemory |  51.67 us | 1.028 us | 1.142 us |            101 |                     193 |          16,888 | |                   Tables |  51.56 us | 0.931 us | 0.870 us |            101 |                     185 |          16,119 | | Tables_WithoutInterfaces |  52.04 us | 0.640 us | 0.599 us |            108 |                     175 |          16,251 | |              Tables_Task |  51.40 us | 0.757 us | 0.708 us |            114 |                     190 |          16,730 |

▍ 3. Только Httpcontext

//let extractFileName (x: string) = x.LastIndexOf "\\" + 1 |> x.Substring let extractFileName (x: string) =     "/" + (x.LastIndexOf "\\" + 1 |> x.Substring)  // ......  //let file (i: string) (ctx: HttpContext) = let file (ctx: HttpContext) =     //match dict.TryGetValue i with     match dict.TryGetValue ctx.Request.Path.Value with     | false, _ ->         ctx.Response.StatusCode <- 404         Task.CompletedTask     | true, idx ->         ctx.Response.ContentType <- mimes.[idx]         ctx.Response.Headers.ContentEncoding <- encodings.[idx]         let b = bytes.[idx]         ctx.Response.ContentLength <- b.LongLength         ctx.Response.Body.WriteAsync(b, 0, b.Length)

Функция возвращающая таск, хоть и быстрее IResult, она аллоцирует String на каждый новый вызов. Мы можем брать путь из HttpContext’а, и работать с ним напрямую.

И bombardier не показывает никакого измеримого результата.

|                   Method |      Mean |    Error |   StdDev | CacheMisses/Op | BranchMispredictions/Op | LLCReference/Op | |------------------------- |----------:|---------:|---------:|---------------:|------------------------:|----------------:| |                     Base | 170.10 us | 1.328 us | 1.243 us |            141 |                     328 |          18,083 | |                 InMemory |  51.67 us | 1.028 us | 1.142 us |            101 |                     193 |          16,888 | |                   Tables |  51.56 us | 0.931 us | 0.870 us |            101 |                     185 |          16,119 | | Tables_WithoutInterfaces |  52.04 us | 0.640 us | 0.599 us |            108 |                     175 |          16,251 | |              Tables_Task |  51.40 us | 0.757 us | 0.708 us |            114 |                     190 |          16,730 | |       Tables_TaskNoAlloc |  50.95 us | 0.614 us | 0.575 us |            105 |                     184 |          16,423 |

▍ 4. Своя мидлварь

image

Стандартный роутинг, осуществляется сопоставлением двух строк, пути из URL и HTTP метода. Такое количество проверок для файлового сервера, объективно избыточно.

Можно написать свою мидлварь, которая будет перехватывать и обрабатывать подходящие запросы. Это быстрее, но мы больше не отличаем PUT, DELETE и POST, всё будет обрабатываться как GET, про CORS можно забыть.

Для создания своей мидлвари нам на выбор предлагаю два оверлоада, «Func<HttpContext,Func,Task>» и «Func<HttpContext,RequestDelegate,Task>»

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

Вот так выглядит готовая мидлварь:

let file (ctx: HttpContext) ( next: RequestDelegate) =     match dict.TryGetValue ctx.Request.Path.Value with     | false, _ -> next.Invoke ctx          | true, idx ->         ctx.Response.ContentType <- mimes.[idx]         ctx.Response.Headers.ContentEncoding <- encodings.[idx]         let b = bytes.[idx]         ctx.Response.ContentLength <- b.LongLength         ctx.Response.Body.WriteAsync(b, 0, b.Length)

Вот так выглядит её регистрация:

|> fun b -> b.Build() |> fun w ->     w.Use Files.file |> ignore     w.UseRouting() |> ignore     w.Run()

Опустив лишние проверки, RPS увеличился на 985.48, до 28527.55. Раздача файлов в 1 поток ускорилась ещё на 6МБ/с, а в 125 потоков, на 21МБ/с.

|                   Method |      Mean |    Error |   StdDev | CacheMisses/Op | BranchMispredictions/Op | LLCReference/Op | |------------------------- |----------:|---------:|---------:|---------------:|------------------------:|----------------:| |                     Base | 170.10 us | 1.328 us | 1.243 us |            141 |                     328 |          18,083 | |                 InMemory |  51.67 us | 1.028 us | 1.142 us |            101 |                     193 |          16,888 | |                   Tables |  51.56 us | 0.931 us | 0.870 us |            101 |                     185 |          16,119 | | Tables_WithoutInterfaces |  52.04 us | 0.640 us | 0.599 us |            108 |                     175 |          16,251 | |              Tables_Task |  51.40 us | 0.757 us | 0.708 us |            114 |                     190 |          16,730 | |       Tables_TaskNoAlloc |  50.95 us | 0.614 us | 0.575 us |            105 |                     184 |          16,423 | |        Tables_Middleware |  48.95 us | 0.977 us | 1.045 us |            109 |                     181 |          16,031 |

Знакомьтесь — Unsafe

▍ Unsafe.As

image

Эта штука позволяет нам переинтерпретировать одни данные как другие. Например, можно прочитать string как byte[], не без ограничений.

Если взять строку и попытаться её перечислить как массив, то мы сможем перечислить его по индексам, через for или foreach, но если запустить enumerator, то приложение упадёт с (Fatal error. Internal CLR error. (0x80131506).

Тут нужно знать, какие методы каких данных совместимы друг с другом. Энуметарторы string и 'T[] несовместимы.

image

Тоже самое касается структур. Мы не можем представить int64 как byte[], однако каждый байт в этом int64 можно прочитать отдельно, если переинтерпретировать int64 как другую структуру.

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

▍ Unsafe.Add

image

[<Test>] let MutateString () =     let mutable s = "abcde"     let mutable res: char array = Unsafe.As &s      let ref =         &MemoryMarshal.GetArrayDataReference res      // заголовок массива это 8 байт     // заголовок строки это 4 байта     // нужно сдвинуть поинтер влево     // ещё на 2 char'a, то есть на 4 байта     for i = -2 to s.Length - 3 do         Unsafe.Add(&ref, i) <- '-'      printfn "%s" s     Assert.AreEqual("-----", s)

Тут начинается магия работы с поинтерами. Вот этот код может мутировать иммутабельное, представив string как, char[].

Сместив поинтер ещё на 4 байта влево, можно читать и редактировать заголовок, изменяя длину строки. В этом примере я изменил содержимое строки.

Больше можно не заморачиваться с voidptr’ами и intptr’ами. К тому же — такой способ просто быстрее, чем использование fixed. Так вот, к чему я это?

Закатываем всё в один массив

image

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

Давайте представим себе, что первые 8 байт массива это long обозначающий длину заголовка Content-Encoding, последующие N+1 байт — это длина заголовка Content-Type, следующие за ними N+1 байты это Content-Length и массив байтов за ним это байты файла lenna.webp. Как на картине.

let enc = UTF8Encoding false  [<Struct>] type LongBytes =     { A: byte       B: byte       C: byte       D: byte       E: byte       F: byte       G: byte       H: byte }  let inlineFileBytes =     fun f ->         let mimeBytes =             extractMime f |> enc.GetBytes          let mutable mimeBytesLongLength =             mimeBytes.LongLength          let mimeLen: LongBytes =             Unsafe.As(&mimeBytesLongLength)          let encodingBytes = "none" |> enc.GetBytes          let mutable mimeBytesLongLength =             encodingBytes.LongLength          let encLen: LongBytes =             Unsafe.As(&mimeBytesLongLength)          let mutable fileBytes = File.ReadAllBytes f          let mutable fileBytesLongLength =             fileBytes.LongLength          let fileLen: LongBytes =             Unsafe.As(&fileBytesLongLength)           [| [| mimeLen.A               mimeLen.B               mimeLen.C               mimeLen.D               mimeLen.E               mimeLen.F               mimeLen.G               mimeLen.H |]            mimeBytes             [| encLen.A               encLen.B               encLen.C               encLen.D               encLen.E               encLen.F               encLen.G               encLen.H |]            encodingBytes             [| fileLen.A               fileLen.B               fileLen.C               fileLen.D               fileLen.E               fileLen.F               fileLen.G               fileLen.H |]            fileBytes |]         |> Array.concat         |> Array.pin

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

let file (ctx: HttpContext) (next: RequestDelegate) =     match dict.TryGetValue ctx.Request.Path.Value with     | false, _ -> next.Invoke ctx     | true, arr ->         let spanRef =             &arr.AsSpan().GetPinnableReference()          let mimeLen: int32 =             Unsafe.ReadUnaligned(&spanRef)          ctx.Response.ContentType <- enc.GetString(arr, 8, mimeLen)          let encStart = mimeLen + 8          let encLen: int32 =             Unsafe.ReadUnaligned(&Unsafe.Add(&spanRef, encStart))          ctx.Response.Headers.ContentEncoding <- enc.GetString(arr, encStart + 8, encLen)          let fileStart = encStart + encLen + 8          let mutable fileSize: int64 =             Unsafe.ReadUnaligned(&Unsafe.Add(&spanRef, fileStart))          ctx.Response.ContentLength <- fileSize          ctx.Response.Body.WriteAsync(arr, fileStart + 8, Unsafe.As(&fileSize))

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

Тестирование показало, что в массив лучше помещать long, а читать его как int, так быстрее, поэтому так и оставил.

Что ещё интересно, на этом этапе Jit встроил буквально все вызовы прямо в функцию. ASM функции теперь представляет из себя длинное полотно длиной в 7030 байт. 175 встроенных вызовов. Вот это действительно серьёзно.

Статистика Jit

    ; Assembly listing for method Files:file(HttpContext,RequestDelegate):Task     ; Emitting BLENDED_CODE for X64 CPU with AVX - Windows     ; Tier-1 compilation     ; optimized code     ; optimized using profile data     ; rsp based frame     ; fully interruptible     ; with Dynamic PGO: edge weights are invalid, and fgCalledCount is 63369     ; 63 inlinees with PGO data; 109 single block inlinees; 3 inlinees without PGO data     ......     ; Total bytes of code 7030

RPS увеличился ещё на 179.44, до 28706.99. Даже BenchmarkDotnet показывает улучшение.

|                   Method |      Mean |    Error |   StdDev | CacheMisses/Op | BranchMispredictions/Op | LLCReference/Op | |------------------------- |----------:|---------:|---------:|---------------:|------------------------:|----------------:| |                     Base | 170.10 us | 1.328 us | 1.243 us |            141 |                     328 |          18,083 | |                 InMemory |  51.67 us | 1.028 us | 1.142 us |            101 |                     193 |          16,888 | |                   Tables |  51.56 us | 0.931 us | 0.870 us |            101 |                     185 |          16,119 | | Tables_WithoutInterfaces |  52.04 us | 0.640 us | 0.599 us |            108 |                     175 |          16,251 | |              Tables_Task |  51.40 us | 0.757 us | 0.708 us |            114 |                     190 |          16,730 | |       Tables_TaskNoAlloc |  50.95 us | 0.614 us | 0.575 us |            105 |                     184 |          16,423 | |        Tables_Middleware |  48.95 us | 0.977 us | 1.045 us |            109 |                     181 |          16,031 | |         InlineEverything |  49.97 us | 0.752 us | 0.666 us |             98 |                     190 |          15,784 |

Закатываем всё в один массив v2

image

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

Давайте представим себе, что первые два байта массива это энумы, указывающие на Content-Encoding, и Content-Type, а байты с индексами 2 по 10 это long, обозначающий длину контента. Все индексы мы знаем заранее, они будут вкомпилены в функцию.

let file (ctx: HttpContext) (next: RequestDelegate) =     match dict.TryGetValue ctx.Request.Path.Value with     | false, _ -> next.Invoke ctx     | true, arr ->         ctx.Response.ContentType <- Unsafe.As &arr.[0] |> ContentType.toString          ctx.Response.Headers.ContentEncoding <-             Unsafe.As &arr.[1]             |> ContentEncoding.toStringValues          ctx.Response.ContentLength <- (Unsafe.As &arr.[2]: int64)         ctx.Response.Body.WriteAsync(arr, 10, Unsafe.As &arr.[2])

Получился очень лаконичный код. Лаконичность так же отразилась и на производительности. Тело вызова после рефакторинга похудело на 1904 байт, до 5126 байт.

Статистика Jit после

    ; Assembly listing for method Files:file(HttpContext,RequestDelegate):Task     ; Emitting BLENDED_CODE for X64 CPU with AVX - Windows     ; Tier-1 compilation     ; optimized code     ; optimized using profile data     ; rsp based frame     ; fully interruptible     ; with Dynamic PGO: edge weights are invalid, and fgCalledCount is 83072     ; 49 inlinees with PGO data; 96 single block inlinees; 3 inlinees without PGO data     ......     ; Total bytes of code 5126

RPS не увеличился, но скорость раздачи в один поток выросла на 26МБ/с, а в 125 потоков на 15МБ/с, что в общем счёте даёт нам 621,72МБ/с в один поток и 5,85ГБ/с в 125 потоков, а это без малого 5 и 46,8 гигабит в секунду соответственно.

BanchmarkDotnet показывает, что такой код, увы, менее дружелюбен к железу.

|                   Method |      Mean |    Error |   StdDev | CacheMisses/Op | BranchMispredictions/Op | LLCReference/Op | |------------------------- |----------:|---------:|---------:|---------------:|------------------------:|----------------:| |                     Base | 170.10 us | 1.328 us | 1.243 us |            141 |                     328 |          18,083 | |                 InMemory |  51.67 us | 1.028 us | 1.142 us |            101 |                     193 |          16,888 | |                   Tables |  51.56 us | 0.931 us | 0.870 us |            101 |                     185 |          16,119 | | Tables_WithoutInterfaces |  52.04 us | 0.640 us | 0.599 us |            108 |                     175 |          16,251 | |              Tables_Task |  51.40 us | 0.757 us | 0.708 us |            114 |                     190 |          16,730 | |       Tables_TaskNoAlloc |  50.95 us | 0.614 us | 0.575 us |            105 |                     184 |          16,423 | |        Tables_Middleware |  48.95 us | 0.977 us | 1.045 us |            109 |                     181 |          16,031 | |         InlineEverything |  49.97 us | 0.752 us | 0.666 us |             98 |                     190 |          15,784 | |              FsharpFinal |  48.85 us | 0.966 us | 2.405 us |            120 |                     205 |          16,394 |

Порт на C#

image

Эквивалентный код на F# и C#, как правило, компилят в один и тот же IL. Все оптимизации, которые я привёл, хорошо лягут и на наш C# проект, но есть различия.

В F#, Unsafe.As даёт возможность небезопасно кастить любую область памяти во что угодно и работать с ней как с любым типом. C# такие выкрутасы делать не даёт.

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

Работа с поинтерами тоже различается, Unsafe.Add в C# сдвигает поинтер на sizeof<’T>, где ‘T это тип, который указал пользователь.

F# манипулирует поинтерами как до переинтерпретации, сдвигая поинтер на sizeof<’T>, где ‘T это оригинальный тип элемента массива, различия как на картинке.

На этом различия между языками не заканчиваются, но это то, с чем я столкнулся, пока портировал.

ASM, который я получил в порте, на 208 байт длиннее, чем на F#. C# не даёт писать самый оптимальный код, поэтому производительность, ожидаемо, меньше.

В один поток, порт медленнее на 20МБ/с, и на 29МБ/с в 125 потоков. То есть версия на C# медленнее на 160 и 232 мегабит соответственно.

|                   Method |      Mean |    Error |   StdDev | CacheMisses/Op | BranchMispredictions/Op | LLCReference/Op | |------------------------- |----------:|---------:|---------:|---------------:|------------------------:|----------------:| |                     Base | 170.10 us | 1.328 us | 1.243 us |            141 |                     328 |          18,083 | |                 InMemory |  51.67 us | 1.028 us | 1.142 us |            101 |                     193 |          16,888 | |                   Tables |  51.56 us | 0.931 us | 0.870 us |            101 |                     185 |          16,119 | | Tables_WithoutInterfaces |  52.04 us | 0.640 us | 0.599 us |            108 |                     175 |          16,251 | |              Tables_Task |  51.40 us | 0.757 us | 0.708 us |            114 |                     190 |          16,730 | |       Tables_TaskNoAlloc |  50.95 us | 0.614 us | 0.575 us |            105 |                     184 |          16,423 | |        Tables_Middleware |  48.95 us | 0.977 us | 1.045 us |            109 |                     181 |          16,031 | |         InlineEverything |  49.97 us | 0.752 us | 0.666 us |             98 |                     190 |          15,784 | |              FsharpFinal |  48.85 us | 0.966 us | 2.405 us |            120 |                     205 |          16,394 | |              CsharpFinal |  49.45 us | 0.985 us | 1.590 us |            112 |                     189 |          16,469 |

FULL PGO

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

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

Функции сначала компилируется tier0 — неоптимизированный код со счётчиками для сбора профиля, а потом компилируется tier1, оптимизированный по профилю код.

DOTNET_ReadyToRun=0

Ready to run это форма ahead of time компиляции, ASM метода компилится заранее и не подлежит перекомпиляции Jit’ом.

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

DOTNET_TC_QuickJitForLoops=1

Циклы сразу компилируются в tier1. Чтобы оптимизировать циклы по профилю, нужно сначала компилировать их в tier0, отключить оптимизацию.

DOTNET_TieredPGO=1

Ну и включаем сам Profile Guided Optimization.

По отдельности — эти оптимизации не дают особо большого прироста, но вместе показывают значительные цифры.

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

|                   Method |      Mean |    Error |   StdDev | CacheMisses/Op | BranchMispredictions/Op | LLCReference/Op | |------------------------- |----------:|---------:|---------:|---------------:|------------------------:|----------------:| |                     Base | 184.60 us | 2.295 us | 2.147 us |            222 |                     497 |          20,967 | |                 InMemory |  56.59 us | 0.803 us | 0.751 us |            127 |                     241 |          17,654 | |                   Tables |  56.90 us | 1.033 us | 0.966 us |            124 |                     252 |          17,465 | | Tables_WithoutInterfaces |  56.36 us | 1.096 us | 1.606 us |            146 |                     273 |          18,370 | |              Tables_Task |  56.60 us | 1.094 us | 1.074 us |            149 |                     269 |          17,872 | |       Tables_TaskNoAlloc |  56.04 us | 1.094 us | 1.260 us |            158 |                     280 |          17,682 | |        Tables_Middleware |  55.30 us | 0.831 us | 0.778 us |            137 |                     261 |          17,711 | |         InlineEverything |  54.37 us | 1.087 us | 1.627 us |            144 |                     260 |          17,949 | |              FsharpFinal |  53.97 us | 1.026 us | 1.098 us |            148 |                     250 |          17,646 | |              CsharpFinal |  54.08 us | 1.053 us | 1.081 us |            156 |                     267 |          17,466 |

Тестим в продакшене

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

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

Ну а пока она в разработке, я зашёл на сайт RUVDS, купил дефолтную конфигурацию на Windows Server 2022. Вербозный процесс закупки серверов нам не нравится, мы сделаем лучше.

image


Тестирование провёл файлами 0КБ и 3,5КБ. Это не похоже на тестирование полезной нагрузкой, но пойдёт, чтобы выяснить степень моей оптимизации.

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

Тестировать будем только стоковый файловый сервер, самую первую и самую последнюю версию оптимизации. Вот результаты:

Тестирование файлом 3,5КБ:

|                  | Потоки  | Утилизация ЦП |  RPS    |  StdDev | |------------------|---------|:--------------|---------|---------| | Base             | 22      | 52%           | 2579.06 | 455.11  | | InlineEverything | 16      | 15%           | 2984.72 | 329.30  | | FsharpFinal      | 16      | 14%           | 2990.31 | 315.68  |

Тестирование файлом 0 байт:

|                  | Потоки | Утилизация ЦП | RPS      | StdDev  | |------------------|--------|:--------------|----------|---------| | Base             | 125    | 100%          | 6607.07  | 1625.44 | | InlineEverything | 125    | 47%           | 20105.82 | 2929.65 | | FsharpFinal      | 125    | 44%           | 19986.56 | 2860.83 |

Послесловие

Вывода не будет. По большому счёту — я просто зря потратил время на оптимизацию неоптимизируемого. Лучше бы я траву на улице трогал, чесслово.

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

Telegram-канал с полезностями и уютный чат


ссылка на оригинал статьи https://habr.com/ru/company/ruvds/blog/695808/