一起谈.NET技术,重新认识C#: 玩转指针

  许多文章并不鼓励在C#下使用指针开发,不过,本文偏偏要这样做。在大量尝试C#下使用指针开发之后,你会对C#有更深的认识。

  在说C#下的指针之前,需要提一下C++/CLI。C++/CLI 我们可以把它看作两部分:Native C++和 Managed C++,两者可以无缝结合。对C#,我们也可以把它看作两部分:Managed C# 和 Unmanaged C#。Managed C# 和 Unmanaged C# 是我杜撰的两个词,前者就是我们通常的C#,后者就是使用指针、Struct和非托管内存的C#。事实证明,Unmanaged C#也可以玩的十分优雅——它具有C语言的大部分特性,却比C语言好用的多。 C# 与 C++/CLI之间的对应关系见下图:

  

   C++/CLI默认是 Native C++,而C# 默认是 Unmanaged C# 。除了不能内嵌汇编以及编译方式不同之外,C++/CLI和C#两者在层面上几乎是等价的。其中,C++/CLI略微偏底层一点,C#略微偏高层一点。尽管略微偏高层一点,C#仍然可以当成准系统语言来玩。你可以将Unmanaged C# 当作 mini c 来玩,区别只是,C 语言一般是编译执行的,而 Unmanaged C# 是先编译成 IL ,再使用Ngen编译成机器码或在运行时编译成机器码执行。

  在C#下不鼓励使用指针,这是因为C#的定位是应用级的开发,如果我们把它定位为低一级别的开发,那么,就需要大量的使用指针了。大量使用指针进行Unmanaged C#开发,“本质”上就是使用 C 语言。只是因为目前 JIT 技术发展年代仍不够久远,导致 Unmamaged C# 的性能较 C 语言 略低。

  下面,画张图,描述一下当下的C#语言。

  

 

 

  当下的C#包含了五种编程范式:类C、OO、泛型、Lambda、Dynamic。关于 OO、泛型、Lambda、Dynamic已经有很多文章介绍和总结了,关于类C这一块却很少有人写文章详细介绍。就像Ajax重新发现了javascript一样,我们也应该去重新发现C#中的Unmanaged 成分。

  回看程序设计语言的发展史。C语言是一直的王者。但是由于抽象能力不足,在C的基础上出现了C++,后来又出现了帮你管家的保姆Java,于是,在系统层开发使用C/C++,在应用层开发使用Java成为一种常见的分工方式。有没有一种语言同时具备Java的快速开发优势和C/C++的高性能且能直接访问内存这两个优点呢?D语言就是奔着这个目标设计的。许多人对D语言报以厚望。可问题是,D语言看起来很美,但太草根了,各方面都不成熟。C#诞生之后,人们认为它和Java的定位是类似的,我也一直这样认为。同时,我还在寻找能够快速开发、自己管理内存、拥有庞大的类库的另一种语言,来进行高性能开发及实时开发。我看过D语言,看过Haskell语言,都不是我想要的,转一圈回来,发现,原来答案已在自己的手中,那就是已经用了很多年的C#——C#的Unmanaged部分。

  开发过一个软实时系统,每秒钟有数百万对象生灭,是使用C++开发的。C++开发效率低下,我想寻找一个替代品。最先找到的是Java,由于GC的存在,在Java下开发软实时系统比较困难,以至于出现了专门的Java实时规范和实时Java虚拟机。当时接触C#不久,想,为什么C#下没人研究实时系统?现在知道了答案:那就是开发实时性应用,相对于Java,C#具有非常大的优势——由于Unmanaged 部分的存在,不需要专门的C#实时虚拟机。C# 中,GC 是无法直接插手非托管内存的,如果只有寥寥无几的对象在托管内存中,每一次GC时间十分短暂,可以忽略不计。

  这两年开始进行图像处理方面的程序开发。图像处理开发,C/C++是王道。不过,C/C++开发效率低下是个大问题,同样需要寻找替代品。最开始我使用的是C++/CLI,使用后发现,C++/CLI 不好用,它继承了C++的所有缺点,最不能忍受的是狂慢的编译速度。C++/CLI的CLI部分虽然可以使用.Net的庞大的类库,但是没有C#自然。有没有一种更好的方式平衡开发效率和运行效率?有!那就是打开unsafe之后的C#:优雅的语法、快速的编译、庞大的类库、完美的IDE、想托管内存就托管内存,不想托管就不托管——犀利!非常的犀利!无比的犀利!。

  在《编写高效的C#图像处理程序——我的实验》和《编写高效的C#图像处理程序——我的实验(续)》两篇文章中,我使用指针,得到了近似C语言的性能。因此,不必担心C#的性能。

  C#目前包括的五种编程范式:类C、OO、泛型、Lambda、Dynamic,这五种编程范式几乎可以无缝的结合,熟练使用这些编程范式,可以把C#下的指针玩的天花乱坠:

  (1)Class和Struct中可以直接包含指针成员,这样,我们可以设计一套自己的继承体系(当然,得在托管内存中。不过,可以将性能攸关部分放在非托管内存中,然后,将它的指针放到Class中,遵循Disposable模式来管理,避免内存泄漏。)

  (2)C#下的泛型不支持泛型Class的指针,于是,我在《C#模板编程(1):有了泛型,为什么还需要模板?》和《C#模板编程(2): 编写C#预处理器,让模板来的再自然一点》这两篇文章中编写了C#的预处理器,再结合using关键字和partial关键字实现了对C++模板的模拟,用以Unmanaged C#代码的强类型复用。

  这样处理,就写出了几个纯C#开发的高性能C#图像处理基本类,见博文《发布我的高性能纯C#图像处理基本类,顺便也挑战一下极限。:)》。

  这些基本类可以通过指针访问图像的像素,也可以通过索引器来访问像素,也可以通过迭代器来访问像素。通过指针访问速度最快,但比较麻烦。通过索引器和迭代器访问比较慢,但比较方便。不过,通过索引器和迭代器来访问像素很容易误用,比如说,假设图像是A。A[1,2]可以获得图像的第1行(首行为第0行),第2列(首列为第0列)的像素。假设想更改这个像素的Red值为5,这样写是无效的:A[1,2].Red = 5。因为,A[1,2]是一个Struct实例,它是坐标为(1,2)的像素值的“快照”,对A[1,2]的修改无法写入到图像像素中去,需要这样写才能实现真正的修改:Rgb24 item = A[1,2];item.Red = 5; A[1,2]=item。同理,通过迭代器访问,也无法修改像素具体值。

  这样处理既不优雅,又容易误用。怎么办呢?思来想去,我决定取消它!改用另一种方式提供对图像像素的便捷访问。什么办法呢?Lambda表达式!可是,问题来了,C#下的泛型不支持具体的指针类型作为泛型类型,好在关上了一扇门,C#又打开了另一扇门——delegate 支持指针类型!于是,使用《C#模板编程(1):有了泛型,为什么还需要模板?》和《C#模板编程(2): 编写C#预处理器,让模板来的再自然一点》这两篇文章中提出的C#模板开发技巧,编写代码,有:

