Hello World
Spiga

从汇编入手,探究泛型的性能问题

2009-05-30 05:21 by 老赵, 24025 visits

经过了《泛型真的会降低性能吗?》一文中的性能测试,已经从实际入手,从测试数据上证明了泛型不会降低程序效率。只是还是有几位朋友谈到,“普遍认为”泛型的代码性能会略差一些,也有朋友正在进一步寻找泛型性能略差的证据。老赵认为这种探究问题的方式非常值得提倡。不过,老赵忽然想到,如果从能从汇编入手,证明非泛型和泛型的代码之间没有性能差距——好吧,或者说,存在性能差距,那么事情不就到此为止了吗?任何理论说明,都抵不过观察计算机是如何处理这个问题来的“直接”。因此,老赵最终决定通过这种极端的方式来一探究竟,把这个问题彻底解决。

需要一提的是,老赵并不希望这篇文章会引起一些不必要的争论,因此一些话就先说在前面。老赵并不喜欢用这种方式来解决问题。事实上,如果可以通过数据比较,理论分析,或者高级代码来说明问题,我连IL都不愿意接触,更别说深入汇编。如果是平时的工作,就算使用WinDbg也最多是查看查看内存中有哪些数据,系统到底出了哪些问题。如果您要老赵表态的话,我会说:我强烈反对接触汇编。我们有太多太多的东西需要学习,如果您并没有明确您的目标,老赵建议您就放过IL和汇编这种东西吧。我们知道这些是什么就行了,不必对它们有什么“深入”的了解。

下面就要开始真正的探索之旅了。这不是一个顺利的旅程,其中有些步骤是连蒙带猜,最后加以验证才得到的结果。原本老赵打算按照自己的思路一步一步进行下去,但是发现这样太过冗余,反而会让大家的思路难以集中。因此老赵最后决定重新设计一个流程,和大家一起步步为营,朝着目标前进。此外,为了方便某些朋友按照这文章亲手进行操作,老赵也制作了一个dump文件,如果您是安装了.NET 3.5 SP1的32位x86系统,可以直接下载进行试验。试验过程中出现的地址也会和文章中完全一致。

废话就说到这里,我们开始吧。

测试代码

测试代码便是我们的目标。和上一篇文章一样,我们准备了一份最简单的代码进行测试,这样可以尽可能摆脱其他因素的影响,得到最正确的结果:

namespace TestConsole
{
    public class MyArrayList
    {
        public MyArrayList(int length)
        {
            this.m_items = new object[length];
        }

        private object[] m_items;

        public object this[int index]
        {
            [MethodImpl(MethodImplOptions.NoInlining)]
            get
            {
                return this.m_items[index];
            }
            [MethodImpl(MethodImplOptions.NoInlining)]
            set
            {
                this.m_items[index] = value;
            }
        }
    }

    public class MyList<T>
    {
        public MyList(int length)
        {
            this.m_items = new T[length];
        }

        private T[] m_items;

        public T this[int index]
        {
            [MethodImpl(MethodImplOptions.NoInlining)]
            get
            {
                return this.m_items[index];
            }
            [MethodImpl(MethodImplOptions.NoInlining)]
            set
            {
                this.m_items[index] = value;
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MyArrayList arrayList = new MyArrayList(1);
            arrayList[0] = arrayList[0] ?? new object();

            MyList<object> list = new MyList<object>(1);
            list[0] = list[0] ?? new object();

            Console.WriteLine("Here comes the testing code.");

            var a = arrayList[0];
            var b = list[0];

            Console.ReadLine();
        }
    }
}

我们在这里构建了两个“容器”,一个是MyArrayList,另一个是MyList<T>,前者直接使用Object类型,而后者则是一个泛型类。我们对两个类的索引属性的get和set方法都加上了NoInlining标记,这样便可以避免这种简单的方法被JIT内联。而在Main方法中,前几行代码的作用都是构造两个类的对象,并确保索引的get和set方法都已经得到JIT。在打印出“Here comes the testing code.”之后,我们便对两个类的实例进行“下标访问”,并使控制台暂停。

当Release编译并运行之后,控制台会打印出“Here comes the testing code.”字样并停止。这时候我们便可以使用WinDbg来Attach to Process进行调试。老赵也是在这个时候制作了一个dump文件,您也可以Open Crash Dump命令打开这个文件。更多操作您可以参考互联网上的各篇文章,亦或是老赵之前写过的一篇《使用WinDbg获得托管方法的汇编代码》。

分析MyArrayList对象结构

假设您现在已经打开了WinDbg,并Attach to Process(或Open Crash Dump),而且加载了正确的sos.dll(可参考老赵之前给出的文章)。那么第一件事情,我们就要来分析一个MyArrayList对象的结构。

首先,我们还是在项目中查找MyArrayList类型的MT(Method Table,方法表)地址:

0:000> !name2ee *!TestConsole.MyArrayList
Module: 5bf71000 (mscorlib.dll)
--------------------------------------
Module: 00362354 (sortkey.nlp)
--------------------------------------
Module: 00362010 (sorttbls.nlp)
--------------------------------------
Module: 00362698 (prcp.nlp)
--------------------------------------
Module: 003629dc (mscorlib.resources.dll)
--------------------------------------
Module: 00342ff8 (TestConsole.exe)
Token: 0x02000002
MethodTable: 00343440
EEClass: 0034141c
Name: TestConsole.MyArrayList

我们得到了MyArrayList类型的MT地址之后,便可以在系统中寻找MyArrayList对象了:

0:000> !dumpheap -mt 00343440
 Address       MT     Size
0205be3c 00343440       12
total 1 objects
Statistics:
      MT    Count    TotalSize Class Name
00343440        1           12 TestConsole.MyArrayList
Total 1 objects

不出所料,当前程序中只有一个MyArrayList对象。我们继续追踪它的地址:

0:000> !do 0205be3c
Name: TestConsole.MyArrayList
MethodTable: 00343440
EEClass: 0034141c
Size: 12(0xc) bytes
 (E:\Users\Jeffrey Zhao\...\bin\Release\TestConsole.exe)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
5c1b41d0  4000001        4      System.Object[]  0 instance 0205be48 m_items

OK,到这里为止,我们得到一个结论。如果我们获得了一个MyArrayList对象的地址,那么偏移4个字节,便可以得到m_items字段,也就是存放元素的Object数组的地址。这点很关键,否则可能对于理解后面的汇编代码形成障碍。

如果您使用同样的方法来观察MyList<object>类型的话,您会发现其结果也完全相同:从对象地址开始偏移4个字节便是m_items字段,类型为Object数组。

分析数组对象的结构

接着我们来观察一下,一个数组对象在内存中的存放方式是什么样的。首先,我们打印出托管堆上的各种类型:

0:000> !dumpheap -stat
total 6922 objects
Statistics:
      MT    Count    TotalSize Class Name
5c1e3ed4        1           12 System.Text.DecoderExceptionFallback
5c1e3e90        1           12 System.Text.EncoderExceptionFallback
5c1e1ea4        1           12 System.RuntimeTypeHandle
5c1dfb28        1           12 System.__Filters
5c1dfad8        1           12 System.Reflection.Missing
5c1df9e0        1           12 System.RuntimeType+TypeCacheQueue
...
5c1e3150       48         8640 System.Collections.Hashtable+bucket[]
5c1e2d28      347         9716 System.Collections.ArrayList+ArrayListEnumeratorSimple
5c1b5ca4       46        11024 System.Reflection.CustomAttributeNamedParameter[]
5c1cc590      404        11312 System.Security.SecurityElement
5c1e2a30      578        13872 System.Collections.ArrayList
5c1b50e4      335        14740 System.Int16[]
5c1b41d0     1735        87172 System.Object[]
5c1e0a00      718       167212 System.String
5c1e3470       70       174272 System.Byte[]
Total 6922 objects

既然我们的代码中使用了Object数组,那么我们就把目标放在托管堆上的Object数组中。从上面的信息中我们已经获得了Object数组的MT地址,于是我们继续列举出托管堆上的此类对象:

0:000> !dumpheap -mt 5c1b41d0
 Address       MT     Size
01fd141c 5c1b41d0       80     
01fd1c84 5c1b41d0       16     
01fd1cc0 5c1b41d0       32     
...
0205baa4 5c1b41d0       20     
0205bc4c 5c1b41d0       20     
0205bc60 5c1b41d0       32     
0205bdc4 5c1b41d0       16     
0205be48 5c1b41d0       20     
0205be74 5c1b41d0       20     
0205c058 5c1b41d0       36     
02fd1010 5c1b41d0     4096     
02fd2020 5c1b41d0      528     
02fd2240 5c1b41d0     4096     
total 1735 objects
Statistics:
      MT    Count    TotalSize Class Name
5c1b41d0     1735        87172 System.Object[]
Total 1735 objects

我们随意抽取一个Object数组对象,查看它的内容:

0:000> !do 02fd2020
Name: System.Object[]
MethodTable: 5c1b41d0
EEClass: 5bf9da54
Size: 528(0x210) bytes
Array: Rank 1, Number of elements 128, Type CLASS
Element Type: System.Object
Fields:
None

WinDbg清楚明白地告诉我们,这个数组是1维的,共有128个元素。那么这个数组的长度信息是如何保存下来的呢(这个信息肯定是对象自带的,这个很容易理解吧)?我们直接查看这个数组对象地址上的数据吧:

0:000> dd 02fd2020
02fd2020  5c1b41d0 00000080 5c1e061c 01fd1198
02fd2030  0205bdf0 00000000 00000000 00000000
02fd2040  00000000 00000000 00000000 00000000
02fd2050  00000000 00000000 00000000 00000000
02fd2060  00000000 00000000 00000000 00000000
02fd2070  00000000 00000000 00000000 00000000
02fd2080  00000000 00000000 00000000 00000000
02fd2090  00000000 00000000 00000000 00000000

十六进制数00000080不就是十进制的128吗?没错,老赵对多个数组对象进行分析之后,发现数组对象存放的结构是从对象的地址开始:

