Багатоплатформова AOT з SQLite: Як змусити її працювати! (Українська (Ukrainian))

Багатоплатформова AOT з SQLite: Як змусити її працювати!

Thursday, 12 December 2024

//

22 minute read

Якщо ви намагалися відправити програму .NET як окремі, природні виконувані файли, використовуючи Cival AOT, ви, ймовірно, спершу запустити обличчя у стіну SQLite. Ви налаштуєте все, завершення збирання успішно, а потім, moommaruntime аварійне аварійне завершення з таємничим механізмом DllNotFoundException помилки щодо e_sqlite3Дозвольте мені зберегти вам час розчарування, який я пройшов, і показати вам, як саме змусити SQLite працювати з корінним AOT через Windows, Linux (включаючи ARMS64 Raspberry Pi) і MacOS.

Що таке АОТИ?

Давайте розпочнемо з абсолютної основи. Якщо ви коли- небудь створили програму.NET і бажаєте дізнатися, чому вам слід встановити ". NET Runtime " на серверах або чому ваша консольна програма потребує секунди, щоб запустити її під час першого запуску, AOT - це відповідь на ці проблеми.

Як зазвичай працює. NET (зібрання JIT)

Коли ви пишете код C# і будуєте вашу програму, компілятор не створює компіляторів, які ваш процесор може виконувати напряму. Замість цього, компілятор створює щось, що називається **Між once мова (IL)**І це як півшляху між кодом C # і інструкціями від машини.

Якщо ви запустите програму.NET, ось що станеться:

  1. Ваш комп' ютер завантажує Runtime. NET (відмінна програма)
  2. Час запуску читає ваш код IL
  3. Just- in- time (JIT) компілятор перетворює код IL на рідний комп' ютер під час запуску вашої програми
  4. Нарешті ваш процесор виконує цей код комп' ютера

Це те саме, що мати перекладача, який читає ваш рецепт (IL) і словесно перекладає його на кухаря (CPU) в чергу під час приготування їжі.

  • Час запуску . NET це 50- 150МБ додаткових файлів, які вам потрібні для розгортання
  • Компілятор JIT використовує час для перекладу коду (чому ваша програма у перший раз повільна)
  • Сам компілятор JIT перебуває у пам' яті під час запуску вашої програми

Введіть AOT (Ahead- of- time collection)

Рідна AOT перевертає цю модель на голову. Замість перекладу вашого коду під час виконання, вона перекладає все під час збирання. Ви завершуєте з одним виконуваним файлом, який містить справжні коди машин, які можуть виконувати процесор безпосередньо, не чекаючи на запуск, ні перекладач, ні очікування.

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

Користі, які використовуються у грі

Ось що тобі каже рідна АОТА:

1. Крихітні виконувані файли: 10- 30 Мб замість 150 Мб+

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

2. Негайний запуск: 80% швидше починається холод

Мої тести: нормальний NET взяв на початок ~800мс, АOT взяв ~150 мс.

3. Нуль залежностей: Потрібний час запуску NET

Ви можете скопіювати ваш виконуваний файл на будь- який комп' ютер з відповідними операційними системами (Windows/Linux/Mac) і запустити програму. Без "встановлення. NET 9 Runtime " обов' язково.

4. Нижнє використання пам' яті: Близько 50% менше пам'яті

Немає компілятора JIT, що сидить у пам' яті. У моїй програмі для шлюзу . NET використовує 85MБ бездіяльності, AOT використовується 42MB.

5. Краще для обмеженого середовища: Працює, де JIT не може

У деяких середовищах (зі контейнерами Docker, iOS, вбудованими системами) не дозволяє створення коду під час виконання. OT працює всюди.

Профспілки (Тут завжди є пастка)

ААО - це не магія.

1. Без динамічного створення коду

Все, що генерує код під час виконання, не працюватиме:

  • System.Reflection.Emit (продуктивно розмножуються типи)
  • Динамічне завантаження асемблера (завантаження DLL під час виконання)
  • Деякі оригінальні трюки відбиття

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

2. Специфічні для платформи збирання

Компіляція JIT створює IL, що працює на будь- якій платформі. AOT створює рідний код комп' ютера для одна конкретна платформа. Вам слід зібрати окремо для:

  • Windows x64
  • Linux x64
  • Linux ARMS (Raspberry Pi)
  • MacOS Intel
  • MacOS Apple Clibon

Ми помітимо автоматизацію з діями GitHub.

3. Час збирання довшого