ImageClassHelper_Template.cs

 1 using TPixel = System.Byte; 
 2  using TCache = System.Int32; 
 3  using TKernel = System.Int32; 
 4 
 5 using System; 
 6 using System.Collections.Generic; 
 7 using System.Text; 
 8 
 9 namespace Orc.SmartImage.Hidden 
10 { 
11     static class ImageClassHelper_Template 
12     { 
13         #region mixin 
14 
15         public unsafe delegate void ActionOnPixel(TPixel* p); 
16         public unsafe delegate Boolean PredicateOnPixel(TPixel* p); 
17 
18         public unsafe static void ForEach(this UnmanagedImage<TPixel> src, ActionOnPixel handler) 
19         { 
20             TPixel* start = (TPixel*)src.StartIntPtr; 
21             TPixel* end = start + src.Length; 
22             while (start != end) 
23             { 
24                 handler(start); 
25                 ++start; 
26             } 
27         } 
28 
29         public unsafe static Int32 Count(this UnmanagedImage<TPixel> src, PredicateOnPixel handler) 
30         { 
31             TPixel* start = (TPixel*)src.StartIntPtr; 
32             TPixel* end = start + src.Length; 
33             Int32 count = 0; 
34             while (start != end) 
35             { 
36                 if (handler(start) == true) count++; 
37                 ++start; 
38             } 
39             return count; 
40         } 
41 
42         public unsafe static Int32 Count(this UnmanagedImage<TPixel> src, Predicate<TPixel> handler) 
43         { 
44             TPixel* start = (TPixel*)src.StartIntPtr; 
45             TPixel* end = start + src.Length; 
46             Int32 count = 0; 
47             while (start != end) 
48             { 
49                 if (handler(*start) == true) count++; 
50                 ++start; 
51             } 
52             return count; 
53         } 
54 
55         public unsafe static List<TPixel> Where(this UnmanagedImage<TPixel> src, PredicateOnPixel handler) 
56         { 
57             List<TPixel> list = new List<TPixel>(); 
58 
59             TPixel* start = (TPixel*)src.StartIntPtr; 
60             TPixel* end = start + src.Length; 
61             while (start != end) 
62             { 
63                 if (handler(start) == true) list.Add(*start); 
64                 ++start; 
65             } 
66 
67             return list; 
68         } 
69 
70         public unsafe static List<TPixel> Where(this UnmanagedImage<TPixel> src, Predicate<TPixel> handler) 
71         { 
72             List<TPixel> list = new List<TPixel>(); 
73 
74             TPixel* start = (TPixel*)src.StartIntPtr; 
75             TPixel* end = start + src.Length; 
76             while (start != end) 
77             { 
78                 if (handler(*start) == true) list.Add(*start); 
79                 ++start; 
80             } 
81 
82             return list; 
83         } 
84 
85         #endregion 
86     } 
87 } 

  这样一来,就提供了ForEach扩展方法,可以通过指针直接访问具体的像素。同时,我也顺便实现了Count和Where两个扩展方法。Count和Where两个扩展方法同时提供了指针版本和非指针版本。

  然后,编写类 Rgb24ImageClassHelper:

