This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
Thursday, 12 December 2024
如果您一直试图将 .NET 应用程序作为使用本地 AOT 的单一本地可执行程序装运, 您很可能已经将脸部排在 SQLite 墙上。 您得到所有配置, 建筑完成成功, 然后用加密的自动崩溃 。 DllNotFoundException 有关错误的错误 e_sqlite3让我给你省下我所经历的挫折的时光 并告诉你如何让SQLite 和原住民AOT合作 穿过Windows、Linux(包括ARM64 Raspberry Pi)和MacOS。
让我们从绝对基础开始。 如果您曾经建立过 .NET 应用程序, 并想知道为什么您需要在服务器上安装“ .NET Runtime ” , 或者为什么您的控制台应用程序第一次运行时需要一秒钟才能启动, AOT 是这些问题的答案 。
当您写入 C# 代码并构建您的应用程序时, 编译器不会生成您的 CPU 可以直接执行的机器代码。 相反, 它会生成一个名为 中间语言(IL)- 把它当成你C#代码 和机器实际指示的中间点
当你运行您的. NET 应用程序时, 下面是这样的:
这就像有一个翻译 读你的食谱(IL) , 并用口头翻译 在厨师(CPU)做饭时用线条 将它翻译为厨师(CPU)。
AOT 原生 AOT 将这个模型翻翻到头顶上。 它不是在运行时翻译您的代码, 而是翻译一切 建设时间。最后,你有一个包含实际机器代码的可执行文件。您的CPU可以直接运行,没有运行时间,没有翻译,没有等待。
将它想象成获得一本专业翻译的食谱,而不是雇用一名现场翻译。 工作就完成了一次,提前完成,结果可以立即使用。
以下是原住民AOT给你的:
1. 微小可执行文件:10-30MB,而不是150MB+
您的应用程序和它所需要的一切 都汇编成一个小二进制文件。 没有单独的运行时间文件 。
2. 即时启动:80%更快的寒冷开始
我的测试是:正常的.NET用800米开始,AOT用150米。没有JIT热身时间,机器代码可以立即执行。
3. 零依赖性: 不需要.NET运行时间
您可以将您的可执行文件复制到任何机器, 并使用正确的 OS (Windows/ Linux/Mac) , 并且它正在运行 。 没有“ 安装. NET 9 Runtime ” 的先决条件 。
4. 内存使用率降低: 将内存减50%左右
内存中没有 JIT 编译器。 在我的网关应用程序中, 普通的. NET 使用 85MB 闲置, AOT 使用 42MB 。
5. 改善限制环境在JIT不能工作的地方工作
有些环境(某些多克集装箱、iOS、嵌入系统)不允许运行时间代码生成。 AOT到处工作。
AOT不是魔法,你用运行时的灵活度来交换前期优化:
1. 没有动态代码生成
任何在运行时生成代码的代码都不会有效 :
System.Reflection.Emit (动态生成型号)多数正常的.NET代码是好的,但一些非常依赖反省的框架需要特殊配置。
2. 针对具体平台的建设
JIT编集产生在任何平台上运行的 IL 。 AOT 生成本地机码 一个特定平台。您需要为以下目的单独建房:
我们稍后会和吉特Hub Action 一起报道这个自动处理
3. 较长的建筑时间
AOT没有在几秒钟内编集到 IL, 而是一直编集到机器代码。 期望 2 - 5 分钟而不是 10 秒。 这是一次性的永久收益成本 。
4. 有些特征需要额外配置
JSON 序列化,实体框架, 以及任何使用重反射的东西 可能需要你明确告诉编译者 哪些类型要保留。 我们将覆盖这个类型 。
土著AOT 完美地适用于:
跳过 AOT 用于 :
对于 CLI 工具、 微服务、 容器和边缘设备来说, 改进是改变游戏的。 但是当您添加数据库时, 特别是 SQLite , 就会发现问题。
现在你们明白AOT是什么了,让我们来谈谈一个最大的疼痛点: SQLite。这是在试图使用AOT使用数据库支持的应用程序时被大多数人击中的墙壁。
以下是典型的旅程:
Microsoft.Data.Sqlite 对您的工程项目PublishAot=true 在您 .csprojDllNotFoundException: Unable to load DLL 'e_sqlite3' or one of its dependencies
你的应用程序在它能做任何事情之前就崩溃了 什么能给什么?
要理解问题,你需要知道 本地图书馆.
SQLite不是在.NET里写的——它用C写。 它被编译为平台特有的本地代码:
e_sqlite3.dll 在 Windows 上libe_sqlite3.so 关于Linux的libe_sqlite3.dylib 在 macOS 上使用时 Microsoft.Data.Sqlite 在正常的.NET中,它只是围绕这个本地的 SQLite 库的包装纸。在运行时,它试图 动态动态负载 您平台的适当本地文件 。
使用正常的.NET, 因为:
原住民AOT有两个与SQLite方法相冲突的特点:
1. 侵略性修剪: AOT 删除了它认为你没有使用的代码。如果它不能静态地证明你需要的东西,它就会被删除。动态图书馆的加载会混淆三端- 它看不到您的代码与本地 SQLite DLL 之间的联系 。
2. 没有动态装载支持AOT 生成一个自成一体的二进制。它期望所有本地属地在编译时明确连接,而不是在运行时动态装入。
结果: Microsoft.Data.Sqlite 希望能在运行时找到本地的 SQLite 图书馆,
更糟糕的是,如果您有其他使用 SQLite 的 NUGet 软件包( 如一些ORM 库或我的 mostlylucid.ephemeral.complete 软件包中,您可以使用 多重不兼容 SQLite 供应商 在你的树上,在你的树上,
每个供应商都试图以不同的方式工作:
AOT 编译者对使用哪一种内容感到困惑, 其结果往往包括其中任何一种内容, 更糟糕的是,
经过几个小时的挫折,我找到了解决办法: SQLitePCLRaw.bundle_e_sqlite3。这是一个特殊的NuGet软件包,专门设计用于与AOT合作。
在您的工程中添加这两个包 :
<ItemGroup>
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.0" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.10" />
</ItemGroup>
缩略 bundle_e_sqlite3 软件包不同于普通 SQLite 提供者 :
1. 包括每个主要平台的预先编篡的本地SQLite图书馆:
2. 这些本地图书馆的包装方式由AOT汇编者理解
捆绑标记其本地属地, 以便 AOT 编译者知道将它们包括在最后的二进制书中。 没有动态装入, 没有运行时间搜索文件 。
3. 设计用于跨平台建筑
一个软件包适用于所有平台。 您不需要特定平台的软件包或有条件的引用 。
记得我们发现的两个问题吗?
问题1: AOT 的裁剪删除了它看不到的库
问题2:AOT不支持动态装载
结果:当您为 Windows x64 构建 Windows x64 时,包包括 e_sqlite3.dll当您为Linux ARM64建造时,它包括ARM64 libe_sqlite3.so. 一切都只是管用。
90%的人试图使用SQLite 与AOT, 包括我第一次尝试。
正常 .NET, SQLite捆绑在第一次使用 SQLite 时自动初始化。魔法在幕后发生,不需要做任何事情。
与原住民AOT,自动初始化无效。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 捆绑 :
Microsoft.Data.Sqlite 图书馆和本地图书馆它叫做"电池" 因为它是"包含电池"的捆绑—— 你需要的东西都是包装在一起的。
将 Batteries.Init() 计为 第一行第一行 在您 Main 方法, 前面:
把它想象成在打开前插插插设备。 如果您在拨打 SQLite 之前尝试使用 SQLite Init(),你仍然会得到 DllNotFoundException 即使 DLL 被正确捆绑在您的应用程序中 。
如果你忘了打电话 Batteries.Init()您的应用程序将:
DllNotFoundException: Unable to load DLL 'e_sqlite3' or one of its dependencies
这令人困惑,因为 DLL 是 在您的应用程序中捆绑—— 它只是没有初始化。 我为这个错误损失了两个小时。 不要像我一样。
这儿有满满的 .csproj 配置为带有 SQLite 的多平台本地 AOT :
<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 序列器或ORMs), 因为三角体不能总是通过反射看到您正在使用什么。 我们稍后会讨论如何处理。
InvariantGlobalization=true
这将删除您应用日期格式、 货币符号、 不同语言文本排序规则中所有的文化特定数据。 保存 5- 10MB 。
仅设定此仅为 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-3MB,但性能差异是明显的。
RuntimeIdentifiers
这声明了您想要支持的平台。 它不完全构建这些平台, 它只是用“这些是有效的目标”来描述工具。
可用标识符 :
win-x64: Windows 64 位(英特/AMD)win-arm64: Windows ARM64 (紫色Pro X等)linux-x64Linux 64比特(Ubuntu、Debian、RHEL等)linux-arm64Linux ARM64 (草莓Pi 4/5, AWS Gravton)osx-x64: MacOS 英特尔(老年麦克斯)osx-arm64M1/M2/M3 MacOS 苹果硅(M1/M2/M3 Macs)你一次用一个平台构建一个平台 dotnet publish -r linux-x64例如。
记得我说过AOT需要特定平台的建筑吗? 您需要为 Windows、 Linux x64、 Linux ARM64、 macOS Intel 和 macOS Apple Silicon 分别编集。 手动操作是乏味的 — — 但我们可以将它自动化。
如果您不熟悉, GitHub Action 是 GitHub 所建的免费的 CI/ CD (持续整合/持续部署) 服务。 它允许您在按下代码或创建释放标记时执行自动任务 。
把它想象成拥有一个建筑服务器:
v1.0.0GitHub的服务器上都有这些——你不需要维护任何基础设施。对于开放源码项目和小型个人项目来说,这是完全免费的。
以下是一个GitHub Action 工作流程, 自动为所有主要平台建立 :
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 标记开头的 Git 标记时,工作流程会运行 v (类似) v1.0.0, v2.3.1这是标记发布版本的标准方式。
2. 矩阵战略
这是聪明的部分。 我们定义的不是写5个不同的工作流程,而是 矩阵矩阵表 建筑:
os (运行机机)和 runtime (目标平台)所以当你推 v1.0.0GitHub同时:
3. 每项建设中的步骤
每个平台的构建都采取同样的步骤:
dotnet publish 该特定平台的 AOT 旗帜与 AOT 旗帜4. 结果
全部构建完成后, 您有五个手工艺品( 二进制) 准备分发。 您可以从“ 动作运行” 下载它们, 或者用第二个工作来自动创建 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
缩略 2>/dev/null || true 部分表示“ 如果没有符合此模式的文件, 不要失败—— 继续。 ” 这让相同的脚本在所有平台上工作 。
你们所分配的是什么?
myapp.exe + e_sqlite3.dll (混合在ZIP中)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 的网络界面手动启动触发, 而不按标签 。 可用于测试工作流程或创建 beta 构建 。
Linux ARM64 构建需要特别关注。 您需要交叉编译工具, 并且必须指定正确的 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的机器人检测网关 中间软件和SQLite 记录。这是一个真正的项目,你可以 从 GitHub 下载.
这些是 实际文件大小 而非理论估计:
|----------|-----------|-------------------|-------------------| 视窗 x64 9.2MB +1.7MB 10.9MMB (ZIP) Linux x64 10.8MB +1.6MB 12.4MB 12.4MB (tar.gz) Linux ARM64 9.9MB +1.5MB 11.4MB 11.4MB (tar.gz) MACOS 英特尔 112.MB +1.8MB + 1.8MB * 13.00MB (tar.gz) -=YTET -伊甸园字幕组=- 翻译: 11.5MB :11.5MMB (tar.gz)
相比之下,自足型.NET 9部署 每个平台130-150MB我们谈论的是 规模减少10-12x.
细目:
这两个文件必须一起分发, 但是它们仍然比传统的.NET部署要小得多。
温和的 Linux VPS 开始冷( 第一次请求服务) :
此事对以下各方面有很大影响:
空闲内存( 开关运行, 没有流量 ) :
负载( 1 000 个请求/ 秒) :
较低内存是指:
Batteries.Init()这是 # 1 错误 。 即使字包正确引用, 忘记拨打 SQLitePCL.Batteries.Init() 在您开始的时候 Main 方法将引发运行时崩溃 。
修正 :
public static void Main(string[] args)
{
SQLitePCL.Batteries.Init(); // FIRST LINE
// ... rest of your code
}
这是2号错误,它给了我两次。 PublishSingleFile=true 不将本地 SQLite DLLs 捆绑到您的可执行文件中。 您必须将其与可执行文件一同分发 。
如果你忘记了怎么办:
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
用户应该提取归档并运行可执行文件。 本地库需要在同一目录中 。
winsqlite3 提供者你可能会看到一些建议被使用 SQLitePCLRaw.provider.winsqlite3 在 Windows 上使用OS 提供的 SQLite 。 不要。 它只在 Windows 上起作用, 需要手动初始化, 并打破跨平台构建 。 bundle_e_sqlite3.
如果您使用实体框架核心与 SQLite , 您将得到精密警告( IL 2026, IL 3050) 。 这些通常对于 EF Core 的 SQLite 提供商来说是安全的, 但要彻底测试 :
<PropertyGroup>
<NoWarn>$(NoWarn);IL2026;IL3050</NoWarn>
</PropertyGroup>
最好考虑使用 Dapper 或原始 ADO. NET 与 SQLite 用于 AOT 应用程序,
在 Windows 上, 土著 AOT 需要视觉工作室 MSVC 的 MSVC 链接。 如果您在开发者指令提示之外建构, 您将会在 vswhere.exe。 GitHub Action 自动处理此操作,但对于本地建筑,则使用:
或者在您的构建脚本中初始化环境 :
& "C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\Launch-VsDevShell.ps1" -Arch amd64
dotnet publish -c Release -r win-x64
土著AOT 完美地适用于:
避免 ATO 用于 :
如果你读过这整篇文章, 只想得到一份核对表,
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与原住民AOT合作并不明显, 但一旦你知道魔法咒语SQLitePCLRaw.bundle_e_sqlite3 + Batteries.Init()这是直截了当的。回报是巨大的: 微小的二进制,即时启动, 以及有能力在不依赖运行的情况下 部署到任何平台。
对于使用 . NET 建立 CLI 工具、 网关或边缘应用程序的任何人来说, 使用 SQLite 的本地 AOT 现在是一个现实的选择。 GitHub Action 工作流程在此提供将整个多平台构建过程自动化 — — 只要标记一个发布, 你就可以完成 。
如果您是 AOT 的新人, 请先从小开始: 先转换一个简单的 CLI 工具或工具 。 适应构建过程, 学习要预知的警告, 并理解限制 。 一旦您将基本内容降低, 您就可以处理更复杂的应用程序 。
. NET 生态系统越来越对 AOT 友好。 多数现代图书馆要么在盒子外工作,要么有清晰的AOT支持文件。 未来是本地的,而且比你想的要快。
我将AOT编程的入口 用于:
部署过程在各地都是相同的:
没有安装.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"]
生成图像为 ~130MB 传统的.NET容器为200-2500MB。
我在这里展示的所有东西 都来自一个真实的、可生产的项目。
此工程是一个最小的 YARP 反向代理, 带有将签名登录到 SQLite 的机器人检测中间软件。 它显示 :
克隆它,研究它,使用它 作为模板 你自己的AOT项目。
现在去建造一些小的和快速的东西。
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.