Замість збирання до IL у секундах, AOT компілює весь шлях до комп' ютерного коду. Виділяє 2- 5 хвилин, замість 10 секунд. Це одноразова вартість для постійних вигод.

4. Деякі можливості потребують додаткового налаштування

JSON серіалізація, блокування сутностей, і все, що використовує важкі відбиття, може знадобитися для явного визначення типів компілятора, які слід зберегти. Ми покриємо це.

Коли слід використовувати АТТ?

Рідна AOT ідеально підходить для:

  • Інструменти CLI: Інструменти командного рядка, які мають значення для негайного запуску
  • Мікрослужби: Менші зображення Docker, швидші масштаби у Kubernetes
  • Serverless/ Lambda: " Холодний початок " безпосередньо впливає на ваш законопроект
  • Пристрої ребер: Raspberry Pi, IoT - пристрої з обмеженими ресурсами
  • Програми для шлюзу: Зворотні проксі, шлюзи API з високою протокою
  • Стільничні інструменти: Send an .exe with no "install.NET first" step

Пропустити AOT для:

  • Традиційні веб- програми, час запуску яких не має значення
  • Apps з використанням важкого блоку сутностей з багатьма міграціями
  • Системи додатків, які завантажують DLL динамічно
  • Все, що створює код під час виконання

Для інструментів CLI, мікрослужби, контейнерів і пристроїв країв покращення - це зміна гри. Але коли ви додаєте бази даних, зокрема SQLite, є пастка.

Проблема SQLite

Тепер, коли ви розумієте, що таке AOT, давайте поговоримо про один з найбільших болів: SQLite. Це стіна, на яку б'ють більшість людей, коли вони намагаються скористатися AOT за допомогою програм з резервною базою даних.

Що стається (зруйнуючий досвід)

Ось типова подорож:

  1. Ви додаєте Microsoft.Data.Sqlite до вашого проекту
  2. Ви налаштовуєте PublishAot=true у вашій .csproj
  3. Будівля без пензля виглядає добре!
  4. Ви запустили виконуваний файл і негайно отримали:
DllNotFoundException: Unable to load DLL 'e_sqlite3' or one of its dependencies

Ваш додаток аварійно завершує роботу перед тим, як зможе щось зробити.

Розуміння власних бібліотек (швидка відмова)

Щоб зрозуміти проблему, треба знати про рідні бібліотеки.

SQLite не пишеться на C. Він скомпільований на специфічний для платформ рідний код:

  • e_sqlite3.dll на Windows
  • libe_sqlite3.so у Linux
  • libe_sqlite3.dylib на macOS

Якщо ви використовуєте Microsoft.Data.Sqlite в нормі NET, це просто обгортка навколо цієї рідної бібліотеки SQLite. динамічне завантаження відповідний рідний файл для вашої платформи.

Це добре працює з нормальним. NET, тому що:

  1. Час запуску може динамічно завантажувати бібліотеки
  2. Пакунки NuGet можуть включати рідні бібліотеки для всіх платформ
  3. Правий буде обрано при запуску

Чому АОТО порвав усе це?

Рідна AOT має дві характеристики, які суперечать підходу SQLite:

1. Агресивне обрізання: AOT вилучає будь- який код, який, на його думку, ви не використовуєте. Якщо він не може статично довести, що вам щось потрібно, його буде вилучено. Динамічна бібліотека завантаження плутає символ cymer}it не може бачити з' єднання між вашим кодом і рідною SQLite DLLL.

2. Немає підтримки динамічного завантаження: AOT створює самовживаний виконуваний файл. Він очікує, що всі місцеві залежності буде явно пов' язано під час компіляції, а не динамічно завантажено під час виконання програми.

Результат: Microsoft.Data.Sqlite Очікується знайти рідну бібліотеку SQLite під час роботи під час роботи, але AOT або відрізав її, або не знає, як її скувати належним чином.

Стрімкий період, що триває

Ще гірше, якщо у вас є інші пакунки NuGet, які використовують SQLite (наприклад, деякі бібліотеки ORM або мій). mostlylucid.ephemeral.complete пакунок), який можна завершити за допомогою декілька несумісних постачальників SQLite у вашому дереві залежностей.

Кожен з постачальників послуг намагається працювати по- різному:

  • Можна очікувати, що SQLite з ОС (лише вікна)
  • Інший може в'язати свою власну SQLite
  • Інша може використовувати іншу версію рідної бібліотеки

Компілятор AOT плутається з тим, який з них використовувати, і часто результат полягає в тому, що він не включає жодного з них, ще гірше, включає суперечні файли, які не можуть працювати разом.

