语言如何影响CPU设计?[关闭]


44

人们经常被告知,硬件并不在乎用哪种语言编写程序,因为它只能看到编译后的二进制代码,但这并不是全部。例如,考虑一下不起眼的Z80;它对8080指令集的扩展包括CPIR之类的指令,可用于扫描C样式(以NULL结尾的)字符串,例如执行strlen()。设计人员必须已经确定,运行C程序(与Pascal相反,在Pascal中,字符串的长度位于标头中)是他们的设计可能会使用的东西。另一个经典的例子是Lisp Machine

还有什么其他例子?例如,使特定处理器偏爱特定语言约定的指令,寄存器的数量和类型,寻址模式?我对同一家庭的修订特别感兴趣。


3
不要忘记Z-80还具有LDIR指令,当您知道长度时(例如在Pascal中,长度存储在标头中),在复制字符串时非常有用。
TMN 2012年

27
Z-80的设计始于1975年,当时Unix和C在几台计算机上都是晦涩的操作系统和语言,比K&R第一版早了三年。2.关于Pascal,没有什么要求字符串长度必须在“标头中”。3. CP / M(当时的主要微型计算机操作系统)中的字符串以“ $”字符而不是“ \ 0”终止。CPIR可以搜索任何字符。4. CPIR与CPDR(向后搜索)以及其他-IR和-DR指令匹配。结论:CPIR与C编程语言无关。这只是一个字节搜索指令。
librik

4
C强制执行的最大的操作(也是硬件设计人员最讨厌的操作之一)是字节寻址。如果没有这种可憎的话,CPU将会更简单,更快。
SK-logic

1
@ SK-logic:尽管POSIX标准要求字节寻址,但C标准则不需要。任何sizeof(int)等于1的实现都必须要求对该类型char进行签名(因为an int必须能够保存type的所有值char)。我已经为一台机器编写了代码,其中charint都是16位带符号整数;最大的困难是不能使用并集进行类型转换,而有效存储大量字节需要手动打包和拆包。这些问题与C中sizeof(int)== sizeof(long)的可能性相比是次要的,因为...
supercat

2
...这意味着没有保证保留两个unsigned int值之差的标准类型。C99改善了这种情况,但是在C99之前,还没有保证安全的单步方法将潜在的负值与类型值进行比较unsigned int(在进行比较之前,必须测试该数字是否为负)。
2012年

Answers:


20

现有答案集中在ISA更改上。硬件也有其他变化。例如,C ++通常使用vtable进行虚拟调用。从Pentium M开始,英特尔拥有一个“间接分支预测器”组件,该组件可以加速虚拟函数调用。


6
Berkeley RISC体系结构包含“寄存器文件”的概念,因此,不是将函数“溢出”寄存器放到堆栈上,而是为每个函数分配了8个寄存器块。这会大大加快面向对象的代码的速度,因为它往往包含对短方法的许多方法调用。
TMN

1
这不是有效的示例。“函数指针表”设计用于许多动态链接方案中,例如,通过Windows上的DLL导入和导出,也用于C程序。尽管我猜您可能会争辩说它确实表明处理器已针对特定用途进行了优化,但它不是特定于语言的。
DeadMG

@DeadMG:其他案例受益,是的。但是直到C ++流行起来,CPU设计才受到影响。这就是提出的问题。同样,TMN确实有一点关于寄存器文件的知识。装配没有这么清晰的功能概念。我们今天通常理解的功能可以追溯到Algol 60,因此可以说Algol 60影响了CPU寄存器文件的设计。
MSalters

14

英特尔8086指令集包括“ ret”的变体,该变体弹出返回地址后将值添加到堆栈指针。这对于许多Pascal实现非常有用,在该实现中,函数的调用者会在进行函数调用之前将参数推入堆栈,然后将其弹出。如果例程接受例如价值四个字节的参数,则该例程可以以“ RET 0004”结尾以清理堆栈。如果没有这样的指令,则这样的调用约定很可能需要代码将返回地址弹出到寄存器,更新堆栈指针,然后跳转到该寄存器。

