如何全球化2个小型阵列会导致性能显着下降?

时间:2015-09-23 13:15:31

标签: c performance motorola 68000

我有2个小型本地数组:

short int xpLeft [4], xpRight [4];

我将它们设置为全局(以不同的方式访问它们,但只能在同一个C文件中访问它们(例如,其他模块无法访问)),性能(在Motorola 68000上)下降。 而不是224个vblanks(对于本地),整个基准测试(呈现一个场景的320帧)突然需要249个vblanks(全局数组)!

我尝试了什么:
由于数组中的数据没有在该函数中使用,我虽然编译器捕获了这一点并且没有费心将结果值(从寄存器)写入内存(68000上的极慢操作 - 访问内存)。因此,我添加了一些小代码,以便在函数末尾使用这些数组值,并相应地提高了性能成本(仅1 vblank)。

有什么帮助:
我需要检查最终的ASM代码(并比较两个版本),但我不知道如何使用 vbcc编译器(来自Volker博士)。我从文档中尝试了几个开关,虽然它们确实产生了一些中间输出,但是我无法提供它来提供每个模块的完整ASM列表(具有来自C的函数名称)。

我刚接到开关“-k”才能工作。显然,交换机的顺序很重要,我在命令行中找到了一个可以识别它的地方,我终于得到了* .ASM输出(不超过300多行),但我终于有了一些东西(带符号的ASM)挖掘。

我认为发生了什么:

  1. 使数组全局化,将它们放入RAM中的不同地址,并且内存控制器必须访问不同的银行和银行 在目标平台上切换是一个非常慢的操作 - 导致RAS充电周期(访问不同行 地址)。
  2. 指针别名 - 编译器可能会生成不同的代码,并且可能实际访问实际内存以获取中间代码     结果 - 但如果我有每个功能的ASM输出,我可以     轻松搞清楚
  3. 有关为什么会发生这种情况的提示,或者如何使用相应的ASM代码获取每个已编译模块的vbcc输出完整列表?

    使用ASM输出我创建了一个小测试repro案例:

    short int tmpfn1 ()
    {
        short int xpLeft [4], xpRight [4];
        short int i, tmp;
    
        for (i = 0; i < 4; i++)
        {
            xpLeft [i] = 137 + i;
            xpRight [i] = 215 + i;
        }
    
        tmp = xpLeft [0] + xpRight [0];
        return tmp;
    }
    

    这是最终的ASM。尽管ASM非常明显,但我还是添加了一些评论:

        public  _tmpfn1
        cnop    0,4
    _tmpfn1
        sub.w   #16,a7
        movem.l l4150,-(a7)
        moveq   #0,d1
        lea (0+l4152,a7),a1   ; a1 = &xpLeft [0]
        lea (8+l4152,a7),a2   ; a2 = &xpRight [0]
        move.w  #215,d3    ; d2/d3 = The Bulgarian constants 
        move.w  #137,d2
    l4148
        move.w  d1,d0
        ext.l   d0
        lsl.l   #1,d0
        move.w  d2,(0,a1,d0.l)    ; xpLeft [i] = 137 + i;
        move.w  d3,(0,a2,d0.l)    ; xpRight [i] = 215 + i;
        addq.w  #1,d1    ; d1 = Loop Counter (i++)
        addq.w  #1,d2
        addq.w  #1,d3
        cmp.w   #4,d1
        blt l4148    ; Repeat the loop
        move.w  (8+l4152,a7),d0
        add.w   (0+l4152,a7),d0    ; tmp = xpLeft [0] + xpRight [0];
    l4150   reg a2/d2/d3
        movem.l (a7)+,a2/d2/d3
        add.w   #16,a7
    l4152   equ 12
        rts
    ; stacksize=28
        opt 0
        opt NQLPSMRBT
    

    现在,我将把数组从本地转移到全局。

    以下是包含全局变量的代码。

        public  _tmpfn1
        cnop    0,4
    _tmpfn1
        movem.l l4150,-(a7)
        moveq   #0,d1
        move.w  #215,d2
        move.w  #137,d3
    l4148
        move.w  d1,d0
        ext.l   d0
        lsl.l   #1,d0
        lea _AxpLeft,a0
        move.w  d3,(0,a0,d0.l)
        lea _AxpRight,a0
        move.w  d2,(0,a0,d0.l)
        addq.w  #1,d1
        addq.w  #1,d3
        addq.w  #1,d2
        cmp.w   #4,d1
        blt l4148
        move.w  _AxpRight,d0
        add.w   _AxpLeft,d0
    l4150   reg d2/d3
        movem.l (a7)+,d2/d3
    l4152   equ 8
        rts
    ; stacksize=8
        opt 0
        opt NQLPSMRBT
    

    唯一的区别是两个lea指令,如果记忆正确,则最多16个周期 实际函数必定会发生其他事情,但由于某种原因,其代码在ASM中被混淆(ASM中只有6行,没有跳转,没有其他标签,没有)。我将继续搜索ASM,确切地说是代码。

1 个答案:

答案 0 :(得分:0)

我怀疑,有一个与WTF编译器有关的怪物。原因是,为什么在局部变量的情况下只有6行代码,是编译器能够弄清楚,那120行C代码在全局级别上并没有真正做任何事情,所以它完全忽略了代码干脆!这意味着该方法的ASM代码只有6行(带rts)。然而,我得到的基准测试结果并没有多大意义(但这将是一个不同的故事)

故事的道德:通过使变量成为全局变量,编译器实际上打算为函数创建代码(而不仅仅是空的6-op存根)。因为我在该函数内部编写了所有内容,所以没有嵌套函数调用。 现在,这显然是荒谬的,因为我在目标设备上有大约25个调试会话,其中包含变量和输出。但是那一刻,我删除了那些外部打印/调试调用,这一定是编译器根本没有生成功能代码的时刻。 A-血性-MAZING ....