加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

实时编程C性能困境

发布时间:2020-12-16 10:11:23 所属栏目:百科 来源:网络整理
导读:我正在开发一个ASM占主导地位的嵌入式架构.我想在C中重构大部分遗留ASM代码,以提高可读性和模块性. 所以我仍然迷惑于细微的细节,导致我的希望消失.以下示例中的真正问题要复杂得多,但我想将此作为讨论的切入点. 我的目标是找到最佳的解决方法. 这是最初的例
我正在开发一个ASM占主导地位的嵌入式架构.我想在C中重构大部分遗留ASM代码,以提高可读性和模块性.

所以我仍然迷惑于细微的细节,导致我的希望消失.以下示例中的真正问题要复杂得多,但我想将此作为讨论的切入点.

我的目标是找到最佳的解决方法.

这是最初的例子(不要担心代码的作用.我随机写这个只是为了表明我想谈的问题).

int foo;
int bar;
int tmp;
int sum;

void do_something() {
    tmp = bar;
    bar = foo + bar;
    foo = foo + tmp;
}

void compute_sum() {
    for(tmp = 1; tmp < 3; tmp++)
        sum += foo * sum + bar * sum;
}

void a_function() {
    compute_sum();
    do_something();
}

有了这个虚拟代码,任何人都会立即删除所有全局变量并用本地变量替换它们:

void do_something(int *a,int *b) {
    int tmp = *b;
    *b = *a + *b;
    *b = tmp + *a;
}

void compute_sum(int *sum,int foo,int bar) {
    int tmp;
    for(tmp = 1; tmp < 3; tmp++)
        sum += *foo * sum + *bar * sum;
}

void a_function(int *sum,int *foo,int *bar) {
    compute_sum(sum,foo,bar);
    do_something(foo,bar);
}

不幸的是,这种返工比原始代码更糟糕,因为所有参数都被推入堆栈,这导致了延迟和更大的代码大小.

一切全局解决方案都是最好的解决方案.特别是当源代码大约300k行,具有近3000个全局变量时.

在这里,我们不是面临编译问题,而是结构问题.编写漂亮,可移植,可读,模块化和健壮的代码永远不会通过最终的性能测试,因为编译器是愚蠢的,即使是2015年.

另一种解决方案是更喜欢内联函数.不幸的是,这些函数必须位于头文件中,这也是丑陋的.

编译器无法进一步查看它正在处理的文件.当一个函数被标记为extern时,它将不可逆转地导致性能问题.原因是编译器无法对外部声明做出任何假设.

另一方面,链接器可以完成这项工作,并要求编译器通过givin additionalnal信息重建对象文件到编译器.遗憾的是,没有多少编译器提供这样的功能,当它们这样做时,它们会大大减慢构建过程.

我最终遇到了这个困境:

>保持代码丑陋以保持表现

>一切都是全球性的
>无参数的功能(与程序相同)
>将所有内容保存在同一个文件中

>遵循标准并编写干净的代码

>想想模块
>使用定义明确的参数编写小而多的函数
>编写小而多的源文件

当目标体系结构具有有限的资源时该怎么做.回到大会是我的最后选择.

附加信息

我正在开发一个非常强大的哈佛CISC架构的SHARC架构.不幸的是,一个代码指令需要48位,而一个长只需要32位.有了这个事实,最好保持变量的版本,而不是动态评估第二个值:

优化的例子:

int foo;
int bar;
int half_foo;

void example_a() {
   write(foo); 
   write(half_foo + bar);
}

糟糕的一个:

void example_a(int foo,int bar) {
   write(foo); 
   write(bar + (foo >> 1));
}

解决方法

我习惯于在性能关键的核心/内核类型领域工作,需求非常紧张,通常有利于接受优化程序和标准库性能,但有一些盐(例如:对malloc的速度不太兴奋或者自动生成的矢量化).

但是,我从来没有过这么紧迫的需求,以便使指令的数量或推动更多参数的速度成为一个相当大的问题.如果它确实是目标系统和性能测试失败的一个主要问题,那么需要注意的一点是,在微观粒度级别建模的性能测试通常会让您着迷于最小的微效率.