Rgb24ImageClassHelper.cs

 1 using System; 
 2 using System.Collections.Generic; 
 3 using System.Text; 
 4 
 5 namespace Orc.SmartImage 
 6 { 
 7     using TPixel = Rgb24; 
 8     using TCache = System.Int32; 
 9     using TKernel = System.Int32; 
10 
11     public static partial class Rgb24ImageClassHelper 
12     { 
13         #region include "ImageClassHelper_Template.cs" 
14         #endregion 
15     } 
16 }

  编译之后,就可以通过Lambda表达式通过指针来访问 UnmanagedImage 实例中的像素。例子&性能测试为:

例子与性能测试代码

 1 Rgb24Image rgb24 = new Rgb24Image(map); 
 2 
 3 // 将每个像素的Blue值改为 50
 4 
 5 CodeTimer.Time("ForEachByLambdaWithPointer-" + imgName, 1, () => 
 6 { 
 7     rgb24.ForEach((Rgb24* p) => { p->Blue = 50; }); 
 8     Console.WriteLine(rgb24.Start->Blue); 
 9 }); 
10 
11 CodeTimer.Time("ForEachByPointer-" + imgName, 1, () => 
12 { 
13     Rgb24* start = rgb24.Start; 
14     Rgb24* end = rgb24.Start + rgb24.Length; 
15     while (start != end) 
16     { 
17         start->Blue = 50; 
18         ++start; 
19     } 
20     Console.WriteLine(rgb24.Start->Blue); 
21 }); 
22 
23 CodeTimer.Time("CountByLambdaWithPointer-" + imgName, 1, () => 
24 { 
25     Console.WriteLine(rgb24.Count((Rgb24* p) => { return p->Blue > 50; })); 
26 }); 
27 
28 CodeTimer.Time("CountByLambdaWithValue-" + imgName, 1, () => 
29 { 
30     Console.WriteLine(rgb24.Count((Rgb24 c) => { return c.Blue > 50; })); 
31 }); 
32 
33 CodeTimer.Time("WhereByLambdaWithPointer-" + imgName, 1, () => 
34 { 
35     Console.WriteLine(rgb24.Where((Rgb24* p) => { return p->Blue > 50; }).Count); 
36 }); 
37 
38 CodeTimer.Time("WhereByLambdaWithValue-" + imgName, 1, () => 
39 { 
40     Console.WriteLine(rgb24.Where((Rgb24 c) => { return c.Blue > 50; }).Count); 
41 });

  测试结果:

测试结果

 1 ForEachByLambdaWithPointer-5000_6000_24 
 2 50 
 3         Time Elapsed:   210ms 
 4         CPU Cycles:     333,752,386 
 5         Gen 0:          0 
 6         Gen 1:          0 
 7         Gen 2:          0 
 8 
 9 ForEachByPointer-5000_6000_24 
10 50 
11         Time Elapsed:   76ms 
12         CPU Cycles:     116,868,697 
13         Gen 0:          0 
14         Gen 1:          0 
15         Gen 2:          0 
16 
17 CountByLambdaWithPointer-5000_6000_24 
18 0 
19         Time Elapsed:   249ms 
20         CPU Cycles:     425,180,283 
21         Gen 0:          0 
22         Gen 1:          0 
23         Gen 2:          0 
24 
25 CountByLambdaWithValue-5000_6000_24 
26 0 
27         Time Elapsed:   484ms 
28         CPU Cycles:     850,295,952 
29         Gen 0:          0 
30         Gen 1:          0 
31         Gen 2:          0 
32 
33 WhereByLambdaWithPointer-5000_6000_24 
34 0 
35         Time Elapsed:   242ms 
36         CPU Cycles:     425,229,156 
37         Gen 0:          0 
38         Gen 1:          0 
39         Gen 2:          0 
40 
41 WhereByLambdaWithValue-5000_6000_24 
42 0 
43         Time Elapsed:   496ms 
44         CPU Cycles:     855,667,758 
45         Gen 0:          0 
46         Gen 1:          0 
47         Gen 2:          0

  可见:使用Lambda表达式通过指针来访问像素比使用指针直接访问像素慢,大概速度是后者的 1/2 - 1/3 。而使用Lambda表达式通过值来访问像素比使用Lambda表达式通过指针来访问像素要慢。大概速度是后者的1/2。虽然速度慢下来了,但对于性能不攸关的地方,这样处理还是值得的——使用Lambda表达式可以让代码更简洁更优雅!

  好了,现在,类C,OO,泛型/模板,Lambda表达式就全揉在一起了,至于具体怎么用,就看具体情况下的权衡取舍了。如果再玩玩Dynamic,大概会有更有趣的玩法。

  现在看来,C#真是太NB了!通吃啊!

时间: 2024-07-30 14:00:39

一起谈.NET技术,重新认识C#: 玩转指针的相关文章

魏建波谈网络推广:教你玩转百度空间(四)

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 上一节注重分享如何利用百度空间做外链,这一节我们来谈一下,如何利用百度空间获取大把大把的流量.魏建波测试后,发现可以通过一下几个方法获取大把的流量. 痕迹营销:互访.留言 这是最基本的一种获取流量的方法,没什么技术含量,也是非常容易批量操作的方法.说几点注意的地方:互访,会在对方的空间留下你访问的痕迹.如果你设置一个美女头像,这样可以引起更多

魏建波谈网络推广:教你玩转百度空间(三)

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 前面分享了一下百度空间最基础的知识,俗话说的好:万丈高楼平地起,主要看基础.想在网络这个世界里能走得更长远.更稳健,打好基础是必修课.今天魏建波为大家分享一下,百度空间应用中哪些产品对网站外链起的作用比较大.这几天为了分享经验,我原来的一个老博客也被K了.好多收录的文章都被删除了.总结了一下:凡事要适度,过犹而不及.做外链也是一个道理. 外链

《创业家》牛文文:少谈点模式多谈点技术

"模式"如同当年的"主义",流行于各种创业大赛.创业励志节目.论坛的"街头"式秀场 文/创业家 牛文文 "美国某某公司你知道吧?就是刚被戴尔.惠普.思科十几亿美元抢购的那家.我们的模式和它的一样,现在还没赢利,可将来起码有十几亿人民币的市值." "我开了小煤矿,但煤运不出去,上商学院之后受到启发,想搞模式创新,具体讲就是想在铁路边上搞个煤炭物流开发区,建一个大的物流和信息流平台,把分散的煤炭集中在我这个园区,这样和铁