Вирішення: користуйтеся будлом

Після кількох годин розстроєння, я знайшов розв'язання: SQLitePCLRaw.bundle_e_sqlite3Це особливий пакунок NOG, створений спеціально для роботи з AOT.

Додайте ці два пакунки до вашого проекту:

<ItemGroup>
  <PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.0" />
  <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.10" />
</ItemGroup>

Що робить бендле?

The bundle_e_sqlite3 пакунок відрізняється від звичайних постачальників SQLite:

1. До неї включено попередньо зібрані рідні бібліотеки SQLite на кожній головній платформі:

  • Windows (x64, x86, ARM64)
  • Linux (x64, ARM64, musl- distros, схожий на альпійські)
  • macOS (x64 Intel, ARMS Apple Clipon)

2. Ці рідні бібліотеки упаковуються так, що компілятор AOT розуміє

Пучок явно позначає свої природні залежності, отже компілятор AOT знає, що має включити їх до кінцевого виконуваного файла. Без динамічного завантаження, без пошуку файлів під час виконання.

3. Він призначений для міжплатформних споруд.

Один пакунок працює на всі платформи, вам не потрібні специфічні для платформи пакунки або умовні посилання.

Чому це відбувається?

Пам'ятаєш дві проблеми, які ми визначили?

Проблема 1. Підстриження AOT вилучає бібліотеки, які не використовуються.

  • Вирішення: вузол явно оголошує свої рідні бібліотеки майном для збирання, які слід включити

Проблема 2: AOT не підтримує динамічне завантаження

  • Вирішення: вузол статично з' єднує бібліотеки SQLite під час компіляції

Результат: під час збирання Windows x64 вузол включає e_sqlite3.dll. Коли ви будуєте для Linux ARM64, він включає ARM64 libe_sqlite3.soВсе просто працює.

Критична: ініціалізувати бендл (не пропускати це!)

Ось частина, в якій 90% людей намагаються використовувати SQLite з АOT, включаючи мене при першій спробі.

З нормальним NET, вузол SQLite автоматично ініціює себе, коли ви вперше використовуєте SQLite. магія виникає за сценами, вам не потрібно нічого робити.

З корінним АОТом, автоматична ініціалізація не працює. Компілятор AOT не може бачити автоматичний код запуску (буде виглядати так, ніби невикористаний код і буде обрізано), отже, має бути ініціалізовано вручну вузол на самому початку вашої програми.

Ось вам чарівна лінія:

using SQLitePCL;

public class Program
{
    public static void Main(string[] args)
    {
        // THIS IS CRITICAL: Initialize SQLite FIRST, before ANYTHING else
        SQLitePCL.Batteries.Init();

        // Now you can do normal application setup
        var builder = WebApplication.CreateBuilder(args);

        // This is now safe - SQLite is initialized
        builder.Services.AddDbContext<MyDbContext>(options =>
            options.UseSqlite("Data Source=app.db"));

        var app = builder.Build();
        app.Run();
    }
}

Що робить Batteries.Init() Сделаешь?

Цей метод повідомляє вузол SQLite:

  1. Знайти правильну рідну бібліотеку SQLite для вашої поточної платформи
  2. Завантажити його у пам' ять
  3. Порвати всі з' єднання між Microsoft.Data.Sqlite і рідну бібліотеку

Це називається "Батестери," тому що це "проблеми, включені" тайфуни, які вам потрібні разом упаковки.

Де поставити його

Вмістити Batteries.Init() асunit description in lists найперший рядок у вашій Main метод перед:

  • Створення збирання програм
  • Налаштування ін'єкції залежностей
  • Відкриття всіх з' єднань баз даних
  • Читання файлів налаштування, які можуть використовувати SQLite

Думайте про це як під' єднання за допомогою пристрою перед тим, як його увімкнути. Якщо ви спробуєте скористатися SQLite перед викликом Init()DllNotFoundException Незважаючи на те, що DL правильно вмонтований у вашу програму.

Що станеться, коли ви забудете?

Якщо забудеш подзвонити Batteries.Init()Ваш додаток:

  1. Успішно зібрати (компілятор не попередить вас)
  2. Почати виконання
  3. Аварійне завершення у момент, коли програма спробує скористатися SQLite за допомогою:
DllNotFoundException: Unable to load DLL 'e_sqlite3' or one of its dependencies

Це збиває з пантелику, тому що DLL є Я втратив дві години на цю помилку.

Завершити налаштування проекту

