聚美优品网站建设的目标安卓app开发流程
聚美优品网站建设的目标,安卓app开发流程,北京制作公司网站,网络营销公司搭建平台匠仲习滦然后只需要提供一个已经初始化为 0 的字节数组作为内存、一个指向数组的指针、以及用于输入输出的两个字节流就能够让程序运行了。
比如 Hello World! 程序就可以写成#xff1a;
Copy
[-]
.......
..------…匠仲习滦然后只需要提供一个已经初始化为 0 的字节数组作为内存、一个指向数组的指针、以及用于输入输出的两个字节流就能够让程序运行了。比如 Hello World! 程序就可以写成Copy[-].........------.--------...C# 类型系统入门#既然要用 C# 类型系统来构建 Brainfuck 的编译器我们需要首先对 C# 类型系统有一些认知。泛型系统#C# 的类型系统构建在 .NET 的类型系统之上而众所周知 .NET 是一个有具现化泛型的类型系统的平台意味着泛型参数不仅不会被擦除还会根据泛型参数来分发甚至特化代码。例如Copyclass Foo{public void Print() Console.WriteLine(default(T)?.ToString() ?? null);}对于上面的代码调用 new Foo().Print() 会输出 0调用 new Foo().Print() 会输出 0001-01-01T00:00:00而调用 new Foo().Print() 则会输出 null。更进一步因为 .NET 泛型在运行时会根据类型参数对代码进行特化比如Copyclass Calculator where T : IAdditionOperators{public T Add(T left, T right){return left right;}}我们可以前往 godbolt 看看 .NET 的编译器对上述代码产生了什么机器代码CopyCalculator1[int]:Add(int,int):int:this (FullOpts):lea eax, [rsirdx]retCalculator1[long]:Add(long,long):long:this (FullOpts):lea rax, [rsirdx]retCalculator1[ubyte]:Add(ubyte,ubyte):ubyte:this (FullOpts):add edx, esimovzx rax, dlretCalculator1[float]:Add(float,float):float:this (FullOpts):vaddss xmm0, xmm0, xmm1retCalculator1[double]:Add(double,double):double:this (FullOpts):vaddsd xmm0, xmm0, xmm1ret可以看到我代入不同的类型参数进去会得到各自特化后的代码。接口的虚静态成员#你可能好奇为什么上面的 Calculator 里 left 和 right 可以直接加这是因为 .NET 支持接口的虚静态成员。上面的 IAdditionOperators 接口其实定义长这个样子Copyinterface IAdditionOperators{abstract static TResult operator(TSelf self, TOther other);}我们对 T 进行泛型约束 where T : IAdditionOperators 之后就使得泛型代码中可以通过类型 T 直接调用接口中的静态抽象方法 operator。性能#有了上面的知识我想知道在这套类型系统之上.NET 的编译器到底能生成多优化的代码那接下来我们进行一些小的测试。首先让我们用类型表达一下具有 int 范围的数字毕竟之后构建 Brainfuck 编译器的时候肯定会用到。众所周知 int 有 32 位用 16 进制表示那就是 8 位。我们可以给 16 进制的每一个数位设计一个类型然后将 8 位十六进制数位组合起来就是数字。首先我们起手一个 interface IHex然后让每一个数位都实现这个接口。Copyinterface IHex{abstract static int Value { get; }}比如十六进制数位 0、6、C 可以分别表示为Copystruct Hex0 : IHex{public static int Value 0;}struct Hex6 : IHex{public static int Value 6;}struct HexC : IHex{public static int Value 12;}这里我们想把数字和数位区分开因此我们定义一个跟 IHex 长得差不多但是泛型的接口 INum 用来给数字 Int 实现之所以是泛型的是因为给万一没准以后想要扩展点浮点数之类的做考虑Copyinterface INum{abstract static T Value { get; }}struct Int : INumwhere H7 : IHexwhere H6 : IHexwhere H5 : IHexwhere H4 : IHexwhere H3 : IHexwhere H2 : IHexwhere H1 : IHexwhere H0 : IHex{public static int Value{[MethodImpl(MethodImplOptions.AggressiveInlining)]get H7.Value 28 | H6.Value 24 | H5.Value 20 | H4.Value 16 | H3.Value 12 | H2.Value 8 | H1.Value 4 | H0.Value;}}这里我们给 Value 加了 [MethodImpl(MethodImplOptions.AggressiveInlining)] 确保这个方法会被编译器 inline。如此一来如果我们想表达一个 0x1234abcd我们就可以用 Int 来表达。这里我们同样去 godbolt 看看 .NET 编译器给我们生成了怎样的代码CopyInt8[Hex1,Hex2,Hex3,Hex4,HexA,HexB,HexC,HexD]:get_Value():int (FullOpts):push rbpmov rbp, rspmov eax, 0x1234ABCDpop rbpret可以看到直接被编译器折叠成 0x1234ABCD 了没有比这更优的代码属于是真正的零开销抽象。那么性能方面放心了之后我们就可以开始搞 Brainfuck 编译器了。Brainfuck 编译器#Brainfuck 编译分为两个步骤一个是解析 Brainfuck 源代码一个是产生编译结果。对于 Brainfuck 源代码的解析可以说是非常的简单从左到右扫描一遍源代码就可以这里就不详细说了。问题是怎么产生编译结果呢这里我们选择使用类型来表达一个程序因此编译结果自然也就是类型。我们需要用类型来表达程序的结构。基本操作#Brainfuck 程序离不开 4 个基本操作移动指针操作内存输入输出因此我们对此抽象出一套操作接口Copyinterface IOp{abstract static int Run(int address, Span memory, Stream input, Stream output);}然后我们就可以定义各种操作了。首先是移动指针我们用两个泛型参数分别表达移动指针的偏移量和下一个操作Copystruct AddPointer : IOpwhere Offset : INumwhere Next : IOp{[MethodImpl(MethodImplOptions.AggressiveInlining)]public static int Run(int address, Span memory, Stream input, Stream output){return Next.Run(address Offset.Value, memory, input, output);}}然后是操作内存Copystruct AddData : IOpwhere Data : INumwhere Next : IOp{[MethodImpl(MethodImplOptions.AggressiveInlining)]public static int Run(int address, Span memory, Stream input, Stream output){memory.UnsafeAt(address) (byte)Data.Value;return Next.Run(address, memory, input, output);}}我们 Brainfuck 不需要什么内存边界检查因此这里我用了一个 UnsafeAt 扩展方法跳过边界检查Copyinternal static ref T UnsafeAt(this Span span, int address){return ref Unsafe.Add(ref MemoryMarshal.GetReference(span), address);}接下来就是输入和输出了这个比较简单直接操作 input 和 output 就行了Copystruct OutputData : IOpwhere Next : IOp{[MethodImpl(MethodImplOptions.AggressiveInlining)]public static int Run(int address, Span memory, Stream input, Stream output){output.WriteByte(memory.UnsafeAt(address));return Next.Run(address, memory, input, output);}}struct InputData : IOpwhere Next : IOp{[MethodImpl(MethodImplOptions.AggressiveInlining)]public static int Run(int address, Span memory, Stream input, Stream output){var data input.ReadByte();if (data -1){return address;}memory.UnsafeAt(address) (byte)data;return Next.Run(address, memory, input, output);}}控制流#有了上面的 4 种基本操作之后我们就需要考虑程序控制流了。首先我们的程序最终毕竟是要停下来的因此我们定义一个什么也不干的操作Copystruct Stop : IOp{[MethodImpl(MethodImplOptions.AggressiveInlining)]public static int Run(int address, Span memory, Stream input, Stream output){return address;}}然后Brainfuck 是支持循环的这要怎么处理呢其实也很简单模拟 while (*ptr) { 这个操作就行了也就是反复执行当前操作更新指针直到指针指向的数据变成 0然后跳到下一个操作去。Copystruct Loop : IOpwhere Body : IOpwhere Next : IOp{[MethodImpl(MethodImplOptions.AggressiveInlining)]public static int Run(int address, Span memory, Stream input, Stream output){while (memory.UnsafeAt(address) ! 0){address Body.Run(address, memory, input, output);}return Next.Run(address, memory, input, output);}}Hello World!#有了上面的东西我们就可以用类型表达 Brainfuck 程序了。我们来看看最基础的程序Hello World!。Copy[-].........------.--------...上面这个实现可能不是很直观那我们换一种非常直观的实现Copy............这段程序很粗暴的分别把内存从左到右写成 Hello World! 的每一位然后把指针移回到开头后逐位输出。不过这么看 Hello World! 还是太长了不适合用来一上来就展示我们换个简单点的输出 123Copy...表达这个程序的类型自然就是CopyAddData49, AddPointer1, AddData50, AddPointer1, AddData51, // 分别设置 1 2 3AddPointer-2, // 指针移回开头OutputDataStop // 停止这里为了简洁我把数字全都带入了数字类型不然会变得很长。例如实际上 49 应该表达为 Int。那怎么运行呢很简单CopyAddData49, AddPointer1, AddData50, AddPointer1, AddData51, AddPointer-2, OutputData.Run(0, stackalloc byte[8], Console.OpenStandardInput(), Console.OpenStandardOutput());即可。我们可以借助 C# 的 Type Alias这样我们就不需要每次运行都打那么一大长串的类型Copyusing Print123 AddData49, AddPointer1, AddData50, AddPointer1, AddData51, AddPointer-2, OutputData;Print123.Run(0, stackalloc byte[8], Console.OpenStandardInput(), Console.OpenStandardOutput());那我们上 godbolt 看看 .NET 给我们的 Brainfuck 程序产生了怎样的机器代码Copypush rbppush r15push r14push r13push rbxlea rbp, [rsp0x20]mov rbx, rsimov r15, r8movsxd rsi, ediadd rsi, rbxadd byte ptr [rsi], 49 ; 1inc edimovsxd rsi, ediadd rsi, rbxadd byte ptr [rsi], 50 ; 2inc edimovsxd rsi, ediadd rsi, rbxadd byte ptr [rsi], 51 ; 3lea r14d, [rdi-0x02]movsxd rsi, r14dmovzx rsi, byte ptr [rbxrsi]mov rdi, r15mov rax, qword ptr [r15]mov r13, qword ptr [rax0x68]call [r13]System.IO.Stream:WriteByte(ubyte):thisinc r14dmovsxd rsi, r14dmovzx rsi, byte ptr [rbxrsi]mov rdi, r15call [r13]System.IO.Stream:WriteByte(ubyte):thisinc r14dmovsxd rsi, r14dmovzx rsi, byte ptr [rbxrsi]mov rdi, r15call [r13]System.IO.Stream:WriteByte(ubyte):thismov eax, r14dpop rbxpop r13pop r14pop r15pop rbpret这不就是Copy*(ptr) 1;*(ptr) 2;*ptr 3;ptr - 2;WriteByte(*(ptr));WriteByte(*(ptr));WriteByte(*ptr);吗可以看到我们代码里的抽象全都被 .NET 给优化干净了。而前面那个不怎么直观的 Hello World! 代码则编译出CopyAddData8, LoopAddPointer1, AddData4, LoopAddPointer1, AddData2, AddPointer1, AddData3, AddPointer1, AddData3, AddPointer1, AddData1, AddPointer-4, AddData-1, Stop,AddPointer1, AddData1, AddPointer1, AddData1, AddPointer1, AddData-1, AddPointer2, AddData1,Loop,AddPointer-1, AddData-1, Stop,AddPointer2, OutputDataJIT 编译#如果我们想以 JIT 的形式运行 Brainfuck 代码那如何在运行时生成类型然后运行代码呢我们在 .NET 中有完善的反射支持因此完全可以做到运行时创建类型。比如根据数字来生成数字类型Copyvar type GetNum(42);static Type GetHex(int hex){return hex switch{0 typeof(Hex0),1 typeof(Hex1),2 typeof(Hex2),3 typeof(Hex3),4 typeof(Hex4),5 typeof(Hex5),6 typeof(Hex6),7 typeof(Hex7),8 typeof(Hex8),9 typeof(Hex9),10 typeof(HexA),11 typeof(HexB),12 typeof(HexC),13 typeof(HexD),14 typeof(HexE),15 typeof(HexF),_ throw new ArgumentOutOfRangeException(nameof(hex)),};}static Type GetNum(int num){var hex0 num 0xF;var hex1 (num 4) 0xF;var hex2 (num 8) 0xF;var hex3 (num 12) 0xF;var hex4 (num 16) 0xF;var hex5 (num 20) 0xF;var hex6 (num 24) 0xF;var hex7 (num 28) 0xF;return typeof(Int,,,,,,,).MakeGenericType(GetHex(hex7), GetHex(hex6), GetHex(hex5), GetHex(hex4), GetHex(hex3), GetHex(hex2), GetHex(hex1), GetHex(hex0));}同理也可以用于生成各种程序结构上。最后我们只需要对构建好的类型进行反射然后调用 Run 方法即可Copyvar run (EntryPoint)Delegate.CreateDelegate(typeof(EntryPoint), type.GetMethod(Run)!);run(0, memory, input, output);delegate int EntryPoint(int address, Span memory, Stream input, Stream output);AOT 编译#那如果我不想 JIT而是想 AOT 编译出来一个可执行文件呢你会发现因为编译出的东西是类型因此我们不仅可以在 JIT 环境下跑还能直接把类型当作程序 AOT 编译出可执行文件只需要编写一个入口点方法调用 Run 即可Copyusing HelloWorld AddData8, LoopAddPointer1, AddData4, LoopAddPointer1, AddData2, AddPointer1, AddData3, AddPointer1, AddData3, AddPointer1, AddData1, AddPointer-4, AddData-1, Stop,AddPointer1, AddData1, AddPointer1, AddData1, AddPointer1, AddData-1, AddPointer2, AddData1,Loop,AddPointer-1, AddData-1, Stop,AddPointer2, OutputData;static void Main(){HelloWorld.Run(0, stackalloc byte[16], Console.OpenStandardInput(), Console.OpenStandardOutput());}然后调用 AOT 编译Copydotnet publish -c Release -r linux-x64 /p:PublishAottrue /p:IlcInstructionSetnative /p:OptimizationPreferenceSpeed上面的 /p:IlcInstructionSetnative 即 C 世界里的 -marchnativeOptimizationPreferenceSpeed 则是 -O2。运行编译后的程序就能直接输出 Hello World!。性能测试#这里我们采用一段用 Brainfuck 编写的 Mandelbrot 程序进行性能测试代码见 Pastebin。它运行之后会在屏幕上输出mandelbrot_brainfuck这段程序编译出来的类型也是非常的壮观mandelbrot_brainfuck_type去掉所有空格之后类型名称足足有 165,425 个字符这里我们采用 5 种方案来跑这段代码C 解释器C 语言编写的 Brainfuck 解释器直接运行GCC用 Brainfuck 翻译器把 Brainfuck 代码翻译到 C 语言后用 gcc -O3 -marchnative 编译出可执行程序后运行Clang用 Brainfuck 翻译器把 Brainfuck 代码翻译到 C 语言后用 clang -O3 -marchnative 编译出可执行程序后运行.NET JIT通过 JIT 现场生成类型后运行统计之前会跑几轮循环预热.NET AOT通过 .NET NativeAOT 编译出可执行程序后运行测试环境系统Debian GNU/Linux 12 (bookworm)CPU13th Gen Intel(R) Core(TM) i7-13700KRAMCORSAIR DDR5-6800MHz 32Gx2运行 10 次取最优成绩为了避免输出影响性能所有输出重定向到 /dev/null。得出的性能测试结果如下项目 运行时间毫秒 排名 比例C 解释器 4874.6587 5 5.59GCC 901.0225 3 1.03Clang 881.7177 2 1.01.NET JIT 925.1596 4 1.06.NET AOT 872.2287 1 1.00