有趣的是,尽管在68000中缺少便利指令,原始Macintosh上的大多数代码(包括OS例程)仍使用Pascal调用约定。使用该调用约定在典型的调用站点上可以节省2-4个字节的代码,但需要额外的代码每个带有参数的函数的返回站点的4-6字节代码。


还有ENTER与此相对的RET n……
Herby 2012年

1
@herby:我不认为ENTER原来的8086存在;它带有后来的处理器。但是,它的确提出了一个有趣的观点:基于BP的寻址模式显然是围绕使用堆栈参数和通过帧指针访问的局部变量来设计的。我发现该约定有多种有趣的方式,尤其是考虑到(1)纯汇编语言代码比栈更易于使用寄存器中的值,但是(2)[BP + nn]相对于[SP +]寻址的优势nn]对于访问堆栈中内容的汇编语言程序而言,寻址比...更具意义
supercat

...用于手写的汇编代码。对于每个生成的指令,编译器通常会知道SP和BP的比较方式。例如,如果SP是BP-8,那么编译器处理[BP + 12]并不比[SP + 20]容易。如果在重新编译时编译器必须在代码块周围添加另一个PUSH / POP,则它可以适当地调整基于SP的偏移量。另一方面,在手写汇编中,添加PUSH / POP更可能需要调整它们之间的代码。因此,帧指针主要是组合高级/ asm代码的好处。
2012年

也许无需重新编译就可以重用代码,这对于BP解决也有一定的可用性。而且上帝知道BP寻址指令在电路上是否不比SP寻址指令快,因为BP寻址是一种标准...
Herby 2012年

3
@herby:实际上,我怀疑编译器通常使用帧指针的很大一部分原因与调试有关。要调试不使用此类约定的程序,将要求编译器生成并使用调试器,该文件列出了每条指令的SP-BP偏移量。这种详细的元数据在当今很普遍(并且是使垃圾收集的语言成为现实的重要组成部分),但是30年前所需的RAM数量是无法接受的。
2012年

10

一个示例是MIPS,它同时具有addaddu来捕获和忽略溢出。(也subsubu)。它首先需要的类型的指令像Ada的语言(我认为-我从来没有实际使用阿达虽然)涉及明确溢出,而第二类为像C无视溢出的语言。

如果我没记错的话,实际的CPU在ALU中有一些额外的电路来跟踪溢出。如果人们唯一关心的语言是C,则不需要此语言。


不确定是否相关,但是这些指令在其他情况下也可能有用,例如安全的内存分配,即如果您正在分配nmemb*size+offset字节并需要确保不会溢出。
NikiC 2012年

@NikC:我在想addusubu指令(检查溢出的指令)是为了使C高兴而添加的指令。当然,我真的不知道,我们只是在演讲中模糊地介绍了这一点,而且我当然不是建筑专家:P。
迪洪·杰维斯

哦,是的,我在想另一种方法,对不起:/
NikiC 2012年

8

Burroughs 5000系列旨在有效支持ALGOL,而英特尔iAPX-432旨在高效执行Ada。Inmos Transputer拥有自己的语言Occam。我认为Parallax“螺旋桨”处理器的设计旨在使用其自身的BASIC变体进行编程。

它不是语言,但是VAX-11指令集只有一条指令来加载流程上下文,该指令是根据VMS设计团队的要求而设计的。我不记得详细信息,但是ISTR实施了很多指令,以至于严重限制了他们可以调度的进程数量。


这些设计使它们特别适合的原因是什么?例如,Ada特别受益于iAPX的哪些功能?
盖乌斯2012年

ISTR认为iAPX-432的Ada目标更多地是通过将失败的设计附加到对它抱有很高期望的事物上来,从而比其他任何事情都更能挽救失败的设计。
AProgrammer

@AProgrammer:我很确定iAPX-432是从一开始就使用Ada设计的。我什至还记得一些谣言,说英特尔不会发布指令集,以阻止汇编语言编程,并迫使人们使用Ada进行所有操作。
TMN 2012年