Тут повно грошей. .csproj налаштовано для багатоплатформової внутрішньої AOT у SQLite:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>

    <!-- Native AOT -->
    <PublishAot>true</PublishAot>
    <PublishTrimmed>true</PublishTrimmed>
    <TrimMode>full</TrimMode>
    <InvariantGlobalization>true</InvariantGlobalization>

    <!-- Single File -->
    <PublishSingleFile>true</PublishSingleFile>
    <StripSymbols>true</StripSymbols>

    <!-- Optimization -->
    <OptimizationPreference>Speed</OptimizationPreference>

    <!-- Multi-platform targets -->
    <RuntimeIdentifiers>
      win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64
    </RuntimeIdentifiers>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.0" />
    <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.10" />
  </ItemGroup>

</Project>

Параметри ключів пояснені (за простою англійською)

Дозвольте мені розбити всі дії кожного з цих параметрів:

PublishAot=true

Це головний перемикач, за допомогою якого можна увімкнути компіляцію елементів AOT. Якщо цей пункт не буде позначено, ви зможете скористатися звичайною поведінкою. NET (зібрання JIT). За допомогою цього пункту ви зможете отримати початковий код у компіляції.

PublishTrimmed=true і TrimMode=full

Вони кажуть компілятору вилучити будь-який код, яким ти не користуєшся.

  • PublishTrimmed=true уможливлює обрізання
  • TrimMode=full означає "будь агресивний, убери все, що можеш."

ПопередженняЦе може зламати код, який використовує важкі відбиття (на зразок деяких серіалізаторів JSON або ORM), оскільки інструмент обрізання не завжди може бачити те, що ви використовуєте у віддзеркаленні. Ми поговоримо про це пізніше.

InvariantGlobalization=true

За допомогою цього пункту можна вилучити всі дані, специфічні для культури, з форматів вашої програми app}, символів валюти, правил впорядкування тексту для різних мов.

Встановити лише це значення true якщо ваша програма:

  • Використовувати лише англійську
  • Немає потреби у форматуваннях дати/ часу, специфічних для культури
  • Використовувати лише ординал (у байтах за байтом) порівняння рядків

Якщо ви будуєте інструмент CLI або шлюз API, який не має значення для локалізації, це вільні заощадження. Якщо ви створюєте щось, що потребує форматування дат для користувачів французької мови або впорядкування турецького тексту, пропустити цей параметр.

PublishSingleFile=true

Розбивати всі файли до одного виконуваного файла. Замість того, щоб мати:

myapp.exe
myapp.dll
System.Text.Json.dll
... 50 more files

Просто:

myapp.exe

Набагато простіше.

StripSymbols=true

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

Цей параметр вилучає їх. Ваша програма запускає точно те саме, лише менше.

OptimizationPreference=Speed

За допомогою цього пункту можна наказати компілятору визначати пріоритет під час прийняття рішень:

  • Speed: Робіть це швидко (майже більші бінарні файли, але кращі результати)
  • Size: Зробити його маленьким (майже повільнішим, але мінімальним бінарним розміром)

Для більшості програм, Speed Різниця розмірів, зазвичай, лише 2-3МБ, але різниця швидкодії може бути помітною.

RuntimeIdentifiers

Це означає, які платформи ви хочете підтримувати. Він не будує їх всі' ї просто каже інструментing "це реальні цілі."

Наявні ідентифікатори:

  • win-x64: Windows 64- bit (Intel/AMD)
  • win-arm64: Windows ARM64 (Surface Pro X тощо)
  • linux-x64: Linux 64- bit (Ubuntu, Debian, RHEL тощо)
  • linux-arm64: Linux ARM64 (Raspberry Pi 4/5, AWS Graviton)
  • osx-x64: macOS Intel (старший Macs)
  • osx-arm64: MacOS Apple Clicon (M1/ M2/ M3 Macs)

Ви будуєте одну платформу за раз, використовуючи dotnet publish -r linux-x64, наприклад.

Будівництво декількох платформ

Пам' ятаєте, як я сказав, AOT потребує специфічної для платформи збирання? Вам слід зібрати окремо для Windows, Linux x64, Linux ARM64, macOS Intel і macOS Apple Clibon. Виконання цього завдання має бути достатньо іншим, ми можемо автоматично виконати його.

Що таке дії GitHub?

Якщо ви не знайомі, GitHub Дії - це безкоштовна служба CI/CD (Continentive Integration/ContentiveDoupment), вбудована у GitHub. За її допомогою ви можете виконувати автоматичні завдання кожного разу, коли ви натискаєте на код або створюєте теґ випуску.

