为Ada Array of Records循环生成了什么代码?

时间:2017-01-19 15:57:05

标签: ada

例如:

type PERSONCV is
   record
      name: String ( 1..4 );
      age: Integer;
      cvtext: String ( 1..2000 );
   end record;

N: constant := 40000;
persons : array ( 1..N ) of PERSONCV;

function jobcandidate return Boolean is
   iscandidate: Boolean := False;
begin
   for p of persons loop   -- what code is generated for this?
      if p.age >= 18 then
         iscandidate := true;
         exit;
      end if;
   end loop;
   return iscandidate;
end;

在C / C ++中,循环部分通常是:

PERSONCV * p; // address pointer
int k = 0;
while ( k < N )
   {
   p = &persons [ k ]; // pointer to k'th record
   if ( p->age >= 18 )...  
   ...
   k++ ;
   }

我读过Ada对记录使用Value语义。 上面的Ada循环是否将第k个记录复制到循环变量p? 例如像这样在C / C ++中:

PERSONCV p; // object/variable
int k = 0;
while ( k < N )
   {
   memcpy ( &p, &persons [ k ], sizeof ( PERSONCV ) ); // copies k'th elem 
   if ( p.age >= 18 )...  
   ...
   k++ ;
   }

3 个答案:

答案 0 :(得分:13)

假设您使用的是GNAT,则有两种调查途径。

交换机-gnatG将重新生成类似Ada的表示,表示编译器的前端将传递到后端(在任何优化之前)。在这种情况下,我看到了

   function resander__jobcandidate return boolean is
      iscandidate : boolean := false;
      L_1 : label
   begin
      L_1 : for C8b in 1 .. 40000 loop
         p : resander__personcv renames resander__persons (C8b);
         if p.age >= 18 then
            iscandidate := true;
            exit;
         end if;
      end loop L_1;
      return iscandidate;
   end resander__jobcandidate;

所以问题是,renames如何翻译?鉴于记录大小是2008字节,编译器生成副本的几率几乎为零。

第二种调查方法是将编译器通常发出的汇编代码保存到汇编器,然后使用开关-S删除。这证实生成的代码就像您的第一个C ++版本(对于macOS)。

作为一个有趣的侧面,Ada 2012允许jobcandidate的替代实现:

   function jobcandidate2 return Boolean is
     (for some p of persons => p.age >= 18);

生成相同的代码。

答案 1 :(得分:9)

我怀疑你读到的有关Ada的内容是错误的,可能更糟糕的是,鼓励你以错误的方式思考Ada。

Ada的目的是鼓励在问题领域进行思考,即指明应该发生什么,而不是在解决方案领域进行思考,即实现具体方法的精细细节。

所以这里的目的是遍历所有人,在第一次超过18时退出返回True,否则返回False。

就是这样。

一般而言,只要满足这些语义,Ada就不会对如何完成的细节作出任何规定。

然后,意图是,你只希望编译器做正确的事。

现在一个单独的编译器可以选择一个实现而不是另一个 - 或者可以根据优化启发式在实现之间切换,考虑它正在编译的CPU,以及对象的大小(它们是否适合寄存器?)等

您可以想象一个具有许多寄存器的CPU,其中单个高速缓存行读取使得复制实现比在适当位置操作更快(特别是如果没有修改以回写到P的内容),或其他目标CPU,其中反向是真正。为什么要阻止编译器选择更好的实现?

这方面的一个很好的例子是Ada将参数传递给子程序的方法 - 名称,值或引用语义实际上不适用 - 而是指定参数传递模式 - inout,或in out描述到子程序(或来自)子程序的信息流。直观,提供可以更严格检查的语义,并使编译器可以自由选择正确服从这些语义的最佳(最快,最小,取决于您的目标)实现。

现在,特定的Ada编译器可能会做出糟糕的选择,而30年前,当计算机根本不足以运行Ada编译器时,您可能会发现在早期版本中,编译器。

但我们现在有三十多年的编译器开发,在更强大的计算机上运行。所以,今天,我希望编译器能够正常地做出最佳选择。如果您发现某个特定编译器错过了性能优化,请提交增强请求。 Ada编译器并不完美,就像任何其他编译器一样。

在这个具体的例子中,我通常希望P成为数组的游标,并且操作就地发生,即引用语义。或者可能是表单之间的混合,其中一个存储器提取到寄存器中用于多个操作,如部分形式的值语义。

如果您的兴趣是学术性的,您可以轻松查看您正在使用的任何编译器的汇编输出并找出答案。或者写上面的所有三个版本并对它们进行基准测试。

答案 2 :(得分:0)

使用当前编译器(GCC 7.0.0),我已使用与std:array<char, 4>等对应的String( 1..4 )等将您的源复制到Ada程序和C ++程序。交换机只是{ {1}}用于C ++,-O2用于Ada,以便使用关于对数组元素的检查访问的类似设置等。

这些是-O2 -gnatp的结果:

C ++:

jobcandidate

阿达:

    movl    $_ZN15Loop_Over_Array7personsE+4, %eax
    movl    $_ZN15Loop_Over_Array7personsE+80320004, %edx
    jmp .L3
.L8:
    addq    $2008, %rax
    cmpq    %rdx, %rax
    je  .L7
.L3:
    cmpl    $17, (%rax)
    jle .L8
    movl    $1, %eax
    ret
.L7:
    xorl    %eax, %eax
    ret

我看到的一个区别在于实现如何使用%edx %eax ;用于将数组的一个元素转换为下一个元素,并测试是否已达到结束。 Ada似乎 movl $1, %eax jmp .L5 .L10: addq $1, %rax cmpq $40001, %rax je .L9 .L5: imulq $2008, %rax, %rdx cmpl $17, loop_over_array__persons-2004(%rdx) jle .L10 movl $1, %eax ret .L9: xorl %eax, %eax ret 设置光标的元素大小,C ++似乎imulq它指向指针。

我还没有衡量表现。