魏建波谈网络推广:教你玩转百度空间(二)

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 魏建波谈网络推广:教你玩转百度空间(一)教大家如何注册.设置.装扮空间,最基本的内容.接下来给大家分享一下: 空间如何排版才能吸引访问者的眼球? 按空间的主题分类,主要分为3种,一种是宣传产品.宣传内容.诱惑类,大家可以使用一键式排版功能.宣传产品可以选择相册型空间,主要突出你的产品;宣传内容可以选择博客型空间,主要突出你的内容;做诱惑类的内

《C程序设计新思维》一第6章 玩转指针6.1 自动、静态和手工内存

第6章 玩转指针 C程序设计新思维他就是那个 喜欢我们所有歌曲的人, 他喜欢一起哼唱, 他喜欢边开枪边唱, 但是他不知道这歌的意义. --选自Nirvana的歌曲"In Bloom(风华正茂)"就像一首描述音乐的歌曲.一部刻画好莱坞的电影,指针就是一种描述其他数据的数据.我们很容易被指针搞崩溃,像引用的引用.别名.内存管理和malloc之类的东西,很容易把我们搞得天旋地转.但是,这些纷繁复杂的痛苦可以分解为独立的片段.例如,我们可以使用指针作为别名,这样就不需要再关注malloc,20

一起谈.NET技术,也玩MVC3.0 Razor自定义视图引擎来修改默认的Views目录结构

刚刚爱上MVC3.0,几个不眠夜的学习越来越有趣.今天随手尝试自定义Mvc3.0的视图引擎,虽然已成功,但是还发现有点小疑问.随手贴出来希望大家指教指教. MVC的视图文件目录被固定/Views目录内,区域视图文件也是被固定在/Areas目录下,出于好奇和对目录名的敏感,尝试修改它.通过reflector找到视图引擎的构造接口类VirtualPathProviderViewEngine 在MVC2.0中,自定义自己的视图引擎,继承它即可,但在3.0中,我发现继承它会缺少一个函数.再reflect

和王敬谈模式:最精不过玩预付

王 敬 曾任普尔斯马特中国采购总监,物美集团高级副总裁,罗兰贝格国际管理咨询(上海)有限公司副总裁.合伙人,华润万家有限公司 首席运营官,宏梦卡通集团首席执行官 最精不过玩预付 预付费生意的核心是把消费者的需求进行分类,然后变成一个向后端采购的模式,继而通过数据支持形成优势.准备性越高,钱的应用效率也就越高 特邀主持人|王敬 嘉宾|杨帆 整理|<创业家>记者 胡宇萌 在消费领域,很多人都觉得预付是门很好的生意,其他的好处不说,现金流一定是充沛的.在我之前服务的公司,也曾尝试过推出具有储值功能的

一起谈.NET技术,抛砖引玉:我看微软.NET各子技术领域之应用前景

从2002年发布.NET 1.0,历经8年发展,.NET发展到了4.0,已经成为一个庞大而复杂的软件开发与运行平台,其架构日益复杂,其应用领域也在不断地扩展,包容了"一堆"的子技术领域.在.NET 4.0即将发布之际,回顾一下已发布的各项.NET技术,看看哪些技术用得很火,哪些被打入冷宫,再猜猜.NET 4.0中可能会有哪些技术会得到"青睐",是件有意思的事. 1. 桌面应用程序开发技术( Windows Form和WPF) 在.NET桌面应用程序开发领域,Wind

一起谈.NET技术,Silverlight 2.5D RPG游戏技巧与特效处理:(十一)AI系统

谈到人工智能(AI),这个话题就太大了:大学里有<人工智能教程>专门讲这方面的知识,什么大名鼎鼎的人工神经网络.遗传算法等等均可一窥究竟,这里如赘述似乎有些班门弄斧,我们暂且丢它一边去吧. 本节,我的主要目的是与大家共同探讨AI在RPG游戏中的应用.看过之前教程的朋友一定不会陌生,A*算法就是其中的一个重要组成部分:而本系列Demo中则使用了更为高级的改进型A*算法,不仅优化了性能,同时也大幅提升了玩家的操控体验.除此之外,AI更常见于RPG游戏中的角色,接下来我将引领大家循着AI的足迹,逐步