Уявіть собі, що у вас є сервер збирання, який:

  1. Спостерігає за вашим сховищем GitHub
  2. Коли ви натискаєте мітку, як v1.0.0
  3. Автоматично обертається у віртуальних машинах Windows, Linux та macOSName
  4. Збирає вашу програму для всіх платформ паралельно
  5. Створює випуск GitHub з усіма долученими бінарними файламиName

Все це працює на "PistHub" GitHub, вам не потрібно підтримувати будь-яку інфраструктуру для проектів з відкритим кодом і малих особистих проектів, це абсолютно безкоштовно.

Повний процес збирання

Ось інтерфейс дій GitHub, який створюється автоматично для всіх основних платформ:

name: Build Native AOT Binaries

on:
  push:
    tags:
      - 'v*'

jobs:
  build-binaries:
    name: Build ${{ matrix.runtime }}
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        include:
          - os: ubuntu-latest
            runtime: linux-x64
            artifact-name: myapp-linux-x64

          - os: ubuntu-latest
            runtime: linux-arm64
            artifact-name: myapp-linux-arm64

          - os: windows-latest
            runtime: win-x64
            artifact-name: myapp-win-x64

          - os: macos-latest
            runtime: osx-x64
            artifact-name: myapp-osx-x64

          - os: macos-latest
            runtime: osx-arm64
            artifact-name: myapp-osx-arm64

    steps:
    - uses: actions/checkout@v4

    - name: Setup .NET
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: '9.0.x'

    - name: Install ARM64 tools (Linux ARM64 only)
      if: matrix.runtime == 'linux-arm64'
      run: |
        sudo apt-get update
        sudo apt-get install -y clang zlib1g-dev gcc-aarch64-linux-gnu

    - name: Publish
      shell: bash
      run: |
        # Set objcopy for ARM64 cross-compilation
        if [ "${{ matrix.runtime }}" = "linux-arm64" ]; then
          OBJCOPY_PARAM="-p:ObjCopyName=aarch64-linux-gnu-objcopy"
        else
          OBJCOPY_PARAM=""
        fi

        dotnet publish \
          -c Release \
          -r ${{ matrix.runtime }} \
          --self-contained \
          --output ./publish/${{ matrix.runtime }} \
          -p:PublishAot=true \
          -p:PublishTrimmed=true \
          -p:StripSymbols=true \
          $OBJCOPY_PARAM

    - name: Create distribution package
      shell: bash
      run: |
        mkdir -p ./dist
        cd ./publish/${{ matrix.runtime }}

        # Copy the main executable
        cp myapp${{ matrix.file-ext }} ../../dist/

        # CRITICAL: Copy native libraries (SQLite and any other native dependencies)
        # PublishSingleFile bundles .NET code, but native DLLs remain separate
        cp *.dll ../../dist/ 2>/dev/null || true
        cp *.so ../../dist/ 2>/dev/null || true
        cp *.dylib ../../dist/ 2>/dev/null || true

        # Copy config files if needed
        cp ../../appsettings.json ../../dist/ || true

        cd ../../dist

        # Create archive with all files
        if [ "${{ runner.os }}" = "Windows" ]; then
          7z a -tzip ../${{ matrix.artifact-name }}.zip *
        else
          tar czf ../${{ matrix.artifact-name }}.tar.gz *
        fi

    - name: Upload artifact
      uses: actions/upload-artifact@v4
      with:
        name: ${{ matrix.artifact-name }}
        path: |
          ${{ matrix.artifact-name }}.zip
          ${{ matrix.artifact-name }}.tar.gz
        retention-days: 7
        if-no-files-found: ignore

Як діє цей процес (похід за кроком)

Якщо YAML виглядає лякаючим, ось що він робить у звичайній англійській:

1. Тривога (on: push: tags)

Процес працює, коли ви натискаєте мітку Git, що починається з v (на зразок v1.0.0, v2.3.1). Це стандартний спосіб позначення версій випуску.

2. Стратегія матриці

Замість того, щоб писати п'ять різних робіт, ми визначаємо матриця збирання:

  • Кожна з збирання отримує різне os (машина для запуску) і runtime (цільова платформа)
  • Дії GitHub запускають всі п' ять побудівлень паралельно
  • Кожен з них створює артефакт (скомпільований бінарний файл)

То ж, коли ти штовхаєш v1.0.0, GitHub одночасно:

  • Розкручує машину Ubuntu для побудови Linux x64 і ARM64
  • Обертається до комп' ютера Windows, щоб зібрати Windows x64Name
  • Розташовує машину MacOS для побудови MacOS x64 і ARM64

