Hello World
Spiga

计算机体系结构与程序性能

2009-01-22 08:28 by 老赵, 14787 visits

文章原来的题目是《计算机基础对.NET程序员是否重要》,再我看来,这是一句废话。

当然重要。可是好像有些朋友对于这点很疑惑。最近我又收到一封邮件,一个朋友问我说,他在大学里学的那些课程,似乎都无法对工作有所帮助——当然,这是他目前对于工作的观察,其中大部分是他通过博客园的“管中窥豹”得来的结果:

  • 好多讲ASP.NET的文章啊,控件真好用,做网站很方便。
  • ORM好像对开发很有帮助,我们也来LINQ to SQL,NHibernate一下……网站不就是CRUD吗?
  • JavaScript框架XXX的效果好炫,网站越来越漂亮了。
  • 好像进入并行处理时代了?呀,有了微软的并行库,加上AsParallel方法就能变快了。
  • ……

(以上内容略有艺术夸张,如有雷同,绝非巧合)

是啊,看看大学里的那些课程:数据结构、算法、操作系统、网络、计算机组成原理、编译原理……工作中哪里用上了?于是乎,那位朋友说,他看不少同学平时不上课,也能做做几个网站赚点小钱,生活过的十分滋润。而自己却越学越迷茫,不知应该“转行”去“学做网站”,还是继续“好好学习,天天向上”。我给他的建议是:在大学就好好上课,珍惜学校里的宝贵时光,以免工作时候后悔;如果觉得老师课上得不好,那么就自己看教材,用心去看(不过老师上的好还是要靠自己课后钻研);如果嫌教材不好,那么去买点好教材,现在似乎各种“美国名校教材”都能找到,然后埋头学习便是。

企业抱怨毕业生不好,不是因为他们学了大学里没有用的东西,而是因为学好的人实在太少了。“大学的东西没有用”,大部分原因是没有学好。

想起自己以前也写过类似的文章,叫做《我们到底该怎么学技术?如何成为一个优秀的技术人员?》。老赵自己又看了一遍,发现有朋友对文章里的这么一句话有不同看法:“如果您不了解计算机体系结构,又如何能在Multi-CPU(Multi-Core)时代写出真正高效的应用程序呢?”。他认为这个“如果”(当然也包括文章里的其他“如果”)并不成立。所以老赵现在不谈“数据结构与算法如何有助于改善编程思维有什么改善”,或是“操作系统中线程调度、内存分页机制对于开发大型应用程序的参考价值”等“虚无缥缈”之物。在这篇文章里,我想通过两个直接的例子,来说明了解计算机体系结构对于提高程序性能有什么样的作用。

Locality

在描述何为Locality之前,我们先来看一个例子。例如,我们现在有一个二维数组:

static void Main(string[] args)
{
    int n = 1 << 10;
    int[,] array = new int[n, n];

    for (int x = 0; x < n; x++)
    {
        for (int y = 0; y < n; y++)
        {
            array[x, y] = x;
        }
    }

    ...
}

我们要对这个1024 * 1024的二位数组中所有元素求和。那么我们会怎么写呢?先随手写一把:

static int SumA(int[,] array, int n)
{
    int sum = 0;
    for (int y = 0; y < n; y++)
    {
        for (int x = 0; x < n; x++)
        {
            sum += array[x, y];
        }
    }

    return sum;
}

一个二重循环,遍历二维数组中每一个元素,相加,这也太容易了吧?是啊,不过我们还是可以“换种写法”的:

static int SumB(int[,] array, int n)
{
    int sum = 0;
    for (int x = 0; x < n; x++)
    {
        for (int y = 0; y < n; y++)
        {
            sum += array[x, y];
        }
    }

    return sum;
}

仔细看看,有没有发现区别?没错,只是内层循环和外层循环的位置换了一下。这么做的意义何在?测试一下便知:

static void TestLocality(int[,] array, int n)
{
    Stopwatch watch1 = new Stopwatch();
    watch1.Start();
    for (int i = 0; i < 100; i++) SumA(array, n);
    watch1.Stop();
    Console.WriteLine("SumA: " + watch1.Elapsed);

    Stopwatch watch2 = new Stopwatch();
    watch2.Start();
    for (int i = 0; i < 100; i++) SumB(array, n);
    watch2.Stop();
    Console.WriteLine("SumB: " + watch2.Elapsed);
}

我们把两种加法各执行100次,看看结果:

SumA: 00:00:04.8116776
SumB: 00:00:00.8342202

第二种做法性能是第一种做法的将近5倍。这就是Locality的威力。

Locality(局部性,不知道该不该这么翻译),通俗地说,就是通过利用“缓存”来提高程序运行效率。缓存是计算机中无所不在的概念,这里先借用《Computer Systems: A Programmer's Perspective》(下文称为CSAPP)中的一幅图来简单说明一下:

金字塔的每一层皆是“存储设备”,越靠近顶端则越速度越快,当然也越为昂贵。其中最快的当属寄存器,每个寄存器大小为一个字长(请问这是多大?),数量极其有限;L3则就是我们俗称的“内存”,大小……就取决于我们的荷包了;那么L1和L2是什么,大小又分别是多少呢?L1 Cache(又称Internal Cache),顾名思义它为CPU“内置”的缓存,速度次于寄存器,但还是远远高于内存。L2 Cache原本处于主板之上(因此又称External Cache),它比L1 Cache慢,速度也远高于内存,所以它做为CPU和Chips之间的缓冲——不过如今的CPU都已经“自带”L2 Cache,主板上自然就没有了。至于L1 Cache和L2 Cache的大小,您可以使用CPU-Z看看您机器CPU中Cache的情况如何。这是老赵的工作机:

老赵的机器是双核,各有一个L1 Cache,分为L1 D-Cache(数据缓存)和L1 I-Cache(指令缓存),各32KB大小;两个核共用一个3MB大小的L2 Cache。Cache越大,CPU性能越高,这点毋庸置疑。

L1 Cache和L2 Cache的描述中都有“64-byte line size”字样,表示Cache的Line长为64字节。Line为Cache每次向下级存储设备读取数据的大小。例如,程序在运行时寄存器会向L1请求内存中某个地址的数据(可能是4字节),如果L1中没有这个地址的值,则会向L2中读取包含该地址的一整个Line的数据——也就是64字节,但是并不保证请求的4字节在这64字节的头部或尾部,CPU自有其对齐机制;如果L2没有这个地址的数据,则会向操作系统进行请求,同样是一个Line,64字节。

这就是Locality的关键。在系统中,一个好的Locality表现为,在读取某个地址之后的某个再次读取这个地址或者其附近的地址。由于在读取某个地址的数据之后,缓存中同样保留着附近地址的数据,因此如果请求临近的数据则速度就会非常快(L1 Cache无比迅速)。如果Locality很差,那么就会发现L1 Cache的失效率(Miss Ratio),甚至L2 Cache的失效率都会很高。如果CPU所需要的大量数据都要到L2 Cache甚至更为低效的内存中去读取(试想可能该次内存读取还需要从硬盘交换页上获得),那么性能不变差才令人奇怪。

说了不少“理论”,那么我们来看看上面的例子中为什么第一种方法会远远慢于第二种算法。我们的二维数组每个元素的下标(x, y)如下所示:

0, 0 0, 1 0, 2 0, 1021 0, 1022 0, 1023
1, 0              
2, 0              
             
             
1021, 0              
1022, 0              
1023, 0 1023, 1 1023, 2 1023, 1021 1023, 1022 1023, 1023

在内存中,每个元素的地址按照以下顺序次序排布:

(0, 0), (0, 1), (0, 2), …, (0, 1022), (0, 1023), (1, 0), (1, 1), …, (1023, 0), (1023, 1), …, (1023, 1022), (1023, 1023)

