hez2010の日常

kekekekekekeke

.NET 5 中的隐藏特性

前言 双十一当天 .NET 5 正式发布带来了很多的新特性和改进,个人觉得非常香,并且花了 10 分钟时间就把自己的 4 个 .NET Core 3.1 的项目升级到了 .NET 5,堪称无痛。 但是,.NET 5 中还有一些没有正式公开的隐藏特性,那么现在就开始介绍吧。 Crossgen 2 Crossgen 2 とは Crossgen 其实就是众所周知的 ReadyToRun 特性。该功能将你的程序集进行一定程度的 AOT 编译,然后在运行时跟踪热路径对一些方法进行带有更多优化的 JIT 编译,即分层编译,这使得程序集的加载速度大幅提高。 但是 .NET 5 其实带了 Crossgen 的下一个版本:Crossgen 2。 Crossgen 2 的代码几乎是从 CoreRT 继承而来,并在此基础上做了很大改进。CoreRT 可以对 .NET 程序集进行完全的原生优化编译,编译出来的东西就是完全 native 的,和 Go 的体验完全一致。 Crossgen 2 则使用了这套方法,将你的程序集在支持范围之内进行 Native AOT 编译,然后运行时直接加载启动,并根据运行情况再使用 JIT 编译器进行进一步的优化,是一种混合 AOT 策略。 为什么说在支持范围之内呢?因为 Native AOT 必然对动态加载和 Emit 等特性不友好,但是 Crossgen 2 对于这些地方则直接跳过,并且由于是混合 AOT 方案,运行时依然存留有 JIT,因此这些功能完全不会受到影响。 使用 使用方法很简单,在你发布程序的时候加命令行参数 /p:PublishReadyToRun=true /p:PublishReadyToRunUseCrossgen2=true 即可,例如: dotnet publish -c Release -r win-x64 /p:PublishReadyToRun=true /p:PublishReadyToRunUseCrossgen2=true 注意 由于该功能尚未正式发布,并且存在一些已知的问题还没有解决,因此如果要使用的话建议对发布出的程序做好测试。 另外,.NET 6 将会用 Crossgen 2 代替现有的 Crossgen 1,追求稳定的话可以等到明年再用。 栈上替换 栈上替换とは 栈上替换,即 On Stack Replacement。这个特性允许在运行时,即使一个方法有活跃的栈帧也能直接替换实现。 因此对于分层 JIT 功能来说,这个特性就允许 JIT 将未经优化的代码直接切换成经过优化的代码,即使被切换的方法存在活跃栈帧也没问题。 使用 这是一个运行时特性,需要通过设置两个环境变量来开启: bash: export COMPlus_TC_QuickJitForLoops=1 export COMPlus_TC_OnStackReplacement=1 cmd: set COMPlus_TC_QuickJitForLoops=1 set COMPlus_TC_OnStackReplacement=1 pwsh: $env:COMPlus_TC_QuickJitForLoops = 1 $env:COMPlus_TC_OnStackReplacement = 1 注意 当前仅支持 x64,且目前处于实验性阶段。 更激进的发布裁剪 发布裁剪とは 发布裁剪可以在发布时将没有用到的代码裁剪掉,使得发布出去的程序体积大幅度减小。 但是 .NET 5 默认的裁剪行为是程序集粒度的,意味着会保留用到了的程序集,哪怕你只用了程序集中的一个方法,整个程序集也会被保留下来。 但是 .NET 5 提供了一种更为激进的裁剪方式,基于方法粒度进行裁剪。 开启这个特性之后,如果一个程序集只被调用了一个方法,那裁剪后将只会保留这一个方法,而不是保留整个程序集。 使用 […]

Read More

新しい関数ポインタの機能を試しましょう