  • 偏移0字节:存放了这个数组对象的MT地址,例如上面的5c1b41d0便是Object[]类型的MT地址。
  • 偏移4字节:存放了数组长度。
  • 偏移8字节:存放了数组元素类型的MT地址,例如上面的5c1e061c便是Object类型的MT地址,您可以使用!dumpmt -md 5c1e061c指令进行观察。
  • 偏移12字节:从这里开始,便存放了数组的每个元素了。也就是说,如果这是一个引用类型的数组,那么偏移12字节则存放了第1个(下标为0)元素的地址,偏移16字节则存放第2个元素的地址,以此类推。

实际上,这些是老赵在自己的试验过程中,从接下去会讲解的汇编代码出发猜测出来的结果,经过验证发现恰好符合。为了避免您走这些弯路,老赵就先将这一结果告诉大家了。

分析Main函数的汇编代码

接下去便要观察Main函数的汇编代码了。获取汇编代码的方法很简单,如果您对此还不太了解,老赵的文章《使用WinDbg获得托管方法的汇编代码》会给您一定帮助。Main函数的汇编代码如下:

0:000> !u 01d40070
Normal JIT generated code
TestConsole.Program.Main(System.String[])
Begin 01d40070, size e2
>>> 01d40070  push    ebp
01d40071  mov     ebp,esp
01d40073  push    edi
01d40074  push    esi
01d40075  push    ebx
...
01d4011d  mov     ecx,eax
// 打印字样“Here comes the testing code.”
01d4011f  mov     edx,dword ptr ds:[2FD2030h] ("Here comes the testing code.")
01d40125  mov     eax,dword ptr [ecx]
01d40127  call    dword ptr [eax+0D8h]
// 将MyArrayList对象的地址保存在ecx寄存器中
01d4012d  mov     ecx,esi
// 将edx寄存器清零,作为访问下面get_Item方法的参数
01d4012f  xor     edx,edx
// 获取地址0x343424中的数据(它是get_Item方法的访问入口),并调用
01d40131  call    dword ptr ds:[343424h] (...MyArrayList.get_Item(Int32), ...)
// 将MyList<object>对象的地址保存在ecx寄存器中
01d40137  mov     ecx,edi
// 将edx寄存器清零,作为访问下面get_Item方法的参数
01d40139  xor     edx,edx
// 获取地址0x343594中的数据(它是get_Item方法的访问入口),并调用
01d4013b  call    dword ptr ds:[343594h] (...MyList`1[...].get_Item(Int32), ...)
// 调用Console.ReadLine方法,请注意静态方法不需要把对象地址放到ecx寄存器中
01d40141  call    mscorlib_ni+0x6d1af4 (5c641af4) (System.Console.get_In(), ...)
01d40146  mov     ecx,eax
01d40148  mov     eax,dword ptr [ecx]
01d4014a  call    dword ptr [eax+64h]
01d4014d  pop     ebx
01d4014e  pop     esi
01d4014f  pop     edi
01d40150  pop     ebp
01d40151  ret

老赵为上面这段汇编代码添加了注释,我们主要从打印出“Here comes the testing code.”字样的代码开始进行分析。值得注意的是,在调用MyArrayList或MyList<object>的get_Item方法之前,都会把这个对象的地址放置到ecx寄存器中,然后把edx寄存器清零作为get_Item方法的参数。这样做的好处是加快访问对象及参数的速度,如果每次都需要从线程栈上读取这些(就像我们学习汇编时的那些经典案例),其性能肯定比不上读取寄存器。显然,调用Console.ReadLine静态方法是不需要对象地址的,因此无须对ecx寄存器有所操作。

分析get_Item方法的汇编代码

从Main函数的汇编代码中我们可以获得get_Item方法的入口。那么我们现在就来分析MyArrayList类型的get_Item方法,请注意,此时ecx寄存器保存的是MyArrayList对象的地址,edx保存了get_Item方法的参数:

0:000> dd 343424h
00343424  01d40168 71060003 20000006 01d40190
00343434  fffffff8 00000004 00000001 00080000
00343444  0000000c 00040011 00000004 5c1e061c
00343454  00342ff8 00343478 0034141c 00000000
00343464  00000000 5c136aa0 5c136ac0 5c136b30
00343474  5c1a7410 00000080 00000000 003434c0
00343484  10000002 90000000 003434c0 00000000
00343494  0034c05c 00020520 00000004 00000004
0:000> !u 01d40168
Normal JIT generated code
TestConsole.MyArrayList.get_Item(Int32)
Begin 01d40168, size 17
>>> 01d40168 55              push    ebp
01d40169 8bec            mov     ebp,esp
// 把MyArrayList对象的m_items字段地址(对象地址偏移4字节)保存至eax寄存器中
01d4016b 8b4104          mov     eax,dword ptr [ecx+4]
// 比较传入的参数(edx寄存器)与数组长度(eax寄存器为数组地址,再偏移4字节)的大小
01d4016e 3b5004          cmp     edx,dword ptr [eax+4]
// 如果参数超过数组长度,则跳转至错误处理代码
01d40171 7306            jae     01d40179
// 把需要的元素地址放置到eax寄存器中
// 从数组地址开始偏移12字节为第一个元素的地址,再偏移“下标 * 4”自然就是我们所需要的元素
01d40173 8b44900c        mov     eax,dword ptr [eax+edx*4+0Ch]
01d40177 5d              pop     ebp
// 返回
01d40178 c3              ret
// 如果参数大于数组长度,就会跳转到此
01d40179 e806c2a15c      call    mscorwks!JIT_RngChkFail (5e75c384)
01d4017e cc              int     3

如果要理解上面的代码,可能需要您再去回味文章上半段的分析。尤其是几个偏移量:

  • MyArrayList对象偏移4字节则为m_items字段地址
  • 数组地址偏移4字节则为其长度
  • 数组地址偏移12字节为其第一个元素的地址

然后,再结合ecx(MyArrayList对象地址),edx(参数)以及eax(保存了方法返回值)几个寄存器的作用,相信理解上面这段代码也并非难事。

MyArrayList的代码分析完了,那么MyList<object>的汇编代码又是如何?

0:000> dd 343594h
00343594  01d401b8 01d401e0 00010001 003435a4
003435a4  5c1e0670 00000000 00000000 00000080
003435b4  00000000 fffffff8 00000004 00000001
003435c4  00080010 0000000c 00040011 00000004
003435d4  5c1e061c 00342ff8 00343610 0034355a
003435e4  00343600 00000000 5c136aa0 5c136ac0
003435f4  5c136b30 5c1a7410 00010001 00343604
00343604  5c1e061c 00000000 00000000 00000080
0:000> !u 01d401b8
Normal JIT generated code
TestConsole.MyList`1[[System.__Canon, mscorlib]].get_Item(Int32)
Begin 01d401b8, size 17
>>> 01d401b8 55              push    ebp
01d401b9 8bec            mov     ebp,esp
01d401bb 8b4104          mov     eax,dword ptr [ecx+4]
01d401be 3b5004          cmp     edx,dword ptr [eax+4]
01d401c1 7306            jae     01d401c9
01d401c3 8b44900c        mov     eax,dword ptr [eax+edx*4+0Ch]
01d401c7 5d              pop     ebp
01d401c8 c3              ret
01d401c9 e8b6c1a15c      call    mscorwks!JIT_RngChkFail (5e75c384)
01d401ce cc              int     3

是否发现,两者的代码除了几个地址之外可以说完全一样?

总结

还需要多说什么吗?我们通过比较汇编代码,已经证明了MyArrayList和MyList<Object>在执行时所经过的指令几乎完全相同。到了这个地步,您是否还认为泛型会影响程序性能?

最后继续强调一句:老赵并不喜欢IL,更不喜欢汇编。除非万不得已,老赵是不会往这方面去思考问题的。我们有太多东西可学,如果不是目标明确,老赵建议您还是不要投身于IL或汇编这类东西为好。

最后附上dump文件

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

80 条回复

  1. 老赵
    admin
    链接

    老赵 2009-05-30 05:22:00

    dump文件等老赵睡一觉再上传,困了……

  2. 斯克迪亚
    *.*.*.*
    链接

    斯克迪亚 2009-05-30 06:22:00

    首次排在前面,结果沙发被你自用了~
    老赵果然不愧“严谨”之赞啊。(不是说沙发问题) PS:为什么你也这么早还没睡?难道你作息时间也和地球不同步?还是说你在地球那头~

  3. 菌哥
    *.*.*.*
    链接

    菌哥 2009-05-30 06:55:00

    老赵精通的东西可真多,不得不佩服啊

  4. 在别处
    *.*.*.*
    链接

    在别处 2009-05-30 07:41:00

    学习的榜样啊

  5. Terry Sun[未登录][未注册用户…
    *.*.*.*
    链接

    Terry Sun[未登录][未注册用户] 2009-05-30 08:06:00

    这种钻研的精神太值得我们学习了,敬佩老赵

  6. xuefly
    *.*.*.*
    链接

    xuefly 2009-05-30 08:10:00

    老大,看不懂啊!我一点点都不懂汇编。

  7. zeus2
    *.*.*.*
    链接

    zeus2 2009-05-30 08:13:00

    老赵精辟,.net的范型确实非常强大

  8. Nick Wang (懒人王)
    *.*.*.*
    链接

    Nick Wang (懒人王) 2009-05-30 08:15:00

    老赵的刨根问题求真理的态度令人佩服,但是我非常不理解为什么总有人考虑性能问题.性能对你真得那么重要么?开发效率over性能的现实已经不是一年两年了,大部分领域性能问题都不在语言层面,就算是ArrayList比List<T>快个10%,对整个项目的执行又有多大的影响呢?

    补充一下, List<T>在T为值类型时,做copy可能会比较慢,因为值类型需要copy值而不是引用,因此当这个值类型比较大时,会比copy引用来得慢.同样情况下ArrayList由于已经将值类型装箱了,因此copy的是装箱后的引用.记得范性刚出来的时候就已经有人测试过了.

  9. Nick Wang (懒人王)
    *.*.*.*
    链接

    Nick Wang (懒人王) 2009-05-30 08:17:00

    接上面,如果了解了范型的原理和ArrayList的原理,不需要比较也能推导出范性不会比ArrayList慢,反之,就算比较过了,还是不知道为什么.

  10. 周强
    *.*.*.*
    链接

    周强 2009-05-30 08:46:00

    厉害。老赵的技术我没有学到(不是因为老赵的文章不好,而是我的方向暂与文章不同),其治学态度实在令人钦佩啊,这点一定得学。

  11. 横刀天笑
    *.*.*.*
    链接

    横刀天笑 2009-05-30 09:01:00

    佩服老赵的钻研精神啊

    PS:“......都会把这个该对象的地址放置到ecx寄存器中,然后把edx寄存器清零作为get_Item方法的参数......”,也许不仅仅是加快访问而已,这种方法访问的方式是__fastcall调用的约定,说明了.NET中方法的调用使用的是__fastcall。

  12. 二手的程序员
    *.*.*.*
    链接

    二手的程序员 2009-05-30 09:30:00

    Mark

  13. Grove.Chu
    *.*.*.*
    链接

    Grove.Chu 2009-05-30 09:30:00

    Up.真的是佩服老赵!

  14. Teddy's Knowledge Bas…
    *.*.*.*
    链接

    Teddy's Knowledge Base 2009-05-30 09:39:00

    老赵分析得够深入,不过我想大多数朋友看汇编一定有点吃力的,所以估计只会关注你说的结果而没有能力完全理解你分析的过程.:)

    不过你建议"不要投身于IL或汇编这类东西",我只赞同后半句,研究汇编的确成本过高,但是,IL还是相对抽象的,鉴于所有的.net语言都会先被编译成IL,其实除非涉及到不同的系统调用,绝大多数情况下分析IL足够用来分析大多数代码的性能问题了.况且,MSDN文档里就包含了很详细的IL的文档了,至少也不需要另一本汇编的参考书在旁边查阅.并不是很大的负担.

    个人还是提倡经常reflect代码的IL的,语言越抽象越高级,越容易让我们忽略这些细微的差异,我们当然并不是要提倡所有类型的应用都要把性能做到极致,或者说,为了性能忽略效率,但是,很多时候,很多好的coding习惯并不会必然导致效率降低,却能够保证程序性能相对更好,bug率更低,那么又何乐而不为呢?

    对于有朋友说"真正的性能问题都不在语言层面",这个话很多时候的确不能说错,尤其是对大多数所谓"企业应用".如果你一辈子就准备之开发这类"企业应用"的话,的确没必要太关注这点性能差异.请大家注意分辨,这所谓"企业应用"和另一个词"企业级应用"的区别.

  15. 包建强
    *.*.*.*
    链接

    包建强 2009-05-30 09:47:00

    如果不熟悉IL,又怎么能自己动手分析性能呢?
    你这篇文章不就证明了学习一点IL的重要性了么?

  16. 水水水水水水水[未注册用户]
    *.*.*.*
    链接

    水水水水水水水[未注册用户] 2009-05-30 09:58:00

    我对老赵真是佩服,不是因为他的文章,而是大家看看他发文章的时间,都早上5点了,还在研究,这么好的体力精力+学习的精神 ,这才是成为高手的关键,成功来自于努力,一点不假,再聪明的人一天学习1个小时也抵不上普通人一天学4个小时,佩服,关键老赵也不是个傻子,所以大家看老赵的技术越来越牛,越来越精,牛!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  17. 幸存者[未注册用户]
    *.*.*.*
    链接

    幸存者[未注册用户] 2009-05-30 10:01:00

    看来汇编也并没有解释为什么MyList<object>和MyArrayList进行foreach遍历的时候会有性能差异。

  18. egmkang
    *.*.*.*
    链接

    egmkang 2009-05-30 10:05:00

    泛型效率还是很高的.
    大二的时候写排序,对int的排序和对T(T为int)的排序时间是一样的

  19. MicroCoder
    *.*.*.*
    链接

    MicroCoder 2009-05-30 10:12:00

    --引用--------------------------------------------------
    水水水水水水水: 我对老赵真是佩服,不是因为他的文章,而是大家看看他发文章的时间,都早上5点了,还在研究,这么好的体力精力+学习的精神 ,这才是成为高手的关键,成功来自于努力,一点不假,再聪明的人一天学习1个小时也抵不上普通人一天学4个小时,佩服,关键老赵也不是个傻子,所以大家看老赵的技术越来越牛,越来越精,牛!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    --------------------------------------------------------
    这位仁兄好激动呀 ⊙﹏⊙b汗

  20. 紫色永恒
    *.*.*.*
    链接

    紫色永恒 2009-05-30 10:22:00

    追根究底 值得学习 程序员的榜样 和谐社会的栋梁。。。

  21. jowo
    *.*.*.*
    链接

    jowo 2009-05-30 10:42:00

    专研精神的确可以佳,注意身体
    分析的不错,

  22. 生鱼片
    *.*.*.*
    链接

    生鱼片 2009-05-30 10:46:00

    大家还在争论对于.net程序员学习IL的必要性,其实我还是比较同意老赵的观点,一个相对性,值得不值得,以及该学到什么程度。

  23. 老赵
    admin
    链接

    老赵 2009-05-30 10:55:00

    --引用--------------------------------------------------
    幸存者: 看来汇编也并没有解释为什么MyList<object>和MyArrayList进行foreach遍历的时候会有性能差异。
    --------------------------------------------------------
    不是不能,不是解释不了,而是不需要用汇编来观察这个问题,我也没有设法去观察这一点。
    要知道为什么foreach会有性能差距,用Reflector观察高级语言代码就可以了。

  24. 老赵
    admin
    链接

    老赵 2009-05-30 10:59:00

    --引用--------------------------------------------------
    包建强: 如果不熟悉IL,又怎么能自己动手分析性能呢?
    你这篇文章不就证明了学习一点IL的重要性了么?
    --------------------------------------------------------
    你又搞浑了,我这篇文章一点IL都没有涉及到,而且从IL上根本看不出文章中分析的东西。
    而且我在文章里说了,我并不喜欢用这种方式来说明问题,我认为前一篇文章的“性能测试”是最靠谱的,实际出发,很说明问题。
    是因为有朋友还是比较坚持“泛型性能略差”的“普遍说法”,我理解不了,因此动用汇编了。
    还是汇编,不是IL。IL和汇编是对应不起来的,看IL看不到很多东西。
    IL我平时几乎不碰,因为99%的问题是可以从反编译成C#代码看出问题来的,而再直接一些,似乎就要看汇编。
    因为IL往往和高级代码对应,因此IL表现的东西,C#也可以表现。汇编表现出的细节,IL和高级代码是做不到的。所以我觉得IL倒的确不值得投入。
    当然我还是不觉得研究问题要搞到这种地步,到这种时候投入单位时间,产出就少很多了……

  25. 老赵
    admin
    链接

    老赵 2009-05-30 11:00:00

    --引用--------------------------------------------------
    横刀天笑: 佩服老赵的钻研精神啊

    PS:“......都会把这个该对象的地址放置到ecx寄存器中,然后把edx寄存器清零作为get_Item方法的参数......”,也许不仅仅是加快访问而已,这种方法访问的方式是__fastcall调用的约定,说明了.NET中方法的调用使用的是__fastcall。
    --------------------------------------------------------
    嗯嗯,我再去看看。
    不过其实也肯定是在例如参数少的时候才__fastcall起来。

  26. 温景良(Jason)
    *.*.*.*
    链接

    温景良(Jason) 2009-05-30 11:04:00

    佩服老赵的钻研精神啊,谢谢老赵

  27. 老赵
    admin
    链接

    老赵 2009-05-30 11:07:00

    --引用--------------------------------------------------
    Teddy's Knowledge Base: 个人还是提倡经常reflect代码的IL的,语言越抽象越高级,越容易让我们忽略这些细微的差异,我们当然并不是要提倡所有类型的应用都要把性能做到极致,或者说,为了性能忽略效率,但是,很多时候,很多好的coding习惯并不会必然导致效率降低,却能够保证程序性能相对更好,bug率更低,那么又何乐而不为呢?

    对于有朋友说c真正的性能问题都不在语言层面",这个话很多时候的确不能说错,尤其是对大多数所谓"企业应用".如果你一辈子就准备之开发这类"企业应用"的话,的确没必要太关注这点性能差异.请大家注意分辨,这所谓"企业应用"和另一个词"企业级应用"的区别.
    --------------------------------------------------------
    我建议要多用Reflector阅读框架内部代码,但是我不建议阅读汇编,我平时解决问题,99%以上还是用Reflector反编译成C#代码看得,几乎从来不看IL,如果真要解决某些问题,就直接用WinDbg看汇编了。我觉得IL的投入产出比比较少。
    至于性能,其实要在这个性能上做追究,并不是说“企业级”是需要的。“企业级”往往是指规模大,整合多,吞吐量大吧,但是这并非是计算密集型应用。其实也只有计算密集型应用,需要大量纯粹“计算”,那么需要追究极致性能。
    否则的话,只要没把程序写错,犯大错误,就OK了。否则一个网络传输,一个数据库查询,耗费的时间远远超过1亿次MoveNext方法调用的消耗,呵呵。

  28. QQ3666****[未注册用户]
    *.*.*.*
    链接

    QQ3666****[未注册用户] 2009-05-30 11:07:00

    首先,比来比去,比Object有啥意思?要比就比带装箱拆箱的值类型!
    其次,这篇文章和老赵以前的文章不一样,这篇以步骤为主,以前的文章以目标为主,读者在阅读的时候因为没有目标,所以容易茫然,建议加上以下字眼:“下面为了证明×××和YYY的不同,我们就要证明mmm和nnn有什么不同,现在……”这样大家的思路会比较集中,否则只是看不断地dump等……
    最后,这跟研究IL代码没有啥关系,所以楼上有人说这篇文章证明了学习IL有必要的言论并不成立,倒是研究一下汇编有点意思。

  29. 老赵
    admin
    链接

    老赵 2009-05-30 11:08:00

    --引用--------------------------------------------------
    QQ3666****: 首先,比来比去,比Object有啥意思?要比就比带装箱拆箱的值类型!
    --------------------------------------------------------
    这个大家有共识,就不比较了。而且这点通过写C#代码运行就能看出明显差距。

  30. 冰の酷龙
    *.*.*.*
    链接

    冰の酷龙 2009-05-30 11:11:00

    老赵喜欢较真,可能有些人不喜欢,但是我们确实从中受益。

  31. QQ3666****[未注册用户]
    *.*.*.*
    链接

    QQ3666****[未注册用户] 2009-05-30 11:11:00

    既然大家都来写.NET的代码了也就是认同了托管代码略微的性能差距,那又何必在乎List<>和ArrayList之间的那一点不足挂齿的性能差距呢?把自己的几句该死的SQL写好,所有性能差距都被你弥补回来了!
    一定要记住一句话,写.NET代码,怎么方便怎么来

  32. 老赵
    admin
    链接

    老赵 2009-05-30 11:13:00

    --引用--------------------------------------------------
    QQ3666****: 一定要记住一句话,写.NET代码,怎么方便怎么来
    --------------------------------------------------------
    只要不犯错,怎么方便怎么来,似乎也没有问题。

  33. MSN2009V2.20038[未注册用户…
    *.*.*.*
    链接

    MSN2009V2.20038[未注册用户] 2009-05-30 11:15:00

    性能,第一,就是IO,把IO解决好了,性能就上台阶了,第二,就是循环,把循环减少了,性能就回来了,第三,把第一和第二结合在一起的做法一旦出现性能就大大瓶颈了!除此之外,请注意,依靠个人的力量改变性能已经非常有限了,一个大型的程序从2s向1s的进步可能给你带来的难度是可观的,但是你的收益却太小,投入硬件吧,你的性能从1s到1ms都是很容易做到的

  34. MSN2009V2.20038[未注册用户…
    *.*.*.*
    链接

    MSN2009V2.20038[未注册用户] 2009-05-30 11:18:00

    养成良好的习惯比写完代码再调优要容易地多,可以看看代码大全2,可能没有涉及到C#,但很多东西是很有启发的,如果买不起书,那就多关注老赵吧,特别是程序员养成的一些言论会让你很受用

  35. Q.Lee.lulu
    *.*.*.*
    链接

    Q.Lee.lulu 2009-05-30 11:24:00

    佩服佩服,睡那么迟起这么早,注意身体。。。

  36. 胆子也太大了![未注册用户]
    *.*.*.*
    链接

    胆子也太大了![未注册用户] 2009-05-30 11:26:00

    老赵怎么会红成这样,大半夜的顶到了35楼~

  37. xiongli lixiong[未注册用户…
    *.*.*.*
    链接

    xiongli lixiong[未注册用户] 2009-05-30 11:42:00

    我记得泛型就是在后期把类型T展开具现后当成普通类型编译的啊
    如果编译出来不一样那反而还不好解释了。。。

  38. 老赵
    admin
    链接

    老赵 2009-05-30 11:51:00

    文末已经附上dump文件,大家可以下载着玩。

  39. 走不寻常的路[未注册用户]
    *.*.*.*
    链接

    走不寻常的路[未注册用户] 2009-05-30 12:21:00

    老赵,你的学术精神真的是令我感动啊.
    敢问你每天的作息时间是怎么安排的,什么时候也写写这方面的东东。毕竟,大家要想向你这样的高手靠近,对于时间的安排也是比较关心的。
    你这都早上5点了 还在钻研程序。你身边的朋友都支持你麽, 另送一句"注意身体"
    呵呵.

  40. 老赵
    admin
    链接

    老赵 2009-05-30 13:35:00

    @走不寻常的路
    还不是因为放假休息么,可以睡懒觉么……平时哪会到5点啊。

  41. 量变才有质变[未注册用户]
    *.*.*.*
    链接

    量变才有质变[未注册用户] 2009-05-30 14:21:00

    性能问题来自哪里?来自于“量”,解决了量的问题,基本上就解决了性能问题。
    什么是“量”?
    通俗地说就是一次处理大量的数据,导致了性能问题,比如一次读取100万条数据,但是无数的程序员,从来不去考虑为什么要一次读取100万条这个问题,而更多的去想怎么在最短时间内读取100万条,死钻牛角尖。

    最讨厌那些动不动就叫什么泛型,Linq等等性能差的人。骨子里头其实就是对新技术的恐惧和懒惰,叫性能差只不过是为自已的白痴找借口,装B而已,潜意识里害怕,如果泛型和Linq等技术的大量运用,还需要多少做应用的程序员呢?

    国内多数程序员只不过在做做应用系统,不写框架及底层算法,不开发更高速的CPU,甚至都穷得没钱多加点内存,或升级服务器,还好意思叫唤。

    另外再了BS一下,很多公司一招人,动不动就出什么不调系统函数,写个程序排个字符串,或写个Sql语句之类的题,真白痴。

  42. 老赵
    admin
    链接

    老赵 2009-05-30 14:28:00

    --引用--------------------------------------------------
    量变才有质变: 另外再了BS一下,很多公司一招人,动不动就出什么不调系统函数,写个程序排个字符串,或写个Sql语句之类的题,真白痴。
    --------------------------------------------------------
    这有什么问题,考察编程能力啊,程序员基本技能。

  43. clayman
    *.*.*.*
    链接

    clayman 2009-05-30 16:48:00

    --引用--------------------------------------------------
    幸存者: 看来汇编也并没有解释为什么MyList&lt;object&gt;和MyArrayList进行foreach遍历的时候会有性能差异。
    --------------------------------------------------------
    老赵的这个例子并不好,只得出了两者相同的结论,却没有展示泛型的性能提升究竟在什么地方。没有讨论box,unbox带来的影响,没有讨论foreach会对泛型和非泛型产生不同的代码,对enumerator的优化,以及泛型本身的空间代价等等。这些才是泛型与非泛型性能差别的根源。我在另外一篇文章里已经说过,MS的技术大牛早就对这个问题进行过全面分析,可惜没有多少人看(sigh)http://blogs.msdn.com/ricom/archive/2005/08/26/performance-quiz-7-generics-improvements-and-costs-solution.aspx

  44. 老赵
    admin
    链接

    老赵 2009-05-30 16:55:00

    @clayman
    唉,你没有搞清楚现在到底在讨论什么话题,为什么就来指出别人没有看你给的文章呢?
    现状是:对于某些情况泛型带来的性能提高(如box,unbox)大家都承认,没有质疑,但是对于最“纯粹”的泛型容器,是否会降低直接的Object容器性能有不同意见。
    因此我的文章就是针对这个问题写的,比的就是Xxx<Object>和ObjectXxx的性能。我为什么要花额外的工作来“展示”大家都已经承认并理解的内容?
    你给的文章也不是新的了,在前一篇文章的评论中也有朋友已经指出是实现上的问题,也给出了代码作为证据,你给的文章只是单独开篇论述地更详细了一些而已。
    在这个问题上,国内技术人员哪里比“微软大牛”要差多少了。真要说起来,我还嫌那篇文章没有说明我这篇文章所证明的问题呢。

    在指责别人没有尊重自己劳动成果之前,自己是否应该先遵守别人的劳动成果,先看清楚再回复呢?

  45. clayman
    *.*.*.*
    链接

    clayman 2009-05-30 18:24:00

    @Jeffrey Zhao
    我只是觉得文章的题目叫“比较泛型汇编”之类的更合适....但既然是“探究泛型性能”,就应该给出更全面的分析,不管用汇编还是IL,展示两者间的不同点应该更有用吧,基于前几篇文章的讨论,你的读者也更想知道性能差异在哪里,而不是简单的得出2者汇编一样的结论。对特定于*集合*的例子,连for都没有讨论到,感觉有些遗憾。我并没有否认你的劳动成果,只是觉得这个例子不太好而已。另外虽然大家认为泛型性能的提升很大程度来于避免了box,unbox,但实际上是否box,unbox在这里并不重要。既然都到汇编级别了,为什么不再展开一点呢?

    看来我说的就是lz认为的"不必要的争论" 吧,呵呵

  46. 老赵
    admin
    链接

    老赵 2009-05-30 18:31:00

    @clayman
    看来你真的没有理解文章的意思……我说的不必要的争论是“学习IL和汇编是否有必要”

    我要证明的是“泛型是否会对性能产生负面”,证明“两者相同”就已经达到目的了。而box/unbox的性能问题大家都没有意见,为什么我还要花甚至更多的时间去探究这些呢?
    而且,即使我说明了“泛型在某些情况下提高了性能”,就能证明“泛型在某些特别情况下*也不会*影响性能”吗?完整是好事,但是因此把讨论中心给忽悠了,就是坏事了。
    for的问题有必要讨论吗?for其实不是问题关键,问题关键在于for是使用下标进行访问的,我这篇文章就证明了从根本上证明了“下标访问”没有性能差别。
    如果你是说for和foreach的差别,那么这就和“泛型”无关了,这是另一个话题了。要比较的是同一个容器的for和foreach的差别,而不是两个不同容器的下标访问的差别。

    至于unbox/box的性能差别,通过写一点性能比较代码就能看出明显差距,为什么要用汇编来解释?
    这里是因为通过写性能比较代码无法得到令人信服的答案,因此我才使用汇编来从根本上说明问题。
    我的态度,以及写这篇文章原因,其实都从文章里说明了:如果能用高级代码说明问题,那么根本不会使用汇编。

  47. 老赵
    admin
    链接

    老赵 2009-05-30 18:32:00

    @clayman
    标题中所说的《泛型的性能问题》,根据这几天讨论的内容,很明显就是指“泛型会不会对性能产生负面影响”。
    你不看其他文章,独立讨论这篇,所以会有这这样的看法。
    好吧好吧,咬文嚼字就到这里了,接下来还是讨论技术问题吧。

  48. clayman
    *.*.*.*
    链接

    clayman 2009-05-30 19:24:00

    @Jeffrey Zhao
    如果我没记错的话,是你在前一篇文章里进行了foreach的比较而没有给出快在哪里的原因吧。我再把那个链接帖出来,只是显然这里很多人都没有看过,否则也就不会有那么多讨论,原文作者没用汇编就点了问题的本质。lz有点反映过激了吧??算了,不说了,讨论技术.....

  49. 老赵
    admin
    链接

    老赵 2009-05-30 19:42:00

    --引用--------------------------------------------------
    clayman:如果我没记错的话,是你在前一篇文章里进行了foreach的比较而没有给出快在哪里的原因吧。我再把那个链接帖出来,只是显然这里很多人都没有看过,否则也就不会有那么多讨论,原文作者没用汇编就点了问题的本质。lz有点反映过激了吧??算了,不说了,讨论技术.....
    --------------------------------------------------------
    你是不是根本没有看我的文章和他的文章?还是你的逻辑就是如此糟糕?

    他的文章讨论:为什么ArrayList和List<int>的foreach会有性能差距。
    我讨论的是:ArrayList和List<Object>下标访问操作有没有性能区别。
    我之前的文章的确没有说清“为什么两者的foreach会有差距”,但是为什么我接下来写的文章就必须围绕这个来讨论?我就不能谈一下那篇文章中提到的其他问题?
    两篇文章根本不是在说明一个问题。他没有企图证明我的问题,我也没有企图说明他的东西,这又谈何有没有用汇编,有没有问题的本质,两种论证手段又有什么可以比较的?
    如果我想说明他的问题,那么肯定没法用汇编,因为这时候产生的汇编代码过于复杂,这个你怎么可能不知道?
    你的文章贴到上一篇文章里十分合适,我非常欢迎。但是你贴到这里,还想以此讨论这篇文章的内容,合适么?
    而且你如果看过你给的文章的话,就会发现他讨论的性能差距就根本不是泛型造成的。他都写清了:他的问题里ArrayList的boxing/unboxing只消耗了一点点性能。性能关键在于ArrayList的实现方式。
    他讨论的和泛型无关,我讨论就是泛型,这还不够说明问题么?

    所以我从一开始就在说,你根本没有搞清楚现在在谈什么话题

  50. 装配脑袋
    *.*.*.*
    链接

    装配脑袋 2009-05-30 20:46:00

    老赵的思维能力比别人多一步。所以老赵认为不用解释的东西,别人需要解释;老赵觉得已经是严格的东西别人不这么认为;老赵想让别人知道的东西,别人无法知道。所以才有这么多不必要的争论,呵呵

  51. 老赵
    admin
    链接

    老赵 2009-05-30 20:49:00

    @装配脑袋
    脑袋太抬举我了,其实我并不聪明,只是我对于一个问题会仔细观察,努力分析,说话之前再设法驳倒自己,尽可能的严密而已。
    所以说我思维敏捷,“领先一步”,我不觉得,但是我认为我的做事方式是很正确的。

  52. 十二月的雪
    *.*.*.*
    链接

    十二月的雪 2009-05-30 21:58:00

    mark 汇编不是很懂.先去看看其他的再说.

  53. 蛙蛙池塘
    *.*.*.*
    链接

    蛙蛙池塘 2009-05-30 23:20:00

    放假都不好好休息,呵呵。
    u和!u不一样吧应该。
    我觉得讨论挺有意思的,程序员就应该多讨论,多沟通,多交流,闷头写代码也不太好。

  54. 老赵
    admin
    链接

    老赵 2009-05-30 23:21:00

    @蛙蛙池塘
    放假才有时间写东西嘛。
    讨论是值得提倡的,例如《泛型真的会降低性能吗?》后面的评论,我又了解到了新东西。
    这篇就有问题了……讨论一定要“专业”,逻辑清晰,中心明确,不专业的讨论只是在浪费时间。

  55. Sumtec
    *.*.*.*
    链接

    Sumtec 2009-05-30 23:26:00

    Great! 就如楼主说的,一般情况下,对初学者还是不要这般研究,否则容易走火入魔。当然了,有一定基础了,了解一下底层的问题也未尝不可。
    不过我说老赵,这个性能问题用这把牛刀来分析,有点较真了。其实公道自在人心,有的时候,不信者恒不信,总能找出文字漏洞来的。

  56. 老赵
    admin
    链接

    老赵 2009-05-30 23:28:00

    @Sumtec
    不过我觉得就这个下标访问性能问题来说,现在已经说到底了阿。如果要“反对”的话,又能有什么理由呢?我很好奇。

  57. 坚持信念[未注册用户]
    *.*.*.*
    链接

    坚持信念[未注册用户] 2009-05-31 08:22:00

    感觉破解.net软件的时候,呵呵。IL要很熟悉。
    所以,不赞同你对学IL用处不大的结论。

    另:我并不是说破解软件,只是说明IL并不是无用。

  58. 坚持信念[未注册用户]
    *.*.*.*
    链接

    坚持信念[未注册用户] 2009-05-31 08:25:00

    如果不是目标明确,老赵建议您还是不要投身于IL或汇编这类东西为好。

    ----------------
    我错了,我没看到“如果不是目标明确”几个字。

  59. Sumtec
    *.*.*.*
    链接

    Sumtec 2009-05-31 10:33:00

    @Jeffrey Zhao
    嘿嘿,你太善良了。比如可以这么叫板:
    你考虑过Jit代码的性能损失了吗?如果我有一个集合,它就用一两次呢?毕竟用一个N大的循环来测,在现实中能有这么多的调用吗?
    或者,会给你绕到另一个问题上去,这个不举例了,很容易找到。这种情况基本上到最后还是你讲你的他讲他的……

    话说回来了,我一直认为这里面的代码输出没有什么理由有太大的差异(抛开boxing/unboxing),所以不知道为什么会有人建议因为性能缘故不要用泛型。其实是否用泛型不应该考虑性能,而是代码的质量问题。

  60. 老赵
    admin
    链接

    老赵 2009-05-31 10:39:00

    @Sumtec
    其实只要“专业”,叫板并没有关系。比如你的问题就可以回答:既然只用1、2次,那么就算多花个几毫秒也没有关系,反正是一次性投入几毫秒。
    关键就在于,不专业,例如会绕,例如只抛结论不给原因,这就没有意思了。
    如果专业,啥子都可以说得有道理。

  61. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-05-31 10:58:00

    如果专业,啥子都可以说得有道理。

    赞一个


    吐槽:专业的健身不推荐熬夜

  62. 看破Heap[未注册用户]
    *.*.*.*
    链接

    看破Heap[未注册用户] 2009-05-31 11:14:00

    泛型 本身就是为 优化性能而生的.
    扯那么多.

  63. 老赵
    admin
    链接

    老赵 2009-05-31 11:19:00

    --引用--------------------------------------------------
    看破Heap: 泛型 本身就是为 优化性能而生的.
    扯那么多.
    --------------------------------------------------------
    从逻辑上说,可以在90%的情况下提高性能,并不代表在特殊的10%的情况不会造成性能损失。

  64. DiggingDeeply
    *.*.*.*
    链接

    DiggingDeeply 2009-05-31 18:05:00

    性能是我平时听到的最大的话题,给出你的根据,让大家信服,我想这才能服众,而不是“觉得。。。”,“认为。。。”,事实胜于雄辩。

    PS:优化性能最好是在汇编级别。
    老赵,你选择了优化代码选项了吗?可能代码会不一样一些。

  65. 老赵
    admin
    链接

    老赵 2009-05-31 18:08:00

    @DiggingDeeply
    当然优化了,这个我怎么可能忘记。
    其实从结果上看,这个汇编代码的逻辑也已经是最优的了
    还可以和我上一篇文章进行比较,会发现少了许多额外的代码。

  66. 老赵
    admin
    链接

    老赵 2009-05-31 18:10:00

    @DiggingDeeply
    为什么优化性能最好是在汇编级别?
    需要到汇编级别去做性能调优的情况有多少呢?

  67. DiggingDeeply
    *.*.*.*
    链接

    DiggingDeeply 2009-05-31 18:47:00

    @@Jeffrey Zhao
    因为你不论什么语言的程序,最终都是机器码,而汇编就是机器码的符号。任何语言都是经若干编译器经过若干个翻译或是解释的过程转换为机器码来执行的。
    第2个问题,我在.Net上没有到汇编级别。因为硬件条件很充裕,但是在“贫瘠”的机器或是设备上,就可能需要了。(怕你问我为什么需要,我没做过相关的嵌入式的程序)

  68. 老赵
    admin
    链接

    老赵 2009-05-31 18:49:00

    @DiggingDeeply
    你说的这些我自然明白。
    我的意思是,需要汇编级别的优化实在太少了。
    几乎所有问题都是在高级语言级别进行优化,例如提高算法效率,减少资源竞争等等,这是大头。
    只有到那种地方已经提高的再也没法提高了,再去汇编级别优化。
    用C语言也就是在几个critical的地方直接用汇编写,不是吗?

  69. DiggingDeeply
    *.*.*.*
    链接

    DiggingDeeply 2009-05-31 18:54:00

    引用--------------------------------------------------
    Jeffrey Zhao: @DiggingDeeply

    你说的这些我自然明白。

    我的意思是,需要汇编级别的优化实在太少了。

    几乎所有问题都是在高级语言级别进行优化,例如提高算法效率,减少资源竞争等等,这是大头。

    只有到那种地方已经提高的再也没法提高了,再去汇编级别优化。

    用C语言也就是在几个critical的地方直接用汇编写,不是吗?
    --------------------------------------------------------
    我同意你的观点,我也没提倡人人都去搞汇编。我的意思和你“只有到那种地方已经提高的再也没法提高了,再去汇编级别优化。”一样,我的表达有问题。
    我很佩服你的文笔,比我强N倍。

  70. 路过的人[未注册用户]
    *.*.*.*
    链接

    路过的人[未注册用户] 2009-05-31 23:20:00

    老赵极可能(说得太肯定你会遭老赵抓字眼)是个野猫子。
    证据:发帖、回帖时间都几乎(不是说所有,请看清我说的话)很晚。

    上述会否挑出毛病或语句来呢?
    会——第一、与文章无关,跑题,老赵很可能(这个我也有所保留)拿这个抨击他人。

  71. 路过的人[未注册用户]
    *.*.*.*
    链接

    路过的人[未注册用户] 2009-05-31 23:22:00

    第二、老赵不是猫(在老赵地盘,要多读几年书,不能想说啥说啥,稍不留神你的语文水平就要汗颜几下)。
    第三、这回答很无聊。

  72. 路过的人[未注册用户]
    *.*.*.*
    链接

    路过的人[未注册用户] 2009-05-31 23:31:00

    第四、老赵几乎都会不厌其烦地指出你可能是错的地方,如果你是用拼音输入法的话,有的时候你可能还应该注意一下,别敲了同音字。老赵很可能给你标成红色。

  73. 老赵
    admin
    链接

    老赵 2009-05-31 23:41:00

    @路过的人
    哈哈,说的真好,不过其实我只是对技术态度严格,要求专业而已,你见我啥时管过灌水问题啦?
    再说我都是对事不对人,只抨击事情不抨击人,嘿嘿。
    还有建议兄台注册以后评论,这样有补充的话还可以修改一下,现在就要发多次,我看了都觉得不方便哪。

  74. 路过的人[未注册用户]
    *.*.*.*
    链接

    路过的人[未注册用户] 2009-06-01 00:40:00

    也没讽刺的意思,读了你很久的文章,也就开个玩笑。真要计较我可敌不过“皂粉”,哈哈。

    恩…认真说句,我后来看老赵文章,依然会谨慎测试学习,细作推敲。但是不看评论,也不敢参评。因为有很多这些:

    “…请看清楚我的 …(标题、正文引用)…”,
    “…没有看文章内容,不知道文章讨论中心就来评头论足…”,
    “…请看清楚…(引用某某某地方怎么怎么写的)…”,

    不是一两次,偶有为之,以为提醒(对于被批者,我猜这态度也不好受,换位思考,严重地说是一种侮辱),
    再加上对头又一贴来轰炸,
    每每老赵发帖,有人拿糖果鲜花来,有人扛枪抬炮来。
    不过想来伤感,回个帖就要“字字珠玑”吗?
    你看得出我回帖的中心思想吗?

    恩恩,墨水不多,也无法书写几篇造福他人,注册就免了,从老赵身上学到很多,倒是没回过老赵的贴,当真是取泉忘井;
    仅此数贴,不敢再搅浑了。

  75. 老赵
    admin
    链接

    老赵 2009-06-01 01:07:00

    @路过的人
    其实我还是太喜欢较真,尤其见不得在技术方面“不专业”的做法。
    前面几贴还能算“劝说”,到了后面如果还没有效果就会非常反感……
    其实如果专业的话,说什么都可以阿。那位朋友……正好来回几次都踩到我这条神经了吧,所以反应激烈了点。
    当然还是多谢提醒,我会努力注意的。

  76. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-06-02 10:26:00

    老赵不是猫 但是他有猫尾巴 不能踩 鉴定完毕

  77. 我是IE6[未注册用户]
    *.*.*.*
    链接

    我是IE6[未注册用户] 2009-07-15 21:32:00

    其实我想说,老赵你干吗不让我用IE6看你的文章啊 害的我开Chrome。。。。

  78. 老赵
    admin
    链接

    老赵 2009-07-15 21:35:00

    @我是IE6
    其实我已经写的很清楚的吧……因为IE6太弱了,呵呵。

  79. starfork
    *.*.*.*
    链接

    starfork 2009-07-25 09:21:00

    用IE8了,或者FireFox...

  80. 老赵
    admin
    链接

    老赵 2009-07-25 13:56:00

    @starfork
    cool

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我