LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 2488|回复: 8

[原理]编译器如何为不同的处理器都做出最优二元码--CPUID的秘密

[复制链接]
发表于 2005-5-12 10:58:50 | 显示全部楼层 |阅读模式
by echofrompat@linuxsir
转载请勿删除上行。

前几天大家热呼呼地说到如何能知道自己的CPU是32位的还是64位的,小弟专门看了看有关CPUID的东东,在这里给大家参考:

x86的指令集现已成为工业标准,Intel的Pentium系列和AMD的Opteron等的芯片都支持这些指令集。你可能发现,其它的CPU,如VIA的C3也是支持x86指令集的。编译器,如gcc,都可以默认使用x86指令集生成可执行的二元码。这种基于x86最简指令集的二元码都可以在x86兼容的Intel,AMD或是VIA的CPU上运行。

我们知道,Pentium有的MMX和SSE这样的扩展指令器,AMD有3D!NOW,新发布的x86_64 CPU,如AMD64和EM64t,它们都在x86的指令上做了一定的扩充。问题是,如果我们使用的是一个比486更好的CPU,但跑在其上的二元码只使用486的指令器,显然不可能做到运行速度上的最优化。另外,兼容的64位机器上运行32位的二元码,在扩展了指令集的同时还使用到了新的寄存器,问题就更复杂了一些。现在,我们的目的是要让编译器在生成二元码时使用CPU的扩展功能,但不同厂家的CPU有不同的扩展指令集,二元码在使用的特定的扩展指令集后容易在不同的CPU上产生运行期的错误。这就引出了二元码可移植性的问题。

所以我们下载Linux下的rpm包时,有i386,i586,i686之分。i386只使用x86基本指令集,i586对Pentium及与其兼容的CPU做了指令集的优化,i686对Pentium Pro及与其兼容的CPU做了指令集的优化。

那么,编译器是如何知道CPU的特性,并能针对不同的CPU,生成最优的二元码呢?秘密就在于CPUID。

CPUID (central processing unit indentification)其实也是一个CPU的指令。它让OS和程序可以检测是否与CPU兼容,并根据不同的CPU,选择正确的执行路径(execution paths),动态库。

对于EAX寄存器里定义的不同的输入参数,CPUID可以两组信息。如果EAX里存放的是0,CPUID就把生产商的信息放入EBX,ECX,和EDX三个寄存器中。如果EAX里的是1,则这三个寄存器里放入的是处理器特性的标识。

处理器生产商的信息很好说。不外于Intel, AMD, 和VIA。它们都喜欢在这里做广告,如Intel的CPU会返回"GenuineIntel"(真诚到永远!),AMD的CPU会返回"AuthenticAMD"(值得信赖!)。

而对于用户和开发者,更重要的是处理器的功能上的特性。这些特性在EAX寄存器为1时,由CPUID指令将这些处理器特性的标识加载到EBX,ECX和EDX寄存器中。如,EDX中的bit 23指的是64位的MMX指令集,bit 25为SSE,26为SSE2。这里附上AMD64 CPU的程序指南以供大家参考:
http://www.amd.com/us-en/Process ... 82_739_7044,00.html


真相大白! gcc就是使用CPUID来检测CPU类型的, /proc/cpuinfo也是如此。Intel的编译器,icc,甚至可以生成统一的二元码,在执行器检测CPU类型,并选择优化过的程序段给于执行。