はじめに .NET 5には新しい機能「関数ポインタ」を追加されました。これは、ネイティブ相互運用を改善し、パフォーマンスを上げるために作られました。 関数ポインタは下位レベルのデリゲートで、関数呼び出しのより効率的な方法を提供します。既知の方法にSystem.Action<>, System.Func<>といい、System.Delegateといい、どちらもオーバーヘッドがたくさんあります。でも関数ポインタはそういうオーバーエンドがありません。 さらに関数ポインタはマネージ関数のみをサポートすることではなく、アンマネージ関数もサポートされています。だからこそ、ネイティブ相互運用を行う場合は、関数ポインタを使用すると非常に便利になります。 じゃ、早速始めましょう。 使い方 関数ポインタの型の形式は delegate* + managed/unmanaged + [呼び出し規則(ネイティブのみ)]+<パラメータリスト(void を使えます)>です。例えば: 言語宣言関数ポインタの型C++void __cdecl f()delegate* unmanaged[Cdecl]<void>C++void __cdecl f(int)delegate* unmanaged[Cdecl]<int, void>C++int __cdecl f(int)delegate* unmanaged[Cdecl]<int, int>C#static string f(int)delegate* managed<int, string>C#static void f(string)delegate* managed<string, void> [Cdecl] のみならず、[Stdcall]と[Fastcall]と[Thiscall]も使えますよ。 アンマネージ関数の使い方 まずはネイティブ相互運用を紹介しましょう。 C++コードを書けます: #define WIN32 #define UNICODE #include <cstring> #include <cstdio> extern “C” __declspec(dllexport) // C# 関数はパラメーター gen […]

Read More

新版 C# 高效率编程指南

前言 C# 从 7 版本开始一直到如今的 9 版本,加入了非常多的特性,其中不乏改善性能、增加程序健壮性和代码简洁性、可读性的改进,这里我整理一些使用新版 C# 的时候个人推荐的写法,可能不适用于所有的人,但是还是希望对你们有所帮助。 注意:本指南适用于 .NET 5 或以上版本。 使用 ref struct 做到 0 GC C# 7 开始引入了一种叫做 ref struct 的结构,这种结构本质是 struct ,结构存储在栈内存。但是与 struct 不同的是,该结构不允许实现任何接口,并由编译器保证该结构永远不会被装箱,因此不会给 GC 带来任何的压力。相对的,使用中就会有不能逃逸出栈的强制限制。 Span<T> 就是利用 ref struct 的产物,成功的封装出了安全且高性能的内存访问操作,且可在大多数情况下代替指针而不损失任何的性能。 ref struct MyStruct { public int Value { get; set; } } class RefStructGuide { static void Test() { […]

Read More

.NET 异步解说

前言 要了解 .NET 中的 async/await 机制,首先需要有操作系统原理的基础,否则的话是很难理解清楚的,如果没有这些基础而试图向他人解释,大多也只是基于现象得到的错误猜想。 初看异步 说到异步大家应该都很熟悉了,2012 年 C# 5 引入了新的异步机制:Task,并且还有两个新的关键字 await 和 async,这已经不是什么新鲜事了,而且如今这个异步机制已经被各大语言借鉴,如 JavaScript、TypeScript、Rust、C++ 等等。 下面给出一个简单的对照: 语言调度单位关键字/方法C#Task<>、ValueTask<>async、awaitC++std::future<>co_awaitRuststd::future::Future<>.awaitJavaScript、TypeScriptPromise<>async、await 当然,这里这并不是本文的重点,只是提一下,方便大家在有其他语言经验的情况下(如果有),可以认识到 C# 中 Task 和 async/await 究竟是一个和什么可以相提并论的东西。 多线程编程 在该异步编程模型诞生之前,多线程编程模型是很多人所熟知的。一般来说,开发者会使用 Thread、std::thread 之类的东西作为线程的调度单位来进行多线程开发,每一个这样的结构表示一个对等线程,线程之间采用互斥或者信号量等方式进行同步。 多线程对于科学计算速度提升等方面效果显著,但是对于 IO 负荷的任务,例如从读取文件或者 TCP 流,大多数方案只是分配一个线程进行读取,读取过程中阻塞该线程: void Main() { while (true) { var client = socket.Accept(); new Thread(() => ClientThread(client)).Start(); } } void ClientThread(Socket client) { […]

Read More

手写一个简易的多周期 MIPS CPU