微效率性能测试

我们错误地在以前的工作场所编写了各种表面的微观级别测试,我在那里进行测试,只是简单地计算一些基本的东西,比如从文件中读取一个32位浮点数.同时,我们进行了优化,大大加快了与读取和解析整个文件内容相关的广泛,真实世界的测试用例,同时,一些超级微测试实际上因某些未知的原因而变慢(他们甚至没有直接修改过,但是围绕它们的代码的更改可能会产生一些与动态因素相关的间接影响,如缓存,分页等,或仅仅是优化器如何处理这样的代码).

因此,当您使用高级语言而不是汇编语言时,微观层面的世界可能会变得更加混乱.很少有东西可以在你脚下移动,但是你必须问自己什么更重要:从文件中读取一个32位浮点数的性能略有下降,或者从实际读取的实际操作整个文件变得更快.在更高级别对性能测试和性能分析会话建模将为您提供选择性和高效优化真正重要的部分的空间.在那里,你有许多方法可以给猫皮肤.

在一个超级粒度的操作上运行一个分析器,重复执行了一百万次,你本来就已经支持自己进入一个程序集类型的微角,只是根据你如何分析代码的性质来执行这些微观级别的测试.所以你真的想在那里缩小一点,在更粗糙的水平上测试一些东西,这样你就可以像一个训练有素的狙击手一样,磨练非常精选的部分的微观效率,派遣领导者效率低下而不是试图成为一个英雄取出可能是性能障碍的每一个微不足道的步兵.

优化链接器

您的一个误解是只有编译器才能充当优化器.链接目标文件时,链接器可以执行各种优化,包括内联代码.因此,如果有的话,应该很少需要将所有内容都作为优化插入到单个目标文件中.如果您发现其他情况,我会尝试更多地查看链接器的设置.

界面设计

抛开这些东西,可维护的大规模代码库的关键在于接口(即头文件)而不是实现(源文件).如果你的汽车发动机每小时行驶一千英里,你可能会在引擎盖下找到并发现几乎没有喷火的恶魔在周围跳舞以允许这种情况发生.也许有一个与魔鬼有关的协议来获得这样的速度.但是你不必向驾驶汽车的人揭露这一事实.你仍然可以给他们一套很好的直观,安全的控制来驱动那个野兽.

所以你可能有一个系统让非内联函数调用“昂贵”,但相对于什么昂贵?如果你正在调用一个对一百万个元素进行排序的函数,那么无论你正在处理什么样的硬件,将指针和整数等几个小参数推送到堆栈的相对成本应该是绝对微不足道的.在函数内部,你可以做各种各样的探查器辅助的东西,以提高性能,如宏,强制内联代码,无论什么,甚至一些内联汇编,但保持代码在整个系统中级联复杂的关键是保持所有恶魔代码都隐藏在使用你的排序功能的人身上,并确保它经过充分测试,以便人们不必经常试图找出故障源.

忽略那个“相对于什么?”问题,只关注绝对性也是导致微观剖析的原因,这种微观剖析可能会产生适得其反的效果.

因此,我建议从公共界面设计层面进一步研究这个问题,因为在界面后面,如果你看到幕后/幕后,你可能会发现各种各样的邪恶事物在表现中获得所需的优势剖析器中显示的热点区域.但是,如果您的接口设计良好且经过充分测试,则不需要经常使用引擎盖.

全球范围越大,全球化就越大.如果你有一个静态定义的全局变量,在源文件中内部链接没有其他人可以访问,那么这些实际上是相当“本地”的全局变量.如果线程安全不是问题(如果是,那么你应该尽可能地避免使用可变的全局变量),那么你的代码库中可能有许多性能关键区域,如果你在引擎盖下找到它们,你会发现文件范围 – 静态变量很多,以减轻函数调用的开销.这仍然比装配更容易维护,特别是当这些全局变量的可见性随着越来越小的源文件的减少而变得越来越小,这些源文件专用于执行更加单一,明确的职责.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读