下面的是一个具体的检查CPU信息的实现:

  1. /* small utility to extract CPU information
  2. Used by configure to set CPU optimization levels on some operating
  3. systems where /proc/cpuinfo is non-existent or unreliable. */

  4. #include <stdio.h>
  5. #include <sys/time.h>

  6. #ifdef __MINGW32__
  7. #include <sys/timeb.h>
  8. void gettimeofday(struct timeval* t,void* timezone)
  9. {       struct timeb timebuffer;
  10.         ftime( &timebuffer );
  11.         t->tv_sec=timebuffer.time;
  12.         t->tv_usec=1000*timebuffer.millitm;
  13. }
  14. #define MISSING_USLEEP
  15. #define sleep(t) _sleep(1000*t);
  16. #endif

  17. #ifdef __BEOS__
  18. #define usleep(t) snooze(t)
  19. #endif

  20. #ifdef M_UNIX
  21. typedef long long int64_t;
  22. #define        MISSING_USLEEP
  23. #else
  24. #include <inttypes.h>
  25. #endif


  26. typedef struct cpuid_regs {
  27.         unsigned int eax;
  28.         unsigned int ebx;
  29.         unsigned int ecx;
  30.         unsigned int edx;
  31. } cpuid_regs_t;

  32. static cpuid_regs_t
  33. cpuid(int func) {
  34.         cpuid_regs_t regs;
  35. #define        CPUID        ".byte 0x0f, 0xa2; "
  36.         asm("push %%ebx; "
  37.             "movl %4,%%eax; " CPUID
  38.             "movl %%eax,%0; movl %%ebx,%1; movl %%ecx,%2; movl %%edx,%3; "
  39.             "pop %%ebx"
  40.                 : "=m" (regs.eax), "=m" (regs.ebx), "=m" (regs.ecx), "=m" (regs.edx)
  41.                 : "g" (func)
  42.                 : "%eax", "%ecx", "%edx");
  43.         return regs;
  44. }


  45. static int64_t
  46. rdtsc(void)
  47. {
  48.         unsigned int i, j;
  49. #define        RDTSC        ".byte 0x0f, 0x31; "
  50.         asm(RDTSC : "=a"(i), "=d"(j) : );
  51.         return ((int64_t)j<<32) + (int64_t)i;
  52. }


  53. static void
  54. store32(char *d, unsigned int v)
  55. {
  56.         d[0] =  v        & 0xff;
  57.         d[1] = (v >>  8) & 0xff;
  58.         d[2] = (v >> 16) & 0xff;
  59.         d[3] = (v >> 24) & 0xff;
  60. }


  61. int
  62. main(int argc, char **argv)
  63. {
  64.         cpuid_regs_t regs, regs_ext;
  65.         char idstr[13];
  66.         unsigned max_cpuid;
  67.         unsigned max_ext_cpuid;
  68.         unsigned int amd_flags;
  69.         char *model_name = "Unknown CPU";
  70.         int i;
  71.         char processor_name[49];

  72.         regs = cpuid(0);
  73.         max_cpuid = regs.eax;
  74.         /* printf("%d CPUID function codes\n", max_cpuid+1); */

  75.         store32(idstr+0, regs.ebx);
  76.         store32(idstr+4, regs.edx);
  77.         store32(idstr+8, regs.ecx);
  78.         idstr[12] = 0;
  79.         printf("vendor_id\t: %s\n", idstr);

  80.         if (strcmp(idstr, "GenuineIntel") == 0)
  81.             model_name = "Unknown Intel CPU";
  82.         else if (strcmp(idstr, "AuthenticAMD") == 0)
  83.             model_name = "Unknown AMD CPU";

  84.         regs_ext = cpuid((1<<31) + 0);
  85.         max_ext_cpuid = regs_ext.eax;
  86.         if (max_ext_cpuid >= (1<<31) + 1) {
  87.             regs_ext = cpuid((1<<31) + 1);
  88.             amd_flags = regs_ext.edx;

  89.             if (max_ext_cpuid >= (1<<31) + 4) {
  90.                 for (i = 2; i <= 4; i++) {
  91.                     regs_ext = cpuid((1<<31) + i);
  92.                     store32(processor_name + (i-2)*16, regs_ext.eax);
  93.                     store32(processor_name + (i-2)*16 + 4, regs_ext.ebx);
  94.                     store32(processor_name + (i-2)*16 + 8, regs_ext.ecx);
  95.                     store32(processor_name + (i-2)*16 + 12, regs_ext.edx);
  96.                 }
  97.                 processor_name[48] = 0;
  98.                 model_name = processor_name;
  99.             }
  100.         } else {
  101.             amd_flags = 0;
  102.         }

  103.         if (max_cpuid >= 1) {
  104.                 static struct {
  105.                         int bit;
  106.                         char *desc;;
  107.                         char *description;
  108.                 } cap[] = {
  109.                         { 0,  "fpu",   "Floating-point unit on-chip" },
  110.                         { 1,  "vme",   "Virtual Mode Enhancements" },
  111.                         { 2,  "de",    "Debugging Extension" },
  112.                         { 3,  "pse",   "Page Size Extension" },
  113.                         { 4,  "tsc",   "Time Stamp Counter" },
  114.                         { 5,  "msr",   "Pentium Processor MSR" },
  115.                         { 6,  "pae",   "Physical Address Extension" },
  116.                         { 7,  "mce",   "Machine Check Exception" },
  117.                         { 8,  "cx8",   "CMPXCHG8B Instruction Supported" },
  118.                         { 9,  "apic",  "On-chip CPIC Hardware Enabled" },
  119.                         { 11, "sep",   "SYSENTER and SYSEXIT" },
  120.                         { 12, "mtrr",  "Memory Type Range Registers" },
  121.                         { 13, "pge",   "PTE Global Bit" },
  122.                         { 14, "mca",   "Machine Check Architecture" },
  123.                         { 15, "cmov",  "Conditional Move/Compare Instruction" },
  124.                         { 16, "pat",   "Page Attribute Table" },
  125.                         { 17, "pse36", "Page Size Extension 36-bit" },
  126.                         { 18, "psn",   "Processor Serial Number" },
  127.                         { 19, "cflsh", "CFLUSH instruction" },
  128.                         { 21, "ds",    "Debug Store" },
  129.                         { 22, "acpi",  "Thermal Monitor and Clock Ctrl" },
  130.                         { 23, "mmx",   "MMX Technology" },
  131.                         { 24, "fxsr",  "FXSAVE/FXRSTOR" },
  132.                         { 25, "sse",   "SSE Extensions" },
  133.                         { 26, "sse2",  "SSE2 Extensions" },
  134.                         { 27, "ss",    "Self Snoop" },
  135.                         { 29, "tm",    "Therm. Monitor" },
  136.                         { -1 }
  137.                 };
  138.                 static struct {
  139.                         int bit;
  140.                         char *desc;;
  141.                         char *description;
  142.                 } cap_amd[] = {
  143.                             { 22, "mmxext","MMX Technology (AMD Extensions)" },
  144.                         { 30, "3dnowext","3Dnow! Extensions" },
  145.                         { 31, "3dnow", "3Dnow!" },
  146.                         { 32, "k6_mtrr", "Memory Type Range Registers" },
  147.                         { -1 }
  148.                 };
  149.                 int i;

  150.                 regs = cpuid(1);
  151.                 printf("cpu family\t: %d\n"
  152.                        "model\t\t: %d\n"
  153.                        "stepping\t: %d\n" ,
  154.                         (regs.eax >>  8) & 0xf,
  155.                         (regs.eax >>  4) & 0xf,
  156.                          regs.eax        & 0xf);
  157.                
  158.                 printf("flags\t\t:");
  159.                 for (i = 0; cap[i].bit >= 0; i++) {
  160.                         if (regs.edx & (1 << cap[i].bit)) {
  161.                                 printf(" %s", cap[i].desc);
  162.                         }
  163.                 }
  164.                 for (i = 0; cap_amd[i].bit >= 0; i++) {
  165.                         if (amd_flags & (1 << cap_amd[i].bit)) {
  166.                                 printf(" %s", cap_amd[i].desc);
  167.                         }
  168.                 }
  169.                 printf("\n");

  170.                 if (regs.edx & (1 << 4)) {
  171.                         int64_t tsc_start, tsc_end;
  172.                         struct timeval tv_start, tv_end;
  173.                         int usec_delay;

  174.                         tsc_start = rdtsc();
  175.                         gettimeofday(&tv_start, NULL);
  176. #ifdef        MISSING_USLEEP
  177.                         sleep(1);
  178. #else
  179.                         usleep(100000);
  180. #endif
  181.                         tsc_end = rdtsc();
  182.                         gettimeofday(&tv_end, NULL);

  183.                         usec_delay = 1000000 * (tv_end.tv_sec - tv_start.tv_sec)
  184.                                 + (tv_end.tv_usec - tv_start.tv_usec);

  185.                         printf("cpu MHz\t\t: %.3f\n",
  186.                                 (double)(tsc_end-tsc_start) / usec_delay);
  187.                 }
  188.         }

  189.         printf("model name\t: %s\n", model_name);

  190.         exit(0);
  191. }