一点前言 多周期 CPU 相比单周期 CPU 以及流水线的实现来说其实写起来要麻烦那么一些,但是相对于流水线以及单周期 CPU 而言,多周期 CPU 除了能提升主频之外似乎并没有什么卵用。不过我的课题是多周期 CPU 那么就开始吧。 多周期 CPU 不同于单周期 CPU,多周期 CPU 指的是将整个 CPU 的执行过程分成几个阶段,每个阶段用一个时钟去完 成,然后开始下一条指令的执行,而每种指令执行时所用的时钟数不尽相同,这就是所谓的多周期CPU。 CPU在处理指令时,一般需要经过以下几个阶段: (1) 取指令(IF):根据程序计数器 PC 中的指令地址,从存储器中取出一条指令,同时,PC 根据指令字长度自动递增产生下一条指令所需要的指令地址,但遇到“地址转移”指令 时,则控制器把“转移地址”送入 PC,当然得到的“地址”需要做些变换才送入 PC。 (2) 指令译码(ID):对取指令操作中得到的指令进行分析并译码,确定这条指令需要完成的操作,从而产生相应的操作控制信号,用于驱动执行状态中的各种操作。 (3) 指令执行(EXE):根据指令译码得到的操作控制信号,具体地执行指令动作,然后转移到结果写回状态。 (4) 存储器访问(MEM):所有需要访问存储器的操作都将在这个步骤中执行,该步骤给出存储器的数据地址,把数据写入到存储器中数据地址所指定的存储单元或者从存储器中得 到数据地址单元中的数据。 (5) 结果写回(WB):指令执行的结果或者访问存储器中得到的数据写回相应的目的寄存器中。 这也就意味着一条 CPU 指令最长需要 5 个时钟周期才能执行完毕,至于具体需要多少周期则根据指令的不同而不同。 MIPS 指令集的设计为定长简单指令集,这为 CPU 的实现带来了极大的方便。 指令集 MIPS 指令分为三种:R、I 和 J,三种指令有不同的存储方式: 其中, op:操作码;rs:第1个源操作数寄存器,寄存器地址(编号)是00000~11111,00~1F;rt:第2个源操作数寄存器,或目的操作数寄存器,寄存器地址(同上);rd:目的操作数寄存器,寄存器地址(同上);sa:位移量(shift […]

Read More

你所不知道的 C# 中的细节

前言 有一个东西叫做鸭子类型,所谓鸭子类型就是,只要一个东西表现得像鸭子那么就能推出这玩意就是鸭子。 C# 里面其实也暗藏了很多类似“鸭子类型”的东西,但是很多开发者并不知道,因此也就没法好好利用这些东西,那么今天我细数一下这些藏在编译器中的细节。 至于为什么 C# 要这么做,其实有部分的原因是为了让不能实现接口的 ref struct 也能实现对应的功能。 不是只有 Task 和 ValueTask 才能 await 在 C# 中编写异步代码的时候,我们经常会选择将异步代码包含在一个 Task 或者 ValueTask 中,这样调用者就能用 await 的方式实现异步调用。 西卡西,并不是只有 Task 和 ValueTask 才能 await。Task 和 ValueTask 背后明明是由线程池参与调度的,可是为什么 C# 的 async/await 却被说成是 coroutine 呢? 因为你所 await 的东西不一定是 Task/ValueTask,在 C# 中只要你的类中包含 GetAwaiter() 方法和 bool IsCompleted 属性,并且 GetAwaiter() 返回的东西包含一个 GetResult() 方法、一个 […]

Read More

C# 9 新特性:代码生成器、编译时反射

前言 今天 .NET 官方博客宣布 C# 9 Source Generators 第一个预览版发布,这是一个用户已经喊了快 5 年特性,今天终于发布了。 简介 Source Generators 顾名思义代码生成器,它允许开发者在代码编译过程中获取查看用户代码并且生成新的 C# 代码参与编译过程,并且可以很好的与代码分析器集成提供 Intellisense、调试信息和报错信息,可以用它来做代码生成,因此也相当于是一个加强版本的编译时反射。 使用 Source Generators,可以做到这些事情: 获取一个 Compilation 对象,这个对象表示了所有正在编译的用户代码,你可以从中获取 AST 和语义模型等信息可以向 Compilation 对象中插入新的代码,让编译器连同已有的用户代码一起编译 Source Generators 作为编译过程中的一个阶段执行: 编译运行 -> [分析源代码 -> 生成新代码] -> 将生成的新代码添加入编译过程 -> 编译继续。 上述流程中,中括号包括的内容即为 Source Generators 所参与的阶段和能做到的事情。 作用 .NET 明明具备运行时反射和动态 IL 织入功能,那这个 Source Generators 有什么用呢? 编译时反射 – 0 […]

Read More

Hello world!

ようこそ、こちらは最初の投稿です。 Welcome! This is my first post.

Read More