1
@TMN,英特尔的432项目始于1975年,并于1981年引入(维基百科)。Ironman(对Ada的最终要求)于1977年1月发布,1979年5月选择绿色,并经过修改,最终结果于1980年7月作为军事标准发布。在时间表方面存在问题,指出iAPX-432是从开始使用Ada。(这是一种较晚且典型的“缩小语义鸿沟”处理器,在开始寻找替代品时具有通常的缺陷;将其作为Ada处理器进行销售是一种尝试来保存失败的设计-ISTR,除英特尔外没有人使用它) )
AProgrammer 2012年

1
@AProgrammer:嗯,看起来你是对的。我从432的首席架构师那里浏览了这篇论文,他在总结中说:“架构和语言的这种紧密匹配并没有发生,因为432被设计为执行Ada,而没有。” 我必须掏出旧的432本书,看看书中所说的是什么。
TMN

8

到目前为止,似乎没有人提到的一件事是,编译器优化的进步(在很大程度上与基本语言无关)推动了从CISC指令集(主要是由人编写的代码)向RISC指令集(大部分是设计供编译器编码。)


5

摩托罗拉68000系列引入了一些自动递增地址模式,使通过cpu复制数据非常高效和紧凑。

[更新示例]

这是一些影响68000汇编程序的C ++代码

while(someCondition)
    destination[destinationOffset++] = source[sourceOffset++]

在常规汇编程序中实现(伪代码,我忘记了68000汇编程序命令)

adressRegister1 = source
adressRegister2 = destination
while(someCondition) {
    move akku,(adressRegister1)
    move (adressRegister2), akku
    increment(adressRegister1, 1)
    increment(adressRegister2, 1)
}

有了新的地址模式,它变成了类似的东西

adressRegister1 = source
adressRegister2 = destination
while(someCondition) {
    move akku,(adressRegister1++)
    move (adressRegister2++), akku
}

每个循环只有两个指令,而不是4。


1
这如何受特定语言约定的影响?
盖乌斯2012年

查看更新的示例
k3b

嗯,让我想起了68010中的DBxx循环优化。–
Gaius

7
实际上,我认为您对此很反感。自动[中|德] crement处理是在PDP-11指令集,这可能会影响C的设计的一部分
TMN

5

IBM的Z系列大型机是1960年代IBM 360的后代。

有一些专门用于加速COBOL和Fortran程序的指令。经典示例是BXLE–“索引低或相等的分支”,它是大多数Fortran for循环或PERFORM VARYING x from 1 by 1 until x > n封装在单个指令中的COBOL 。

还有一整套打包的十进制指令来支持COBOL程序中常见的定点十进制算术。


我想你的意思是后代
Clockwork-Muse

@ X-Zero-哎呀!清晨,没有足够的caffiene系统等.......
詹姆斯-安德森

1
更有趣的是TI 32050 DSP的块重复指令。它的操作数是循环中最后一条指令之后的指令的地址。装入循环计数寄存器然后执行块重复指令,将导致直到(但不包括)目标的指令重复指定的次数。非常强烈地让人联想到FORTRAN DO循环。
2012年

@supercat每个值得称呼的DSP都包含三个功能:零开销循环,单指令乘法累加和某种位反转寻址模式。Man几乎每种DSP算法都使用循环。两种最常见的算法是FIR滤波器和FFT,对于这些算法而言,位反转寻址至关重要,它是围绕乘法累加的循环。许多DSP都包含单指令基数2的FFT蝶形运算,或可用于制作单指令蝶形的双乘法/加法运算。
约翰·斯特罗姆

@ JohnR.Strohm:我见过的每个DSP都包含一个重复乘累加,但是并不是所有的DSP都包含更通用的零开销循环。实际上,我不确定为什么将这样的循环仅视为“ DSP”功能,因为它们在许多“常规处理器”代码中也很有用。
2012年

3