假如按照SumA方法中的读取顺序,L1 Cache中的状况可能就会是:

  1. 读取(0, 0)位置数据 => Cache Miss => L1向L2获取(0, 0), (0, 1), …, (0, 14), (0, 15)共64B数据 => 返回(0, 0)位置数据
  2. 读取(1, 0)位置数据 => Cache Miss => L1向L2获取(1, 0), (1, 1), …, (1, 14), (1, 15)共64B数据 => 返回(1, 0)位置数据
  3. ……
  4. 读取(1023, 0)位置数据 => Cache Miss => L1向L2获取(1023, 0), (1023, 1), …, (1023, 14), (1023, 15)共64B数据 => 返回(1023, 0)位置数据
  5. 读取(0, 1)位置数据 => Cache Miss(因为L1大小有限,读取(0, 0)时放入L1的64B数据已经被其他数据替换) => L1向L2获取(0, 0), (0, 1), …, (0, 14), (0, 15)共64B数据 => 返回(0, 1)位置数据
  6. 读取(1, 1)位置数据 => Cache Miss(理由同上) => L1向L2获取(1, 0), (1, 1), …, (1, 14), (1, 15)共64B数据 => 返回(1, 1)位置数据
  7. ……

而按照在SumB方法中的读取顺序,L1 Cache中的状况可能就会是:

  1. 读取(0, 0)位置数据 => Cache Miss => L1向L2获取(0, 0), (0, 1), …, (0, 14), (0, 15)共64B数据 => 返回(0, 0)位置数据
  2. 读取(0, 1)位置数据 => Cache Hit => 直接返回(0, 1)位置数据
  3. 读取(0, 2)位置数据 => Cache Hit => 直接返回(0, 2)位置数据
  4. ……
  5. 读取(1, 0)位置数据 => Cache Miss => L1向L2获取(1, 0), (1, 1), …, (1, 14), (1, 15)共64B数据 => 返回(1, 0)位置数据
  6. 读取(1, 1)位置数据 => Cache Hit => 直接返回返回(1, 1)位置数据
  7. ……

两种做法立分高下。

其实Locality这一特性在很多系统或应用中都有体现,例如磁盘文件的顺序读取就远比随机读取要快。再举个“时髦”点的东西,Google的Map Reduce论文中就使用了一节来提到Locality——调度器往往选择GFS中文件所在的机器作为Map Worker,这样可以通过读取本地文件尽可能减少数据在网络中的传输,从而大大提高效率。

False Sharing

尽可能读取接近的数据可以通过加强Locality来提高效率,但是在目前的多核甚至多CPU的环境下,操作两个“位置接近”数据可能反而会坏事。

再看一个例子。运行刚才的程序时您应该会发现CPU只用了50%左右,这是因为我们单线程的程序只能在一个核上运行。现在已经进入了并行时代,我们的程序也要与时俱进。微软推出了并行库让并行操作变得异常容易,那么我们就利用并行库来进行刚才二维数组所有元素求和。如下:

static int ParallelSumA(int[,] array, int n)
{
    int processorCount = Environment.ProcessorCount;
    int[] result = new int[processorCount];

    Parallel.For(0, processorCount, (part) =>
    {
        int minInclusive = part * n / processorCount;
        int maxExclusive = minInclusive + n / processorCount;

        for (int x = minInclusive; x < maxExclusive; x++)
        {
            for (int y = 0; y < n; y++)
            {
                result[part] += array[x, y];
            }
        }
    });

    int sum = 0;
    for (int i = 0; i < result.Length; i++)
    {
        sum += result[i];
    }

    return sum;
}

我们首先获取系统中的处理器数目(processorCount),然后根据这个数据对二维数组进行分块,每个线程负责其中一块的计算,并将其保存到一个中间值中(result数组),最后再把所有的中间值累加即可。这种做法是并行计算中常用的模式,有了并行库的帮助,代码可以变得非常简单,直观。不过还是要用数据说话,还是100次求和运算,消耗时间为:

00:00:01.8105218

怎么所花时间反而比单线程要增加了!这固然有线程调度的开销在里面,但是问题的关键还是程序的写法有问题,这种写法发生了False Sharing

False Sharing(错误共享)意为错误地共享了本不该共享的数据。由于每个核的L1缓存互相独立,因此CPU必须有一种机制,能够确保一个核在向它的L1 Cache中写入一个值之后,其他核内L1 Cache中包含这个数据的整个Line就会过期。这意味着其他核在读取地址相同,或者是接近的数据时会遇到L1 Cache Miss。CPU的这种同步机制就是MESI协议。那么我们来分析一下上面的代码到底如何造成了False Sharing。

上面的并行代码会将二维数组分割为独立的几块数据,并且将每一块数据之和存入result数组中。result数组很小,每次都会被完整地读入每个核的L1 Cache内,修改其中任何一个元素都会导致所有核内的result数据过期。于是,两个核在计算时可能就会发生如下情况(以下用CL0和CL1来代表两个核的L1缓存):

  1. CL0读取result[0]的值 => Cache Miss => result数组被加载到CL0中 => 修改CL0中result[0]
  2. CL1读取result[1]的值 => Cache Miss => result数组被加载到CL1中 => 修改CL1中result[1]
  3. CL0读取result[0]的值 => 由于刚才CL1修改了result[1],导致整条Line失效,于是Cache Miss => result数组被加载到CL0中 => 修改CL0中result[0]
  4. CL1读取result[1]的值 => 由于刚才CL0修改了result[0],导致整条Line失效,于是Cache Miss => result数组被加载到CL1中 => 修改CL0中result[1]
  5. ……

从此,两个核结下了不解之缘,他俩将会纠缠大部分时间,直到整个任务结束。当然,在上面的问题中,消除False Sharing非常容易:

static int ParallelSumB(int[,] array, int n)
{
    int processorCount = Environment.ProcessorCount;
    int[] result = new int[processorCount];

    Parallel.For(0, processorCount, (part) =>
    {
        int partSum = 0;
        int minInclusive = part * n / processorCount;
        int maxExclusive = minInclusive + n / processorCount;

        for (int x = minInclusive; x < maxExclusive; x++)
        {
            for (int y = 0; y < n; y++)
            {
                partSum += array[x, y];
            }
        }

        result[part] = partSum;
    });

    int sum = 0;
    for (int i = 0; i < result.Length; i++)
    {
        sum += result[i];
    }

    return sum;

与ParallelSumA方法的实现不同,ParallelSumB使用了一个临时变量partSum来保存每次元素相加后的结果,在最后才将partSum,也就是某一块数据计算后的最终结果写入result数组。由于partSum位于不同线程的堆栈上,因此不同线程的partSum变量的地址分布很广,很难被同时读入同一个核的L1 Cache中,自然也不会造成False Sharing。ParallelSumB方法的性能就会较为令人满意,它执行100次所花时间为:

00:00:00.5366263

上面的例子说明了一点:即使从代码角度来看没有共享任何数据,False Sharing还是可能发生。这迫使我们开发人员在平时工作中需要多留个心眼,偶尔也要考虑一下不同线程频繁修改的数据地址是否非常接近。这需要开发人员对一些“黑盒”内的状况进行适当探索。例如:在.NET程序中,由于托管堆内分配空间的连续性,几乎同时分配的对象地址会比较接近。而且,由于垃圾收集机制在回收时会进行“挤压”,因此生存时间久的对象的地址会愈发接近。可见,即使有了并行库,并行开发依旧不是那么容易。

回到这片文章的主题。不知道经过这两个示例,朋友们是否更进一步了解到计算机体系结构是如何对开发高性能应用程序,尤其是并行环境下的应用程序产生影响的。类似的事例还有很多,也欢迎您谈谈自己的感想。

广告时间

老赵还是要强调计算机基础课程的重要性,也因此在这里推广一个课程。这们课叫做ICS,全称叫做Introduction to Computer System,是老赵就读的复旦大学软件学院大二学生的必修课,由软件学院院长臧斌宇教授主讲。使用的教材便是之前提到的CSAPP。更为关键的是,这门课现在已经成为复旦大学的精品课程,其课件、作业、解答、甚至某些课程的录像也都会在网上公开。大家可以在http://ics.fudan.edu.cn/上访问该课程。

臧教授是老赵十分佩服的一位德才兼备的教授,还非常年轻。他的实验室在系统、编译、网格计算等方面成绩斐然,严谨的科研氛围也一直让我向往。老赵由他带出来的师兄师姐师弟师妹非常之牛,往往去国内其他科研机构发展的硕士生都会给那里的博士生带来很大压力。他们发布在顶级期刊上的论文不在少数(例如这次在OSDI08里就有他们的科研结果),也经常有人被美国名校录取。

Creative Commons License

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

Add your comment

128 条回复

  1. Anders Cui
    *.*.*.*
    链接

    Anders Cui 2009-01-22 01:55:00

    刚好看到另一篇好文:
    http://www.jdon.com/article/31338.html

  2. Joey Yin
    *.*.*.*
    链接

    Joey Yin 2009-01-22 02:13:00

    受教了。
    以往知道数据结构和算法的重要性,对计算机体系结构知识不太注意,以后需要加强了。

  3. 菜鸟2.0 SP2[未注册用户]
    *.*.*.*
    链接

    菜鸟2.0 SP2[未注册用户] 2009-01-22 03:10:00

     回到这片文章的主题。不知道经过这两个示例,
    ——怎么看都像是“离开这篇文章的主题”

  4. 飞林沙
    *.*.*.*
    链接

    飞林沙 2009-01-22 05:52:00

    唉,早就明知道重要,可是要多久才能把东西都学完呢,感觉越学就越感觉自己不会的东西越多。
    老赵,麻烦问下,您今年多大呢?我在想等我到了您这么大的时候,差距和你会有多大呢?

  5. 飞林沙
    *.*.*.*
    链接

    飞林沙 2009-01-22 05:52:00

    唉,学习,学习........

  6. Tony Qu
    *.*.*.*
    链接

    Tony Qu 2009-01-22 06:51:00

    哦!?计算机基础课讲得这么好,听上去很诱人,请问校外人员能不能试听?被老赵讲得心里好痒

  7. eaglet
    *.*.*.*
    链接

    eaglet 2009-01-22 07:01:00

    写得太好了,软件是跑在硬件上的,如果对计算机硬件原理缺乏了解,确实很难编写出高效的代码。

  8. Anders Liu
    *.*.*.*
    链接

    Anders Liu 2009-01-22 07:26:00

    如果把知识结构想象成一个倒置的树,就像N叉树那样。

    那么,各种基础知识就是叶节点,而工作需求则是树枝——需要各种知识的组合。

    问题在于,学校学习是“自底向上”的。可叶子太多了,学校教不完,以至于没时间教你树枝。

    这意味着,从树叶到树枝,这一步需要你自己迈出;也意味着,树叶很重要,否则你迈不到树枝上。

    (这个必须可能不太恰当,知识结构应该是网状的。)

  9. 胡一刀
    *.*.*.*
    链接

    胡一刀 2009-01-22 07:51:00

    很受启发,老赵什么时候给我们再来段扫盲的系列课程就好!

  10. zeus2
    *.*.*.*
    链接

    zeus2 2009-01-22 08:22:00

    当然重要了,而且很重要。

  11. 老赵
    admin
    链接

    老赵 2009-01-22 08:28:00

    --引用--------------------------------------------------
    菜鸟2.0 SP2:  回到这片文章的主题。不知道经过这两个示例,
    ——怎么看都像是“离开这篇文章的主题”
    --------------------------------------------------------
    没错,所以标题改掉了……

  12. 老赵
    admin
    链接

    老赵 2009-01-22 08:29:00

    --引用--------------------------------------------------
    飞林沙: 唉,早就明知道重要,可是要多久才能把东西都学完呢,感觉越学就越感觉自己不会的东西越多。
    --------------------------------------------------------
    不可能学完的阿,我也觉得不会的东西越来越多。

  13. 老赵
    admin
    链接

    老赵 2009-01-22 08:29:00

    --引用--------------------------------------------------
    Tony Qu: 哦!?计算机基础课讲得这么好,听上去很诱人,请问校外人员能不能试听?被老赵讲得心里好痒
    --------------------------------------------------------
    不行,就网上看看课件吧,呵呵……其实我之前也没有学好,现在也在看课件在补。

  14. 老赵
    admin
    链接

    老赵 2009-01-22 08:30:00

    @Anders Liu
    hmmm,如果是树的话,那么它的根节点是什么?

  15. 水果阿生
    *.*.*.*
    链接

    水果阿生 2009-01-22 08:38:00

    好文,写的不错。

  16. 丁学
    *.*.*.*
    链接

    丁学 2009-01-22 08:50:00

    我百分百承认你的强悍,却无法“完全”同意你的观点

  17. 老赵
    admin
    链接

    老赵 2009-01-22 08:51:00

    @丁学
    谢谢,谢谢……

  18. 余寅槎
    *.*.*.*
    链接

    余寅槎 2009-01-22 08:56:00

    老赵, 我顶你。

  19. Yannic Yang
    *.*.*.*
    链接

    Yannic Yang 2009-01-22 08:57:00

    真的很有道理,软件学院就应该好好研究这些东西

  20. 徐少侠
    *.*.*.*
    链接

    徐少侠 2009-01-22 08:59:00

    感谢ing

    好文

  21. 老赵
    admin
    链接

    老赵 2009-01-22 09:01:00

    @Yannic Yang
    是干计算机这一行的,有些东西真的是“必知必会”……当然可能更重要的思路。

  22. 金色海洋(jyk)
    *.*.*.*
    链接

    金色海洋(jyk) 2009-01-22 09:03:00

    标题我是很赞同的,

    就好像一个人虽然不知道汽车的原理,但是他依然可以学会开车,而一个人知道一点汽车原理的话,显然可以更好的开车,可以更好的维护汽车。

    这个很简单。

    但是例子真的很别扭。

    如果定义了一个数组 array[x, y]; 那么一般习惯上我们会把 x作为第一层循环,y作为第二层循环。

    lz的例子里面在给数组赋值的时候也是把x循环放在了外面,但是求和的第一个例子却把y放在了外面。

    因为把y放在了外面才产生的性能损失。而一般情况下,人们都会依据习惯把x放在外面的。只要没有人违反这个习惯,那么就不会出现性能损失。

    当然了,用这个例子来说明两种方法的效率上的差别,让我们在更深层次理解了为什么性能会不一样,这个很好,这个本身也没有什么问题。只是感觉很别扭。


    再看双核的例子。

    在单核求和里面使用的是 sum += array[x, y];

    而在双核求和的第一个例子里面却变成了
    result[part] += array[x, y];

    凭空多出来了一个数组。

    而在第二个例子里面才改成了
    partSum += array[x, y];


    对这个不理解的原因可能是因为我不会写多核的程序的吧。





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

    装配脑袋 2009-01-22 09:08:00

    老赵这个问题正好是非常适合并行的操作,所以除了考虑缓存化之外,用上CPU中的SIMD指令或者直接用SIMD架构的显卡来进行运算那真是合适的不得了了。如果数据规模再大一些的话,就可以把整个矩阵拷到显存里,然后自然按每列分组,用一组SIMD指令对所有列执行相同累加。那个速度快的绝对非同凡响。

  24. 老赵
    admin
    链接

    老赵 2009-01-22 09:08:00

    --引用--------------------------------------------------
    Anders Cui: 刚好看到另一篇好文:
    http://www.jdon.com/article/31338.html
    --------------------------------------------------------
    哇一下,好像吵得很厉害的样子——不过还是不同意banq的一些看法……

  25. 老赵
    admin
    链接

    老赵 2009-01-22 09:09:00

    @金色海洋(jyk)
    第一个可能的确有点别扭,为了说明问题嘛。而且这是最简的最典型的Locality的例子了,其他例子的话——可能要花大量篇幅在问题描述上。
    第二个例子,result数组两种写法都存在阿,只是一种是频繁读取result元素,一种是用中间变量保存,然后最后再写入result数组。如果还是统一用一个sum的话——还是false sharing了,而且多线程访问同一变量会有并发问题。

  26. 老赵
    admin
    链接

    老赵 2009-01-22 09:10:00

    @装配脑袋
    不错不错,不过话说这样就和.net真是丝毫没有关系,完全是系统该干的事情了。

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

    装配脑袋 2009-01-22 09:13:00

    Mono是有SIMD类库的啊,微软的.NET还没有。但是我觉得将来说不定就有了,或者我们应该看看Direct X 11的API?是有很多现成东西你不去用,它就不会自动生效的,所以与其等到PLinq发展到支持这些指令,还不如现在就动手去玩:P

  28. xjb
    *.*.*.*
    链接

    xjb 2009-01-22 09:13:00

    很基础的文章,但不简单

  29. 老赵
    admin
    链接

    老赵 2009-01-22 09:17:00

    @装配脑袋
    有道理,不过我想PLinq其实追求的是通用的并行性,没有限定操作,例如也可以用于索引建立,字符串匹配。对于某些特定操作,我们还得自己来,PLinq不知道能否发展到这一天,我觉得可能还是一套特外的有针对性的类库更现实一些……

  30. 金色海洋(jyk)
    *.*.*.*
    链接

    金色海洋(jyk) 2009-01-22 09:18:00

    对了,忘了问了。

    Intel的cpu和AMD的cpu(都是是多核的),他们在处理多核的程序的时候有没有差别?

    就好像同一段css,有可能在IE里面显示一个样,在FF里面就有是一个样了。

  31. 老赵
    admin
    链接

    老赵 2009-01-22 09:19:00

    @金色海洋(jyk)
    如果出现这个情况,那就是cpu的bug了。

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

    装配脑袋 2009-01-22 09:24:00

    多核就相当于一个对称多处理器的架构,目前AMD和Intel酷睿i7可以看成是一个有总线的通讯架构,同时还有一些多核共享的三级缓存,每个核还有自己的本地存储(二级缓存)。我觉得两者再怎样差异,也就是通讯模型有些差异,怎么也还算是一种架构。

    处理器内部还有SIMD型的数据并行运算能力,浮点处理器可以一条指令处理8-16个浮点数运算。最最核心的指令也是支持4发射,乱序执行的硬件并行。

  33. Anders Liu
    *.*.*.*
    链接

    Anders Liu 2009-01-22 09:24:00

    --引用--------------------------------------------------
    Jeffrey Zhao: @Anders Liu
    hmmm,如果是树的话,那么它的根节点是什么?
    --------------------------------------------------------

    我不是承认这个例子不太恰当了么。

    如果非要说根的话,那不就是程序员的无敌状态么。

  34. 丁学
    *.*.*.*
    链接

    丁学 2009-01-22 09:30:00

    @Jeffrey Zhao
    之所以百分百承认你的强悍,是因为我原来只知其然不知其所以然,在这个层次上来讲,我明显和你不一级别的~~~

    而不“完全”同意你的观点,是因为,企业抱怨毕业生不好,“多数情况”下并不是因为没有学好这些东西,而是没有学好开篇处你那朋友通过博客园的“管中窥豹”得来的结果

    诚然,按你的观点可以让一个人进入微软、进入IBM等等,然后,就业岗位大部分还是分散在中小型企业,而这些,是他们所“不需要”的

    学东西么,也要讲究个分工,个人选择个人的分工方向,研究编译器的要有,拖控件拖得天花乱缀的也要有。分工不只出现在工作中,当今社会,无处不分工啊

  35. 丁学
    *.*.*.*
    链接

    丁学 2009-01-22 09:32:00

    --引用--------------------------------------------------
    金色海洋(jyk): 对了,忘了问了。
    Intel的cpu和AMD的cpu(都是是多核的),他们在处理多核的程序的时候有没有差别?
    就好像同一段css,有可能在IE里面显示一个样,在FF里面就有是一个样了。
    --------------------------------------------------------
    如果出现了这个情况,你可以找他们家报告BUG,或许他们会送你俩CPU玩玩儿,嘿嘿
    不过这个例子似乎不是很妥

  36. 老赵
    admin
    链接

    老赵 2009-01-22 09:35:00

    @丁学
    嗯嗯,是啊,我也同意“分工”这一点。但是我还是觉得,如果把学校的东西学好,那些asp.net阿,javascript阿,xxx阿……都只是一个具体的发展方向,也会较为顺利。当然这只是个人感觉,就像我说的学校课程在“锻炼思维”这个说法比较虚无缥缈,我也找不到合适的例子来论证我的观点……

  37. 老赵
    admin
    链接

    老赵 2009-01-22 09:36:00

    --引用--------------------------------------------------
    Anders Liu:
    我不是承认这个例子不太恰当了么。
    如果非要说根的话,那不就是程序员的无敌状态么。
    --------------------------------------------------------
    根,那就是程序员那强大的肉体,万物的根源亚,哈哈。

  38. 枫
    *.*.*.*
    链接

    2009-01-22 09:37:00

    --引用--------------------------------------------------
    Jeffrey Zhao: --引用
    哇一下,好像吵得很厉害的样子——不过还是不同意banq的一些看法……
    --------------------------------------------------------

    我认为Banq的看法是有一定的道理的。

    当然,我并没有说老赵你这篇文章的观点错误,基础知识当然很重要,但这个重要是在后期。就如同马斯洛的那个金字塔,满足了一定的层次自然有更高层次的需求!

    也许,老赵你的这篇文章对于还在学校学习的学生来说,的确很适用,因为他们仍然有条件;但是对已经工作的,尤其是在阿朱所提到的软件作坊中工作的在职人员,那么我觉得,Banq的看法可能更适合他们。

  39. 老赵
    admin
    链接

    老赵 2009-01-22 09:39:00

    @枫
    可能是吧。

  40. 余寅槎
    *.*.*.*
    链接

    余寅槎 2009-01-22 09:40:00

    问老赵---文中所述:“在系统中,一个好的Locality表现为,在读取某个地址之后的某个再次读取这个地址或者其附近的地址。”

    这里的"系统"是指什么?操作系统?还是什么?

    “其附近的地址”又是指什么? 边上两个? 还是边上10个? 还是把cache存满为止?

  41. 金色海洋(jyk)
    *.*.*.*
    链接

    金色海洋(jyk) 2009-01-22 09:41:00

    记得以前有过“真假双核”的说法,区别好像就是在于“共享缓存”的处理上。

    不知道现在是什么样子了,所以才会有这样的疑问。

    另外还有一个问题。

    通过lz的讲解,我们知道了数组如何处理效率才能高效,那么其他的地方呢?如何去处理,如何学习呢?

  42. 老赵
    admin
    链接

    老赵 2009-01-22 09:46:00

    @余寅槎
    “系统”是个模糊的概念,可以简单的认为是“CPU”,也就是你的程序要让CPU读取数据时尽可能利用到缓存。附近的地址,就是指一个Line上的数据,不用追究到底是边上几个……实际上一般也没法追究,因为程序逻辑还是关键,而且不同CPU还不一样,系统也不是只为你一个程序服务的,呵呵。
    Locality是一个“原则”,不时考虑到便是。

  43. 老赵
    admin
    链接

    老赵 2009-01-22 09:48:00

    @金色海洋(jyk)
    我的目的又不是讲“数组该怎么处理”,只是举个例子来说明问题而已,其他环境下自然要自己思考阿。

  44. dyd[未注册用户]
    *.*.*.*
    链接

    dyd[未注册用户] 2009-01-22 09:56:00

    对于这种在内存中顺序访问的数据访问类型当然是可以通过楼主的上述方式进行执行优化,但对于一些随即访问类型,比如索引等,则不是这么简单,大家可以参看一些这方面的论文,有很多关于优化Cache访问性能的方法。有时候学习学习学术论文,还是可以吸收很多先进的优化方法的

  45. 金色海洋(jyk)
    *.*.*.*
    链接

    金色海洋(jyk) 2009-01-22 09:59:00

    对呀,问题就在于如何思考?!

    我们知道了重要性,那么我们如何来思考呢?

    在学校了我们是永远学习不到最新的硬件知识的,老的知识不适用,甚至适得其反。

    我们要如何掌握。

    硬件的发展速度是很快的,几年前,64位、双核,还是很新鲜很贵的,现在呢,已经是“地摊货”了。

    如果我们在写程序之前,要考虑很多很多的话,那不成了前怕狼后怕虎了吗?

    写一段代码之前都要思考,如何写才能够高效、稳定、可扩展、可维护、易读......,还要考虑一下程序是运行在单核还是多核,程序可以使用几个核,那老板岂不会急死?

    这些问题我们要如何处理呢?

    既然lz把我们带进来了,那么下一步要怎么走呢?至少也给个方向吧。

  46. 老赵
    admin
    链接

    老赵 2009-01-22 10:00:00

    --引用--------------------------------------------------
    dyd: 对于这种在内存中顺序访问的数据访问类型当然是可以通过楼主的上述方式进行执行优化,但对于一些随即访问类型,比如索引等,则不是这么简单,大家可以参看一些这方面的论文,有很多关于优化Cache访问性能的方法。有时候学习学习学术论文,还是可以吸收很多先进的优化方法的
    --------------------------------------------------------
    这话我举四肢赞成

  47. dyd[未注册用户]
    *.*.*.*
    链接

    dyd[未注册用户] 2009-01-22 10:00:00

    比如针对Hash索引,BTree索引,都可以通过比较复杂的方法达到Cache优化的目的,比如预取指令,处理器一般都支持各种功能的预取指令,程序员可以直接调用,或者通过多线程也是个不错的选择,不过现在的多核处理器一般都共享L2-Cache,因此需要注意Cache访问冲突问题,即在共享L2-Cache中的时候,可能多个线程都需要访问,因此线程1可能将线程2将要访问的数据替换出L2-Cache,更多处理器核心时这个问题更加突出,我做过实验,在实现Radix-Join算法时,在双核处理器上跑,Cache访问冲突对性能影响较大。

  48. JimLiu
    *.*.*.*
    链接

    JimLiu 2009-01-22 10:05:00

    刚看到Locality的时候,我还真没想到往“局部性”上靠呢。
    并行计算最怕的就是访问共享资源带来的性能隐患了,受教。

  49. dyd[未注册用户]
    *.*.*.*
    链接

    dyd[未注册用户] 2009-01-22 10:05:00

    本贴中讨论的基本上是优化程序的Cache访问性能,如果你的程序运行中,磁盘I/O代价占主要代价的话,而且如果程序赶进度的话,就没有必要注意这些Cache访问性能问题,因为磁盘I/O和内存访问的代价差距实在不小。
    建议朋友们可以先看看Intel的一些官方资料,有很多介绍Cache基本知识和如何优化Cache访问性能的小文章,可以从这里入门,然后探寻Cache性能优化之路。

  50. dyd[未注册用户]
    *.*.*.*
    链接

    dyd[未注册用户] 2009-01-22 10:08:00

    还有一本国防科技大学计算机体系结构课程采用的牛书《计算机体系结构--量化研究方法》,国外超级牛人写的,出到了第4版了吧,介绍的通俗易懂,关于Cache部分的介绍很好很强大

  51. 老赵
    admin
    链接

    老赵 2009-01-22 10:09:00

    @金色海洋(jyk)
    学校里的“旧知识”哪里过时了,就拿计算机体系结构来讲,没有见到有多少变化。
    我写文章目的是要大家知道这回事情,如果搞得前怕虎后怕狼,那只能说明你没有把握问题的关键。
    接下来怎么走,我又要说了,好好学习学校的“旧知识”,锻炼“思维”,等能够把握事物主要矛盾自然不会迷茫了。

  52. 施杨
    *.*.*.*
    链接

    施杨 2009-01-22 10:11:00

    讲的很深刻,这些东西的确很重要。在性能方面的表现尤为突出,算法是程序设计必须有的,
    所以不同档次的人写出相同功能的程序,相差很大。看完后,羡慕一下复旦的学子。

  53. GUO Xingwang
    *.*.*.*
    链接

    GUO Xingwang 2009-01-22 10:11:00

    仔仔细细的看了两遍,感觉老赵的可怕之处不在于技术,而在于商业头脑!哈哈
    玩笑!

  54. 老赵
    admin
    链接

    老赵 2009-01-22 10:11:00

    --引用--------------------------------------------------
    dyd: 还有一本国防科技大学计算机体系结构课程采用的牛书《计算机体系结构--量化研究方法》,国外超级牛人写的,出到了第4版了吧,介绍的通俗易懂,关于Cache部分的介绍很好很强大
    --------------------------------------------------------
    其实我觉得,国内使用很多教材都是世界一流的阿,完全不必说学校里学到的东西没有用。

  55. 金色海洋(jyk)
    *.*.*.*
    链接

    金色海洋(jyk) 2009-01-22 10:12:00

    其实知道这个很重要的人,一直都在思考、研究。

    而对于那些不知道这个很重要的人,自然不回去思考、研究。

    现在托lz的福,现在大家都知道重要性了,那么如何思考呢,不对,思考了之后会是什么样子呢?

    大家都去研究cahce吗?还是做其他的什么?

    上次和丁学、怪怪在一起聊天,知道了一个问题,有一些人写程序就是为了工作(也就是生存),有了工作,才有钱交房租,才有地方住,才有钱吃饭。

    那好,我们思考,思考之后呢?有什么帮助呢?


    不知道为什么,自己突然这么现实了。

    不好意思,在这里发牢骚了,抱歉。

  56. 老赵
    admin
    链接

    老赵 2009-01-22 10:14:00

    @金色海洋(jyk)
    没事没事,希望你可以早日走出迷茫。

  57. 老赵
    admin
    链接

    老赵 2009-01-22 10:14:00

    --引用--------------------------------------------------
    GUO Xingwang: 仔仔细细的看了两遍,感觉老赵的可怕之处不在于技术,而在于商业头脑!哈哈
    玩笑!
    --------------------------------------------------------
    老赵最缺的就是商业头脑

  58. 妖居
    *.*.*.*
    链接

    妖居 2009-01-22 10:17:00

    好文。通过生动的例子阐述相对深奥的问题,才是高手。
    PS:你把标题改了,害得我RSS阅读器读不出你这篇文章。

  59. 助燃
    *.*.*.*
    链接

    助燃 2009-01-22 10:18:00

    --引用--------------------------------------------------
    Jeffrey Zhao: --引用--------------------------------------------------

    Anders Cui: 刚好看到另一篇好文:

    <a href="http://www.jdon.com/article/31338.html" target="_new">http://www.jdon.com/article/31338.html</a>。

    --------------------------------------------------------

    哇一下,好像吵得很厉害的样子——不过还是不同意banq的一些看法……
    --------------------------------------------------------

    老赵,看到那张关于缓存的金字塔图,让我联想到现实世界也是如此:在金字塔顶端的就好像你们mvp,必然是少而精;在金字塔底部的就好像我们IT民工,必然会多而杂。如果想做mvp,自然要打好基础;但如果只是做民工,拿来主义相对而言性价比更好。所以我觉得您和banq都没有错,只是面向的人不同。

  60. 金色海洋(jyk)
    *.*.*.*
    链接

    金色海洋(jyk) 2009-01-22 10:25:00

    其实我并不迷茫,我知道我要做什么,我该做什么,我要如何去做。

    只是我有点懒。

  61. 大石头
    *.*.*.*
    链接

    大石头 2009-01-22 10:37:00

    不得不佩服呀

  62. dyd[未注册用户]
    *.*.*.*
    链接

    dyd[未注册用户] 2009-01-22 10:50:00

    估计在现在这种多核处理器便宜的象白菜的年代,如果充分利用好多核处理器的并行计算性能对程序性能的影响是巨大的,而以前那些用于对称式多处理器的多线程算法,拿到多核处理器上后,会不会因为多核处理器在体系结构上的不同而造成负面影响,这个确是值得探讨啊,特别是对Cache访问性能非常敏感的程序。而且以后超大规模核心的多核处理器的问世,如果将这些庞大的计算资源合理的分配给应用程序的各种线程,也是个头疼的问题啊。
    真是硬件技术的不断发展,是软件性能提升的引擎,但同时也带来了新问题啊。多核,火起来了,大家注意啊!

  63. overred
    *.*.*.*
    链接

    overred 2009-01-22 11:00:00

    《银河麒麟操作系统的自虚拟化技术及其应用》
    老赵能搞点资料不?

  64. ZelluX[未注册用户]
    *.*.*.*
    链接

    ZelluX[未注册用户] 2009-01-22 11:03:00

    赞师兄~~

  65. 老赵
    admin
    链接

    老赵 2009-01-22 11:07:00

    --引用--------------------------------------------------
    ZelluX: 赞师兄~~
    --------------------------------------------------------
    你就是我说的“无比nb的师兄师姐师弟师妹”里的一个,别谦虚了……

  66. Lance.Liang
    *.*.*.*
    链接

    Lance.Liang 2009-01-22 11:10:00

    果然是牛人,看的深,看的远

  67. 丁学
    *.*.*.*
    链接

    丁学 2009-01-22 11:27:00

    @Jeffrey Zhao
    确实这些都是一个具体的发展方向,但是,超过70%的人要靠这个毕业后找一个能吃得起饭的工作,所以在毕业前一定要会
    当然,学会这些和打基础并没有冲突,这个看个人了

  68. kwanhong young
    *.*.*.*
    链接

    kwanhong young 2009-01-22 11:37:00

    计算机体系结构和基础知识还有数据结构固然重要,不过回头看过来我们大学的课程都是砖得太深入了,对于没有做过程序、项目、产品的同学来说基本上是吸收不了,更别说体会其中的精髓了,于是乎这些课程刚开始1,2周还有人听,到后面基本上没有人跟着学了。所以我同意老赵的标题,但是不同意文中提到的学习方法和过程——在大学里将这些内容硬着头皮学完。我觉得学习计算机同练武功差不多,学到浅层次的知识后要去试炼运用,发现层次不够时再去学深层次的知识,也就是一种螺旋式的学习过程。
    我相信如果你的同班同学没有像你这样的实践经验的话,任凭臧教授讲得如何精彩都是索然无味的。

  69. 老赵
    admin
    链接

    老赵 2009-01-22 11:39:00

    @kwanhong young
    作业,实践等都是学习的一部分呀。

  70. kwanhong young
    *.*.*.*
    链接

    kwanhong young 2009-01-22 11:46:00

    还有一点就是,随着部分软件开发领域的工程化,分工越来越细,很多企业需要的恰好是要求熟练掌握你文中管中窥豹提到的那些工具和技巧的人,相信你要招一个web开发人员解决人手问题的时候,也会毫不犹豫地招这类的开发员,所以这些观点还需要结合领域和实际情况而论。

  71. uda1341
    *.*.*.*
    链接

    uda1341 2009-01-22 11:56:00

    恕我直言,其实软件开发知识的根不在硬件体系结构上。

    我可一点都没有否认基础的重要,比如学习贯通了编译原理的话,哪怕就是明白了编译原理怎么回事,然后做过设计一门小语言的课外作业,那各种命令式语言的共同之处,就了然于心了。

    再如现在如日中天的函数式语言,只要具有lambda演算的基础知识,自然就不会莫名惊诧,运用起来也会得心应手。

    再如类似XML,SQL这种基于描述的语言,这种产品的根看起来不是很好找,实际上只要学习过PROLOG,便可以把XML理解为PROLOG的一个很小的子集,用了另外一种表达方式而已。

    最后再举个数据结构的例子,融会贯通,可以让自己在各种语言中运用自如,起到事半功倍的效果。

    在我看来,程序设计的根,是在于训练自己的逻辑表达能力,对抽象的提炼能力,也就是说,除了计算机某些理论基础之外,还得包括逻辑思维的训练,还应该把后来新出现的例如设计模式这种书加入到基础课程中来。而不是先学习各种各样的产品功能,再去看设计模式。

  72. 老赵
    admin
    链接

    老赵 2009-01-22 11:58:00

    @uda1341
    嗯,我基本上同意你的观点。我这篇文章只是对计算机体系结构多说了几句而已,我不是写着其他基础课程的重要性了吗?呵呵

  73. 痘痘熊
    *.*.*.*
    链接

    痘痘熊 2009-01-22 12:05:00

    不错的文章。后悔念书的时候都整天玩游戏去了,工作好几年了才想起来有无数问题。再追根溯源,发现大学教材里面其实大都有答案……

  74. uda1341
    *.*.*.*
    链接

    uda1341 2009-01-22 12:05:00

    @Jeffrey Zhao

    不好意思,那个恕我直言不是针对楼主的,是针对我想象会有的这么一种观点:

    硬件是软件的基础,所以搞清楚硬件的工作原理,是学习好软件的基础。

    这种观点过于机械,从知识的角度看,软件的基础其实越来越与硬件脱离了关系。

  75. 伯乐族人
    *.*.*.*
    链接

    伯乐族人 2009-01-22 12:06:00

    好文章,一定要更多人看到,你的文章已经被伯乐族 www.*** 收藏。你也可以通过伯乐族提交你的文章,这样有更多人能关注到你的文章。

  76. LJLJ[未注册用户]
    *.*.*.*
    链接

    LJLJ[未注册用户] 2009-01-22 12:35:00

    真知灼见,受益匪浅。

  77. 0750[未注册用户]
    *.*.*.*
    链接

    0750[未注册用户] 2009-01-22 13:19:00

    阅读。。。


    理论很重要,之前公司一位同事给我们讲解struct2的时候 谈到拦截器 后来阅读源代码才知道用递归方法实现的。 其实有的时候知识并不是我们想象的那么远不可及。它就在我们身边。只是我们目前还没有发现。。

  78. Ghost-W
    *.*.*.*
    链接

    Ghost-W 2009-01-22 13:33:00

    不错不错 收藏了……

  79. liuzhiy[未注册用户]
    *.*.*.*
    链接

    liuzhiy[未注册用户] 2009-01-22 13:44:00

    我想这种情况是不是和CPU的存取架构有关系呢?共享错误和是不是更多的发生的smp/mpp架构中?numa会不会不太可能发生?

    另外,有篇msdn的文章写得不错,也阐述了错误共享的问题,推荐:
    http://msdn.microsoft.com/zh-cn/magazine/cc872851.aspx

  80. 老赵
    admin
    链接

    老赵 2009-01-22 13:58:00

    @liuzhiy
    这文章写得真不错,尤其是发现False Sharing问题的方法和其他方面的一些指导。

  81. volnet(可以叫我大V)
    *.*.*.*
    链接

    volnet(可以叫我大V) 2009-01-22 14:26:00

    用党中央的口号讲就是要与时俱进,啥叫与时俱进呢?就是在学校学习的时候要NO.1,在岗位工作的时候要NO.1,该算法数据结构的时候,咱要牛逼哄哄,该拖拖控件的时候,也要优雅迷人……不管做啥工作的都得尽心尽责,360行行行处状元,如果做木匠,那就要成为一名鲁班,孜孜不倦,就离胜利不远了!

    好想回到原始社会,穿着树叶漫山遍野地跑,无忧无虑啊!现在的社会,对短暂的人生来说,无疑只是增加了负担,进步不在于你拥有豪宅,而只表现在你过得安逸,年关要到了,想想要挤车回家,想想要包出很多红包,想想还有很多酒宴,想想……,还是原始社会好!没有CPU和集成电路……世界该多美啊

  82. Michael-Chen
    *.*.*.*
    链接

    Michael-Chen 2009-01-22 15:04:00

    好文啊!!~

  83. Angel Lucifer
    *.*.*.*
    链接

    Angel Lucifer 2009-01-22 15:24:00

    看到老赵的这篇文章,倒想起我 N 年前研究 Memory Model 时候翻译的文章了。

    顺便毛遂自荐一把,这篇文章的好处不仅在于清楚阐述 CLR 2.0 的内存模型,而且我顺手增加了关于 lock-free 的内容。

    文章地址:

    CLR 2.0 Memory Model

  84. liuzhiy[未注册用户]
    *.*.*.*
    链接

    liuzhiy[未注册用户] 2009-01-22 15:55:00

    @Jeffrey Zhao

    两个问题请教:
    1. 我想这种情况是不是和CPU的存取架构有关系呢?共享错误和是不是更多的发生的smp/mpp架构中?numa会不会不太可能发生?

    2. 几个小时前给您去了个邮件,请教了个问题,也请您回答下。

    谢谢!~

  85. dyd[未注册用户]
    *.*.*.*
    链接

    dyd[未注册用户] 2009-01-22 16:00:00

    --引用--------------------------------------------------
    liuzhiy: @Jeffrey Zhao

    两个问题请教:
    1. 我想这种情况是不是和CPU的存取架构有关系呢?共享错误和是不是更多的发生的smp/mpp架构中?numa会不会不太可能发生?

    2. 几个小时前给您去了个邮件,请教了个问题,也请您回答下。

    谢谢!~
    --------------------------------------------------------

    只要是每个处理器核心有着各自独立的L1-Cache,楼主说的共享错误就会发生,SMP和CMP都会发生

  86. dyd[未注册用户]
    *.*.*.*
    链接

    dyd[未注册用户] 2009-01-22 16:04:00

    在某些特定的应用中,缓解楼主所说的共享错误其实有一个很好的方法,采用Parallel Buffer结构,即将缓存分成若干个小的缓存块,每个线程同时只能访问一个缓存块。这个具体的细节可以参看《Parallel buffer for chip multiprocessors》一文,可以用google一下就能获得。

  87. liuzhiy[未注册用户]
    *.*.*.*
    链接

    liuzhiy[未注册用户] 2009-01-22 16:06:00

    --引用--------------------------------------------------
    dyd: --引用--------------------------------------------------
    liuzhiy: @Jeffrey Zhao

    两个问题请教:
    1. 我想这种情况是不是和CPU的存取架构有关系呢?共享错误和是不是更多的发生的smp/mpp架构中?numa会不会不太可能发生?

    2. 几个小时前给您去了个邮件,请教了个问题,也请您回答下。

    谢谢!~
    --------------------------------------------------------

    只要是每个处理器核心有着各自独立的L1-Cache,楼主说的共享错误就会发生,SMP和CMP都会发生

    --------------------------------------------------------

    numa会不会不太可能发生?

  88. dyd[未注册用户]
    *.*.*.*
    链接

    dyd[未注册用户] 2009-01-22 16:13:00

    numa只是一种内存访问方式吧,采用分布式存储器模式,不同的是所有节点中的处理器都可以访问全部的系统物理存储器。这个与处理器中的每个处理器核心拥有各自独立的L1-Cache不是同一个概念层次上的东西吧,所以只要是同时运行的线程都将相同的数据读取了L1-cache,并需要修改这块内存,就有可能发生错误共享

  89. Cheney Shue
    *.*.*.*
    链接

    Cheney Shue 2009-01-22 16:53:00

    看不懂啊

  90. liuzhiy[未注册用户]
    *.*.*.*
    链接

    liuzhiy[未注册用户] 2009-01-22 16:56:00

    @dyd

    的确是无论什么内存访问架构,在每颗CPU都会有L1 Cache,因此也会有cacheline的概念。我是在想,是不是numa会不会不太可能发生这中 false cache的情况?l1+l2(如果有的话)+node分区,这样一来是不是“连续性”不那么大?而且还有更重要的一点,numa允许操作系统参与一致性检测,毕竟是mpp过来的产物,我个人觉得这起码给windows提供了机会,减少错误cache的能力?

    另外,非常同意赵老师的看法,很多情况下是我们太“短视”,觉得写程序就是“施施拽拽”,不求本质。就这个false cache的事,操作系统设计上早点有考虑,有兴趣的可以看看linux上MCS Spinlock自旋锁,它就是为了修正Linux Kernel 2.6.25内核中FIFO Ticket Spinlock的false cache问题,通过为每个cpu都配置一个slock进行独立自旋,避免了执行线程都对同一个slock(应该是raw_spinlock_t.slock记不清了)申请和释放的修改时,所造导致的所有参与排队的cpu l1 cache无效,从而大大提升了系统整体的性能。而且据一些资料讲windows实现自旋锁,在这方面更加的精巧,高效。

  91. dyd[未注册用户]
    *.*.*.*
    链接

    dyd[未注册用户] 2009-01-22 17:39:00

    连续性不那么大,可能更加是由程序的数据访问特点决定的吧,不过这个问题也不必要深究了,到时候写了程序跑一下就知道了,可能有些情况下问题不大,有些情况下就严重一些。ps:numa这种结构的机器好像不常见,我至今还无幸有过

  92. 怪怪
    *.*.*.*
    链接

    怪怪 2009-01-23 10:11:00

    纯属个人意见:banq是掉在那坑里出不来了...,Jdon现在整个一害人的站;倾向于老赵的看法。

    不过关于这篇文章的具体内容,也许鉴于我自己的学习方式,我认为是细节,应该是最后才关心的(但绝不是不关心)。

  93. 徐少侠
    *.*.*.*
    链接

    徐少侠 2009-01-23 11:14:00

    我仔细执行了老找提供的程序

    两种方法的效率差别在10%以内?

    老找你是不是Stopwatch 有问题啊?

    另外,我的执行时间只有几个毫秒啊?

    你怎么要4秒多呢?

  94. 徐少侠
    *.*.*.*
    链接

    徐少侠 2009-01-23 11:15:00

    我的机器:

    P8600+4G DDR2

    windows server 2008 sp1

    程序是VS2008里写的3.5的C#控制台项目

  95. 老赵
    admin
    链接

    老赵 2009-01-23 11:41:00

    @徐少侠
    几秒钟的差别,人就可以感觉出来啊,怎么可能是Stopwatch的问题,呵呵。
    你可以把程序发到我的邮箱里看看。

  96. 徐少侠
    *.*.*.*
    链接

    徐少侠 2009-01-23 12:08:00

    ?你电子邮件地址在哪里啊?页面上没看到啊?

  97. 老赵
    admin
    链接

    老赵 2009-01-23 12:27:00

    @徐少侠
    jeffz爱特live到特com

  98. 老赵
    admin
    链接

    老赵 2009-01-23 12:48:00

    @徐少侠
    我当然是用release进行测试的,我用用看你的代码吧(你贴的删了,太长)。
    还有我说的这点和debug/release无关,它们都是受计算机体系结构影响的。

  99. 徐少侠
    *.*.*.*
    链接

    徐少侠 2009-01-23 12:53:00

    邮箱发了

  100. 老赵
    admin
    链接

    老赵 2009-01-23 12:53:00

    @徐少侠
    我不知道你的程序怎么跑得,但是你的代码里执行了一亿次SumA,如果还在几毫秒里出来的,那也太nb了,呵呵。
    // 改成100次之后结果和文章一样的。

  101. 徐少侠
    *.*.*.*
    链接

    徐少侠 2009-01-23 12:56:00

    开工了,下午有空再来聊

  102. 老赵
    admin
    链接

    老赵 2009-01-23 12:56:00

    --引用--------------------------------------------------
    徐少侠: 邮箱发了
    --------------------------------------------------------
    贴个链接在这里让大家下载吧,别人可以一起看看。

  103. 徐少侠
    *.*.*.*
    链接

    徐少侠 2009-01-23 13:25:00

    注意

    我的程序里是先调用B方法后调用A的

    难道还是有这么大差别?

  104. 老赵
    admin
    链接

    老赵 2009-01-23 14:04:00

    @徐少侠
    注意了,结果一样。本来就不会因为这个有差别。
    我还是难以想象你的结果,你可是执行了1亿次的1024*1024的加法,怎么可能几毫秒就出来了。

  105. 徐少侠
    *.*.*.*
    链接

    徐少侠 2009-01-23 14:35:00

    我找了台P4 3G的机器,内存1G的,windows 2000 .net2.0 用CPU-Z查了下,单核的,一级缓存16K,后面的参数也要比32K要小 不过64字节的行是一样的 可是执行结果还是和我自己机器上的差不多啊 老赵,要坦白你用啥机子跑的了。

  106. 老赵
    admin
    链接

    老赵 2009-01-23 14:37:00

    --引用--------------------------------------------------
    徐少侠: 你不用你的Stopwatch看看
    --------------------------------------------------------
    怎么开始搞封建迷信了,几毫秒和几百毫秒差那么大,人都感觉的出来,呵呵……
    就是你的代码,把1亿改成100,结果是SumB 800多,SumA 4000多,和文章的结果一样。

  107. 老赵
    admin
    链接

    老赵 2009-01-23 17:52:00

    @徐少侠
    我跑的机器很普通,实际情况和理论也符合,我的程序和你的程序都很正常。
    反而是你的结果从情理上都说不通,还是那句话,执行了1亿次的1024*1024的加法怎么可能几毫秒就出来了。
    这样吧,大家都用我机器上的代码跑跑看,说一下自己的结果吧。

  108. 徐少侠
    *.*.*.*
    链接

    徐少侠 2009-01-23 18:15:00

    我用了你的代码

    但是我得到的依然是一样的结果

  109. 老赵
    admin
    链接

    老赵 2009-01-23 18:18:00

    @徐少侠
    等别人测试吧。

  110. 徐少侠
    *.*.*.*
    链接

    徐少侠 2009-01-23 18:22:00

    http://files.cnblogs.com/Chinese-xu/数组访问.rar

    大家都下载了看看?

    奇怪的问题啊

    我运行的结果是count=10亿次才需要1.3-1.4秒

    CPU:P8600
    OS:Windows Server 2008 Standard

    注意,直接运行编译后的exe文件

  111. 喝水的骆驼[未注册用户]
    *.*.*.*
    链接

    喝水的骆驼[未注册用户] 2009-01-23 21:20:00

    老赵说的很对,做这行的,刚入行的时候基本上都觉得计算机专业课其实没啥用,尤其是学校教的那些,当逐渐开始深一步的时候,便发现很多东西不自然的就卡在了这些基础上。为了这个,我放弃了工作,选择读研回到学校。
    不过,我发现其实我这样做也不全对,还是要看学校本身。好的学校有好的老师,自然讲的深刻一点,但是无论哪个学校,都不会把一本教材完整的讲完,而是选择挑选部分来讲,甚至一些学校,一本书考完试连三分之一都没有讲完。这一点就导致学而不精,听而不会,甚至于完全不听。
    由于个人经历,我一直认为,基础不能上班后再打,但是妄图在学校通过老师学会一些东西,基本上是不可能的,分数并不重要,而是内容才是关键,自学+大量的空闲时间才是学校的根本。

  112. 老赵
    admin
    链接

    老赵 2009-01-23 21:57:00

    @喝水的骆驼
    其实以前上有些课的时候,教学方式就是“教一做二考三”,作业和考试都是超过课堂的内容,都需要自己来学习。

  113. tsdyy[未注册用户]
    *.*.*.*
    链接

    tsdyy[未注册用户] 2009-01-24 02:44:00

    哎...英语不过关啊...想看些好的资料都看不懂...
    我要恶补英语!!!

  114. 徐少侠
    *.*.*.*
    链接

    徐少侠 2009-01-24 05:28:00

    用AMD 8450 三核运行了一下
    XP 系统
    结果还是两个方法耗时基本一致

    不过程序仅适用了一个核,所以速度上比P8600要差3-4倍

    开始迷惑老赵的机器咋这么样呢?

  115. 老赵
    admin
    链接

    老赵 2009-01-24 10:00:00

    @徐少侠
    下载了你的程序,反编译了exe终于知道怎么回事了。
    你在Program的构造函数里把n设为了1024?你测试用的Main方法之类的都是静态方法阿,也就是说在运行的时候n根本就等于0,你循环100亿次也好,每次循环里面都不作计算的,当然不花时间了。
    这个问题调试一下应该就很容易看出来,你怎么会纠缠到现在呢?用我的程序试验一下也可以阿……

  116. 徐少侠
    *.*.*.*
    链接

    徐少侠 2009-01-24 11:55:00

    真的哦,原来忘记搞静态构造了

    翻在阴沟了了。

    谢谢

  117. 徐少侠
    *.*.*.*
    链接

    徐少侠 2009-01-24 12:00:00

    哦耶

    把构造搞成静态的了

    结果总算是对了

    性能差12倍左右

    再次感谢你的文章,接下来我打算对双核和三核开始下手了。

    压榨他们的性能。

    有问题你可要指点啊。

  118. 徐少侠
    *.*.*.*
    链接

    徐少侠 2009-01-24 13:01:00

    最后附上正确的测试结果:
    SumA: 00:00:02.0338489
    SumB: 00:00:00.1615749
    ParallelSumA: 00:00:00.5773108
    ParallelSumB: 00:00:00.1022118

  119. Alvan[未注册用户]
    *.*.*.*
    链接

    Alvan[未注册用户] 2009-01-26 02:52:00

    看到前半部分似曾相识,看到FDU软件学院差点没哭出来,原来大家居然是同门。毕业这么多年,老大讲的这门课至今仍受用无穷,《CSAPP》也是我为数不多保存的教材之一。顺带一提的是ICS其实UC Berkley的课程,即使在UCB的CS也是绝对的经典课程。

  120. 老赵
    admin
    链接

    老赵 2009-01-26 03:26:00

    @Alvan
    呵呵,你可是我大一时的TA啊……

  121. 残香恨
    *.*.*.*
    链接

    残香恨 2009-02-01 16:46:00

    《Computer Systems: A Programmer's Perspective》
    这本书的中文版我买了,很遗憾,还没时间看。令我非常晕的是,我当时在Amazon买时是60多RMB,后来Amazon 10块RMB就卖了。

  122. 老赵
    admin
    链接

    老赵 2009-02-01 16:56:00

    @残香恨
    Amazon.cn?

  123. 残香恨
    *.*.*.*
    链接

    残香恨 2009-02-02 10:48:00

    @Jeffrey Zhao
    对,是Amazon.cn,但现在难找这种好机会了。

  124. CSharp NET[未注册用户]
    *.*.*.*
    链接

    CSharp NET[未注册用户] 2009-02-03 17:06:00

    很久以前就看到一篇文章。
    也是讨论应不应该掌握基础知识。

    如上面有人在问,
    老赵多大年龄了。

    我们程序员不可能都掌握了,
    专研其它一门就可能要花一辈子的时间。

    不可否认程序员应该多掌握,
    但必竟是有限了。

    这求追求的只是一种理想状态。

  125. Indigo Dai
    *.*.*.*
    链接

    Indigo Dai 2009-02-07 13:59:00

    这篇文章看了,让我有压力……

  126. panye[未注册用户]
    *.*.*.*
    链接

    panye[未注册用户] 2009-04-14 11:18:00

    一流大学的教育质量确实不同呀!

  127. thinkbig
    202.170.216.*
    链接

    thinkbig 2010-05-27 10:22:21

    问lz一个问题

    上面第一段程序是在x86的机器上跑的吧,我在arm架构下跑的结果发现两端程序效率是一样的

    是缓存机制不同的原因吗

    向lz请教一下~~~~~~~~~~~~~~~~~

  128. 老赵
    admin
    链接

    老赵 2010-05-27 12:34:08

    @thinkbig

    我不清楚,我这个内容应该和具体的硬件架构无关。可惜我现在也没条件尝试你说的这个,呵呵。

发表回复

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

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

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

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

使用Live Messenger联系我