复制代码
发表于 2005-5-12 11:04:36 | 显示全部楼层
多多益善!最好解释一下源代码。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2005-5-12 11:11:40 | 显示全部楼层
原码并不难懂,只是在C里加了些汇编代码。如果大家需要解释其中的一段,请回帖告知。
回复 支持 反对

使用道具 举报

发表于 2005-5-12 14:21:47 | 显示全部楼层
echofrompat 谈到二进码的问题,还提供了代码,大家看过以后应该比较清楚了。

不过 Redhat 声称 Fedora Core 是 P4 优化的,但它在 Athlon、Athlon XP 和 Pentium M 这样的 CPU 上也可以运行,那又是什么意思呢?

其实,象 Athlon XP 和 P4 这样的现代 CPU,为了提高运行速度,内部采用了多发射、乱序执行、超标量、流水线等各种措施。虽然支持的指令集大致相同,但是由于内部的微体系结构设计的差异,执行相同的代码,效能上可能会有相当大的差异。举个例子说,你有两行代码


  1. z = x + y;
  2. p = m + n;
复制代码


CPU 就可能把这两行无关的代码同时执行。再比如,


  1. z = x + y;

  2. if (z > 0)
  3. then z = z + 1;
  4. else z = z - 1;
复制代码


这时候虽然看起来 z 不计算出来是无法继续的,但是 CPU 有可能在内部把 z >0 和 z < 0 两种情况同时执行,等 z = x + y 计算完成以后,根据情况直接挑正确的结果。