早期的Intel CPU具有以下功能,其中许多现在已在64位模式下废弃:

  • ENTER,LEAVE和RET nn指令[早期手册明确告诉了那些针对块结构语言(例如,支持嵌套过程的Pascal)而引入的手册]
  • 加快BCD运算速度的说明(AAA,AAM等);在x87中也支持BCD
  • JCXZ和LOOP指令,用于实现计数循环
  • INTO,用于在算术溢出时(例如,在Ada中)生成陷阱
  • XLAT用于表查找
  • BOUND用于检查数组范围

存在于许多CPU的状态寄存器中的符号标志,可以轻松执行有符号和无符号算术运算。

SSE 4.1指令集介绍了用于计数和零终止(PCMPESTR等)的字符串处理指令。

另外,我可以想象有许多系统级功能旨在支持已编译代码的安全性(段限制检查,带有参数复制的调用门等)。


3

一些ARM处理器,主要是移动设备中的处理器,包括:(d)Jazelle扩展,它是硬件JVM解释器;它直接解释Java字节码。支持Jazelle的JVM可以使用硬件来加快执行速度并消除大量的JIT,但是如果无法在芯片上解释字节码,仍然可以确保回退到软件VM。

具有这种单元的处理器包括BXJ指令,该指令将处理器置于特殊的“ Jazelle模式”,或者,如果激活该单元失败,则将其解释为正常的分支指令。该单元重用ARM寄存器来保持JVM状态。

Jazelle技术的后继产品是ThumbEE


2

据我所知,这在过去更为普遍。

问题的会话中,詹姆斯·高斯林说,总有人试图使可能与JVM字节码更好的处理硬件,但随后这些人会找到出路与普通“通用”英特尔的x86做(也许编译字节码)。

他提到使用通用的通用芯片(例如intel的芯片)有优势,因为它有一家大公司向该产品投入巨额资金。

该视频值得一看。他在第19或20分钟谈论此事。



2

英特尔iAPX CPU专为面向对象语言设计的。不过,还没有完全解决。

所述iAPX 432英特尔高级处理器架构)是英特尔的第一个32位微处理器的设计,在1981年引入的一组三个集成电路。它原本是1980年代英特尔的主要设计,它实现了许多高级的多任务处理和内存管理功能。因此,该设计被称为微型计算机

iAPX 432被“设计为完全使用高级语言编程”,其中Ada是主要语言,它直接以硬件和微码支持面向对象的编程垃圾回收。对各种数据结构的直接支持还旨在允许使用比普通处理器少得多的程序代码来实现iAPX 432的现代操作系统。这些特性和功能导致了硬件和微代码设计,其设计要比那个时代的大多数处理器(尤其是微处理器)复杂得多。

使用当今的半导体技术,英特尔的工程师无法将设计转化为非常有效的第一个实施方案。加上过早的Ada编译器缺乏优化,这导致了相当慢但昂贵的计算机系统,在相同的时钟频率下(1982年初)以典型新基准的1/4的速度执行了大约80倍新芯片的典型基准测试。

最初的性能差距与相当低调且价格低廉的8086系列产品有关,这可能是英特尔计划用iAPX 432代替后者(后来称为x86)的计划失败的主要原因。尽管工程师们看到了改进下一代设计的方法,但现在iAPX 432 功能体系结构已开始更多地被视为实现开销,而不是预期的简化支持。

iAPX 432项目是英特尔的商业失败。