3. Кроки у кожному з збирання

Кожне збирання платформ виконує ті самі кроки:

  1. Код звантаження: отримує вихідні коди зі сховища
  2. Налаштувати. NET: встановлює .NET 9 SDK
  3. Встановити інструменти ARM64 (лише Linux ARM64): встановлює інструменти перехресного компіляції
  4. Опублікувати: Виконання dotnet publish з прапорцями AOT для цієї конкретної платформи
  5. Вивантажити артефакт: Зберігає зібрану програму, щоб отримати доступ до наступної задачі

4 Результат

Після завершення збирання у вас буде п' ять артефактів (binarys) готових для розповсюдження. Ви можете звантажити їх з запуску дій або скористатися другим завданням для створення випуску GitHub у автоматичному режимі (не показано у цьому фрагменті, але просто додати).

Критична: рідні бібліотеки не пошкоджені

Ось дещо, що спантеличує мене годинами: PublishSingleFile=true Код лише в' язків. NETКорінні бібліотеки, такі як SQLite e_sqlite3.dll Залишайтеся окремо.

Ось чому крок " Створити пакунок дистрибутива " є таким важливим:

# Copy native libraries - these are NOT included in the main executable
cp *.dll ../../dist/ 2>/dev/null || true    # Windows
cp *.so ../../dist/ 2>/dev/null || true     # Linux
cp *.dylib ../../dist/ 2>/dev/null || true  # macOS

The 2>/dev/null || true Частина означає, що якщо немає файлів, що відповідають цьому шаблону, то не знеохочуйтесь, просто продовжуйте." Це дозволяє однаковому сценарію працювати на всіх платформах.