做同一件事情的两种方法,可能一种对 P4 有利些,另一种对 AMD64 有利些。所以这就涉及到一个指令调度的概念。有些特殊的程序,用 Intel 自己的 icc 编译器生成的二进制代码,效能可能是 gcc 的好几倍。好在 gcc4 一出,这方面应该会来个大跃进。

而 P4 本身的设计,强调的是跑高频,特点是理论峰值高,但是对优化特别敏感。一不小心就写出效能极差的代码,99% 的程序员又是烂程序员,所以不容易发挥出来。目前由于工艺的原因,Intel 在频率提升上遇到了很大的困难,导致性能落后,发热量巨大,动辄 100 多瓦,十分恐怖。这方面 AMD 的 AMD64 系列做得更加均衡,对编译器要求不高,功耗也小。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2005-5-12 15:11:02 | 显示全部楼层
感谢mopz0506兄的补充,讲的太好了。
机器里,寄存器的存取速度仅次于CPU的运算速度。所以寄存器的多少和它与逻辑运算单元的并行关系也就成为了优化二元码的重点。
SSE是SIMD的扩展,它可以允许CPU在一个指令中同时完成多个运算,如,
    z = x + y;
    p = m + n;
这样的两个加法运算,在支持SSE的CPU中就能用一个指令完成。SSE2还可以将两个乘除法运算用一条指令完成。但如果只用x86的指令集就没有这种好处。
这样,编译器在优化二元码时就会把可以并行的指令并行起来,使它们在运行得更快。

所以,程序在编写的时候,看起来是串行的,但实际经过优化的二元码却是串行的。
P4的优点是超线程做得非常强,它的流水线达到了21条(忘记是21还是23了),这样,寄存器就会频繁的读写,来和逻辑运算器并行。经过icc优化的代码可以很好地利用P4的这一有利优势。
但这又带出了一个新问题:程序中除了并行部分,还有很多其它execution path,也就是在执行期由于循环,判断等产生的跳转。如果编译器处理不当,这种跳转在P4这样超线程极强的CPU上会带来严重的性能影响。因为一个跳转有可能会使超线程中的指令在并行过程中执行的取消,而这些指令都是已装载好寄存器的。它们被取消后,寄存器不得不重新装载,这些就会带来性能问题。
P3和Centrino的超线程流水线只有7条,在这方面可以很好地达到一个平衡点。AMD的CPU在这方面与P3也很相似。
这让我想起了一句话: When something is really good, then it's really bad
回复 支持 反对

使用道具 举报

发表于 2005-5-12 15:51:57 | 显示全部楼层
感觉你说的不大对,不过也没什么大关系啦。

P4 有两代,老的 Northwood 核心和新的 Prescott 核心,同属 Netburst 体系结构。流水线长度有所不同,同一核心的,整数和浮点也不同。

Netburst 采用了大量的先进理念和技术,但实际当中效果很不好。基本上,Intel 现在已经承认 Netburst 没有前途,未来的 CPU 将基于全新架构,这个架构跟 P-M 亲缘关系更近一点。

所以目前买笔记本要买 Intel 的,买桌面电脑应当买 AMD 的,呵呵。好在我有先见之明,用的是 Athlon XP。

喜欢这方面的可以去 http://www.sandpile.org/ 看看,有非常详实的 CPU 资料。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2005-5-12 16:53:29 | 显示全部楼层
嗯,我的经验不足,如有错误,请大家指正,先谢谢了!
回复 支持 反对

使用道具 举报

发表于 2005-5-12 20:51:35 | 显示全部楼层
收藏!感谢 echofrompat 兄弟的辛苦工作(最好将这个帖子转到程序版块下)
回复 支持 反对

使用道具 举报

发表于 2005-5-13 07:03:42 | 显示全部楼层
Post by AMD-K6
收藏!感谢 echofrompat 兄弟的辛苦工作(最好将这个帖子转到程序版块下)

程序版块的帖子应该将精力集中于如何编程。而该贴是讨论linux工作原理的,因此还是放在基础版最为适宜。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表