阅读本文,听起来设计的许多方面在当今流行的面向对象框架中可能很有用。一种将32位对象ID和32位偏移量结合使用的体系结构在许多情况下比对象ID均为64位的大多数情况下提供的缓存性能更好(大多数情况下,使用数十亿个对象的应用程序会可以更好地通过代替具有多个被服务,较大的;其中一个将存储数十亿字节的一个对象将得到更好的服务该细分成更小的对象。
supercat

1

68000具有MOVEM,它最适合于在一条指令中将多个寄存器推入堆栈,这是许多语言所期望的。

如果在整个代码中都看到在JSR(跳转子例程)之前出现了MOVEM(MOVE Multiple),那么您通常就知道您正在处理C兼容代码。

MOVEM允许目标寄存器的自动递增,从而允许每次使用继续在目标上堆叠,或者在自动递减的情况下从堆栈中删除。

http://68k.hax.com/MOVEM


1

Atmel的AVR架构完全是从头开始设计的,适合于用C进行编程。例如,本应用笔记进一步详细说明。

IMO认为这与robots4kids的出色回答密切相关,早期的PIC16-s是为直接汇编程序编程而开发的(共40条指令),后来的系列针对C。


1

在设计8087数字协处理器时,对于语言来说,使用最高精度类型执行所有浮点数学运算是很常见的,并且在将结果分配给较低精度变量时仅将结果舍入为较低精度。例如,在原始的C标准中,序列:

float a = 16777216, b = 0.125, c = -16777216;
float d = a+b+c;

将促进abdouble,添加它们,推进cdouble,添加它,然后存储结果四舍五入到float。即使在许多情况下,编译器生成可以直接对type执行操作的代码会更快float,但拥有一组仅对type操作的浮点例程和将double其转换为/的例程更为简单。从float,而不是有独立的组例程来处理业务floatdouble。8087是围绕该算术方法设计的,使用80位浮点类型来执行所有所有算术运算[可能选择80位是因为:

  1. 在许多16位和32位处理器上,使用64位尾数和单独的指数要比使用值在尾数和指数之间划分一个字节的值更快。

  2. 要进行精确到一个人使用的数值类型的精确度的计算是非常困难的。如果要尝试例如计算log10(x)之类的东西,则计算精确到80位类型的100ulp之内的结果要比计算精确到64位1ulp范围内的结果更容易和更快。类型,并将前一个结果四舍五入为64位精度将产生一个比后者更准确的64位值。

不幸的是,该语言的未来版本改变了浮点类型应如何工作的语义。如果语言始终如一地支持8087语义,如果函数f1(),f2()等返回type的话float,本来很好,但是许多编译器作者还是会自己long double为64位double类型做一个别名而不是编译器的80位类型(并且不提供其他创建80位变量的方法),而是任意求值,例如:

double f = f1()*f2() - f3()*f4();

以下列任何一种方式:

double f = (float)(f1()*f2()) - (extended_double)f3()*f4();
double f = (extended_double)f1()*f2() - (float)(f3()*f4());
double f = (float)(f1()*f2()) - (float)(f3()*f4());
double f = (extended_double)f1()*f2() - (extended_double)f3()*f4();

请注意,如果f3和f4分别返回与f1和f2相同的值,则原始表达式显然应该返回零,但是后面的许多表达式可能不会。这导致人们谴责8087的“超精密度”,尽管最后一种形式通常会优于第三种形式,并且-适当地使用扩展双精度类型的代码-很少会逊色。

在最近的几年中,英特尔通过响应语言的(不幸的恕我直言)趋势,通过设计后期处理器以偏向于将结果取整为操作数的精度,从而有利于这种行为,从而损害了代码的利益,而使用较高的代码将使代码受益中间计算的精度。


请注意,您已经在这篇文章中找到了答案(在上方)。他们是否可以/应该合并为一个答案?

@MichaelT:我不这么认为-一个涵盖了堆栈设计,另一个涵盖了浮点语义。
2014年

只是确定一下。我个人认为,可以做出一个更强的答案(使用标题将各部分分开),但这就是我的看法。您可能仍希望使用标题清楚地在顶部标识每个答案部分的内容(## How the stack changed the processor## How floating point changed the processor),以便人们在阅读时可以理解自己的正确思想,而不太可能认为您在回答或重新发布主题时心不在a相同(类似)答案。

@MichaelT:这两个答案是完全脱节的,我认为应该分别对它们进行表决。尽管80486吸收了8087/80287/80387以前执行的功能,但8086和8087被设计为具有几乎独立架构的独立芯片。尽管两者都是从公共指令流中运行的代码,但这是通过让8086将某些字节序列作为请求来生成地址读/写请求的,而忽略了数据总线,而让8087忽略了其他所有事情来进行处理。
2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.