Розподіл даних:

  • Вікна: myapp.exe + e_sqlite3.dll (з'єднані в ZIP)
  • Linux: myapp + libe_sqlite3.so (з'єднаний в брезенті. gz)
  • МакУС: myapp + libe_sqlite3.dylib (з'єднаний в брезенті. gz)

Користувачі видобувають архів і запускають його. Природну бібліотеку SQLite розташовано поряд з виконуваним файлом, і вузол автоматично знаходить його під час виконання програми.

Чому корисно здійснювати ручні вправи?

Зверніть увагу на workflow_dispatch Тригер згори:

on:
  push:
    tags:
      - 'v*'
  workflow_dispatch:
    inputs:
      version:
        description: 'Version to publish'
        required: true

За допомогою цього пункту ви можете увімкнути збирання вручну з веб- інтерфейсу GitHub без пересування теґу. Корисно для тестування процесу обробки або створення бета- збирання.

Перехрестення ARMS memory slot

Для збирання ARM64 Linux потрібна особлива увага. Вам потрібні інструменти перерозподілу і вам слід вказати правильну objcopy Інструмент:

# Install tools
sudo apt-get install gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu

# Build with objcopy specified
dotnet publish \
  -r linux-arm64 \
  -p:PublishAot=true \
  -p:ObjCopyName=aarch64-linux-gnu-objcopy

Без даних ObjCopyName Параметр, інструмент посилання не працює з таємними помилками щодо невідомих форматів файлів.

Результати реального світу

Ось те, чого я досяг з використанням мого виробника YARP, заснованого на bot- шлюзі виявлення з веденням журналу Midrentware і SQLite. Це реальний проект, який ви можете виконати. звантаження з GitHub.

Двійкові розміри (з випусків з GitHub)

Це ті, що фактичні розміри файлів з моїх випусків GitHub, а не теоретичних оцінень:

|----------|-----------|-------------------|-------------------| Д-р Цукер: "Воно x64] +1,7 вечора" 10. 9 Мб (ZIP) ♪

  • Linux x64} 8 Мб +1, 6 Мб} 12. 4 Мб (tar. gz) ⇩ Linux АРМ64 +1,5 вечора 11. 4 Мб (tar. gz) ⇩ Д-р Харріс: "О-о-о-о, в-о-о-о-о, в-о-о-о-о-о-о-о-о, в-о-о-о-о-о-о-о-о!." 13. 0 Мб (tar. gz) ⇩ Д. О. АРМІС. 9.9дня +1,7 вечора 11, 5 Мб (tar. gz) ⇩

Порівняйте це з самозбереженням NET 9 в 130- 150 МБ на платформуМи говоримо про 10- 12x зменшення розміру.

Зрив:

  • Головний виконуваний файл: ваш зібраний код +. NET run time (AOT- compiled)
  • Рідний DLL: рушій бази даних SQLite (написаний на C)

Обидва файли повинні бути розподілені разом, але вони все ще значно менші, ніж традиційні.

Швидкодія запуску

Холодний початок (перший запит подався) на скромній версії Linux VPS:

  • У себе в руках. NET: ~800ms
  • Системний AOT: ~150 мс
  • Покращення: 81% швидше

Це дуже важливо для:

  • Функції Serverless/ Lambda (ви платите за мілісекунду)
  • Контейнери, які часто скочуються вгору або вниз
  • Інструменти CLI, у яких починається кожне злиття

Використання пам' яті

Бездіяльна пам' ять (запущений шлюз, без трафіку):

  • У себе в руках. NET: 85 Мб
  • Системний AOT: 42 Мб
  • Покращення: 51% зменшення

Під час завантаження (1000 запитів/ секунд):

  • У себе в руках. NET: ~320MB
  • Системний AOT: ~180МБ
  • Покращення: 44% зменшення

Нижча пам' ять означає:

  • Більше контейнерів на вузол
  • Рахунки з купівлею хмар
  • Бажаність до пристроїв з керування ресурсами (Raspberry Pi, IoT)

Загальні пастки

1. Забуваючи Batteries.Init()

Це помилка # 1. Навіть якщо вузол правильно посилається, забуваючи про виклик SQLitePCL.Batteries.Init() на самому початку вашого Main метод призведе до аварійних завершень у режимі виконання.

Прилаштування:

public static void Main(string[] args)
{
    SQLitePCL.Batteries.Init(); // FIRST LINE
    // ... rest of your code
}

2. Не розсіювання місцевих бібліотек

Це помилка No2, і я отримав два рази. PublishSingleFile=true НЕ містить до вашого виконуваного файла рідних SQLite DLL. Вам слід поширювати ці файли поряд з вашим виконуваним файлом.

Що станеться, якщо ви забудете:

  • Ваш додаток чудово будує
  • Він працює на вашому комп' ютері dev (де SQLite вже може бути встановлено)
  • Він розпадається на виробництво за допомогою DllNotFoundException

Прилаштування:

Під час пакування вашого випуску завжди включайте:

myapp.exe               # Your main executable
e_sqlite3.dll           # SQLite native library (Windows)
# or libe_sqlite3.so    # SQLite native library (Linux)
# or libe_sqlite3.dylib # SQLite native library (macOS)

У ваших GitHub Дії або скрипти виконання:

# Copy ALL native libraries from the publish directory
cp *.dll ./dist/ 2>/dev/null || true
cp *.so ./dist/ 2>/dev/null || true
cp *.dylib ./dist/ 2>/dev/null || true

Користувачам слід видобути архів і запустити його. Природна бібліотека має знаходитися у тому ж каталозі.

Використання 3. winsqlite3 Провайдер

У вас, можливо, будуть рекомендації на використання SQLitePCLRaw.provider.winsqlite3 у Windows для використання SQLite з підтримкою операційної системи. Не треба. Цей інструмент працює лише на Windows, потребує ініціалізації вручну, а також розбивання міжплатформових структур. Поставтеся з ним bundle_e_sqlite3.

4. Об' єм попереджень з ядром ЕФ

Якщо ви використовуєте Core Framework Core за допомогою SQLite, ви отримаєте попередження щодо обрізання (IL2026, ILL3050). Зазвичай, такі попередження можна буде вимкнути для постачальника SQLite EF Core, але ретельно тестувати:

<PropertyGroup>
  <NoWarn>$(NoWarn);IL2026;IL3050</NoWarn>
</PropertyGroup>

Ще краще, подумайте про використання Dapper або сирої ADO. NET з SQLite для програм AOT applications ♪ Що більш дружній до OT-дружності.

5. Відсутні Visual Studio tools (Windows)

У Windows для Centific AOT потрібен пов' язувач Visual Studio MSVC. Якщо ви зберете програму за межами запрошення до команди розробника, ви побачите помилки щодо vswhere.exe. Дії GitHub працюють у автоматичному режимі, але для локальних збирання використовуйте:

  • Запит команди розробника для VS 2022
  • Розробник PowerShell для VS 2022

Або започаткуйте середовище у вашому скрипті збирання:

& "C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\Launch-VsDevShell.ps1" -Arch amd64
dotnet publish -c Release -r win-x64

Коли використовувати рідну орхідею

Рідна AOT ідеально підходить для:

  • Інструменти CLI: Важливі для негайного запуску
  • Мікрослужби і контейнери: Менші зображення, швидші масштаби
  • Пристрої ребер: Raspberry Pi, IoT - пристрої з обмеженими ресурсами
  • Serverless/ Lambda: Час холоду є критичним
  • Програми для роботи з шлюзом/проксі: Високий прохід, низький над головою

Уникайте AOT для:

  • Використання потужного блоку роботи ядра сутностей (лотів міграцій, складних запитів)
  • Програми з відбиттям
  • Динамічні системи додатків
  • Програми, які потребують створення коду у режимі запуску

Швидкий запуск списку перевірки

Якщо ви прочитали весь цей допис і просто хочете, щоб було проведено контрольний список, ви можете обрати один з таких дописів:

1. Додайте пакунки:

<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.0" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.10" />

2. Налаштуйте ваш комп'ютер .csproj для AOT:

<PublishAot>true</PublishAot>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>full</TrimMode>
<InvariantGlobalization>true</InvariantGlobalization>
<PublishSingleFile>true</PublishSingleFile>
<StripSymbols>true</StripSymbols>
<OptimizationPreference>Speed</OptimizationPreference>

3. Ініціалізувати SQLite на початку вашої роботи Main метод:

SQLitePCL.Batteries.Init();

4. Зібрати для платформи призначення:

dotnet publish -c Release -r linux-x64 --self-contained

5 Тестіть свій двійковий код, він повинен просто запуститися без залежностей!

Висновки

Те, що SQLite працює з корінним АОТО не є очевидним, але як тільки ви знаєте, що магія дрімаєSQLitePCLRaw.bundle_e_sqlite3 + Batteries.Init()Винагорода є істотною: крихітні контейнери, моментальний стартап і можливість піднятися на будь-яку платформу без залежностей під час виконання.

Для будь- кого, хто будує інструменти CLI, шлюзи або програми з країв за допомогою . NET, ATA OT з SQLite тепер є реалістичним параметром. GitHub Actions працює над потоком, який тут постачається, автоматично включає весь багатоплатформний процес збирання процесу * просто теґ, який буде випущено.

Якщо ви новий у AOT, почніть з малого: спочатку перетворити простий інструмент CLI або програму. Зручно скористайтеся процесом збирання, дізнайтеся, які попередження слід очікувати, і зрозумійте обмеження. Після того, як ви отримаєте базові можливості, ви зможете скористатися складнішими програмами.

Екосистема .NET все більше дружить з AOT. Більшість сучасних бібліотек або працюють з коробки, або мають чітку документацію про підтримку AOT. Майбутнє є рідним, і воно швидше, ніж ви думаєте.

Справжній досвід у виховуванні

Я відкрию свій зібраний AOT шлюз до:

  • Цифрові океанські крапельки (Linux x64) - запущено як системна служба
  • Raspberry Pi 5 (Linux ARM64) - крайнє представлення для тестування
  • Сервер Windows (Windows x64) - працює як служба Windows
  • Контейнери панелі - обидва варіанти x64 і ARM64

Процес розгортання є однаковим всюди:

  1. Звантажити архів з випусків GitHub
  2. Видобути його
  3. Запустити виконуваний файл

Ніякого кроку "встановити .NET Runtime" Без залежностей Немає конфліктів версій. Просто витягуйте і тікайте.

Для Докера, мой Dockerfile незручно просто:

FROM debian:bookworm-slim

# Copy just the two files we need
COPY minigw /app/minigw
COPY libe_sqlite3.so /app/libe_sqlite3.so

WORKDIR /app
RUN chmod +x minigw

EXPOSE 5000
ENTRYPOINT ["./minigw"]

Остаточне зображення: ~ 130МБ (здебільшого базове зображення Debian). Традиційний контейнер. NET буде 200- 250MB.

Повнофункціональний приклад роботи

Все, що я тут показую, походить від реального, готового до виробництва проекту. Ви можете:

Проект - це мінімальний проксі- сервер з зворотним інтерфейсом YARP з виявленням bot середніх програм, які реєструють підписи на SQLite. Це демонструє:

  • Системний AOT з SQLite
  • Багатоплатформа будує за допомогою дій GitHub
  • Збірка бундлінгів у рідній бібліотеці
  • Обробка аргументів CLI
  • Рекордований журналювання
  • Милосердне завершення роботи

Клонуйте його, вивчайте його, використовуйте як шаблон для ваших власних проектів AOT.

Ресурси

А теперь построи что-нибудь крошечное и быстрое.

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.