GCC优化技巧,真的有用吗?

     2023-02-17     241

关键词:

【中文标题】GCC优化技巧,真的有用吗?【英文标题】:GCC optimization trick, does it really work? 【发布时间】:2011-10-14 00:03:50 【问题描述】:

在查看一些关于优化的问题时,accepted answer 中关于最有效地使用优化器的编码实践的问题激起了我的好奇心。断言是局部变量应该用于函数中的计算,而不是输出参数。有人建议这将允许编译器进行额外的优化,否则是不可能的。

因此,为示例 Foo 类编写一段简单的代码并使用 g++ v4.4 和 -O2 编译代码片段会得到一些汇编程序输出(使用 -S)。汇编程序清单的部分仅包含如下所示的循环部分。在检查输出时,似乎两者的循环几乎相同,只有一个地址不同。该地址是指向第一个示例的输出参数或第二个示例的局部变量的指针。

无论是否使用局部变量,实际效果似乎都没有变化。所以问题分解为 3 个部分:

a) GCC 没有做额外的优化,即使给出了建议的提示;

b) GCC 成功 在这两种情况下都进行了优化,但不应该这样做;

c) GCC 成功 在这两种情况下都进行了优化,并且产生了符合 C++ 标准定义的输出?

这是未优化的函数:

void DoSomething(const Foo& foo1, const Foo* foo2, int numFoo, Foo& barOut)

    for (int i=0; i<numFoo, i++)
    
         barOut.munge(foo1, foo2[i]);
    

以及对应的组件:

.L3:
    movl    (%esi), %eax
    addl    $1, %ebx
    addl    $4, %esi
    movl    %eax, 8(%esp)
    movl    (%edi), %eax
    movl    %eax, 4(%esp)
    movl    20(%ebp), %eax       ; Note address is that of the output argument
    movl    %eax, (%esp)
    call    _ZN3Foo5mungeES_S_
    cmpl    %ebx, 16(%ebp)
    jg      .L3

这里是重写的函数:

void DoSomethingFaster(const Foo& foo1, const Foo* foo2, int numFoo, Foo& barOut)

    Foo barTemp = barOut;
    for (int i=0; i<numFoo, i++)
    
         barTemp.munge(foo1, foo2[i]);
    
    barOut = barTemp;

这是使用局部变量的函数的编译器输出:

.L3:
    movl    (%esi), %eax          ; Load foo2[i] pointer into EAX
    addl    $1, %ebx              ; increment i
    addl    $4, %esi              ; increment foo2[i] (32-bit system, 8 on 64-bit systems)
    movl    %eax, 8(%esp)         ; PUSH foo2[i] onto stack (careful! from EAX, not ESI)
    movl    (%edi), %eax          ; Load foo1 pointer into EAX
    movl    %eax, 4(%esp)         ; PUSH foo1
    leal    -28(%ebp), %eax       ; Load barTemp pointer into EAX
    movl    %eax, (%esp)          ; PUSH the this pointer for barTemp
    call    _ZN3Foo5mungeES_S_    ; munge()!
    cmpl    %ebx, 16(%ebp)        ; i < numFoo
    jg      .L3                   ; recall incrementing i by one coming into the loop
                                  ; so test if greater

【问题讨论】:

链接到的问题是社区维基,所以如果有人也想做这个,请这样做。我看不出有什么办法。 我们这里说的源码是什么?您能否发布代码并标记代码的哪一部分与您发布的汇编程序摘录相对应? 我已将链接问题中的代码移至此问题中,但如果您使用不同的代码,请替换它。它看起来大致相同,但细节决定一切。 IMO,通过只使用一个编译器和一个平台,你正在徘徊在坏习惯的“黑暗面”。您只提到 GCC,但还有其他编译器,并且有不同的 cpu。如果它们的行为方式与 GCC 不同,那么您的“优化”将浪费时间。我建议坚持使用 C++ 标准并仅在情况需要时执行像这样的低级“优化”(使用分析器检查),但即使那样你也可以使用汇编来强制优化你想要而不是试图预测优化器将对您的代码做什么。 @samoid,代码相同。 【参考方案1】:

该答案中给出的示例不是一个很好的示例,因为调用了一个编译器无法推理的未知函数。这是一个更好的例子:

void FillOneA(int *array, int length, int& startIndex)

    for (int i = 0; i < length; i++) array[startIndex + i] = 1;


void FillOneB(int *array, int length, int& startIndex)

    int localIndex = startIndex;
    for (int i = 0; i < length; i++) array[localIndex + i] = 1;

第一个版本优化很差,因为它需要防止有人将其称为

int array[10] =  0 ;
FillOneA(array, 5, array[1]);

导致1, 1, 0, 1, 1, 1, 0, 0, 0, 0 ,因为使用i=1 的迭代修改了startIndex 参数。

第二个不用担心array[localIndex + i] = 1会修改localIndex的可能性,因为localIndex是一个地址从未被占用过的局部变量。

在汇编中(英特尔表示法,因为这是我使用的):

FillOneA:
    mov     edx, [esp+8]
    xor     eax, eax
    test    edx, edx
    jle     $b
    push    esi
    mov     esi, [esp+16]
    push    edi
    mov     edi, [esp+12]
$a: mov     ecx, [esi]
    add     ecx, eax
    inc     eax
    mov     [edi+ecx*4], 1
    cmp     eax, edx
    jl      $a
    pop     edi
    pop     esi
$b: ret

FillOneB:
    mov     ecx, [esp+8]
    mov     eax, [esp+12]
    mov     edx, [eax]
    test    ecx, ecx
    jle     $a
    mov     eax, [esp+4]
    push    edi
    lea     edi, [eax+edx*4]
    mov     eax, 1
    rep stosd
    pop     edi
$a: ret

添加:以下是编译器洞察 Bar 而不是 munge 的示例:

class Bar

public:
    float getValue() const
    
        return valueBase * boost;
    

private:
    float valueBase;
    float boost;
;

class Foo

public:
    void munge(float adjustment);
;

void Adjust10A(Foo& foo, const Bar& bar)

    for (int i = 0; i < 10; i++)
        foo.munge(bar.getValue());


void Adjust10B(Foo& foo, const Bar& bar)

    Bar localBar = bar;
    for (int i = 0; i < 10; i++)
        foo.munge(localBar.getValue());

生成的代码是

Adjust10A:
    push    ecx
    push    ebx
    mov     ebx, [esp+12] ;; foo
    push    esi
    mov     esi, [esp+20] ;; bar
    push    edi
    mov     edi, 10
$a: fld     [esi+4] ;; bar.valueBase
    push    ecx
    fmul    [esi] ;; valueBase * boost
    mov     ecx, ebx
    fstp    [esp+16]
    fld     [esp+16]
    fstp    [esp]
    call    Foo::munge
    dec     edi
    jne     $a
    pop     edi
    pop     esi
    pop     ebx
    pop     ecx
    ret     0

Adjust10B:
    sub     esp, 8
    mov     ecx, [esp+16] ;; bar
    mov     eax, [ecx] ;; bar.valueBase
    mov     [esp], eax ;; localBar.valueBase
    fld     [esp] ;; localBar.valueBase
    mov     eax, [ecx+4] ;; bar.boost
    mov     [esp+4], eax ;; localBar.boost
    fmul    [esp+4] ;; localBar.getValue()
    push    esi
    push    edi
    mov     edi, [esp+20] ;; foo
    fstp    [esp+24]
    fld     [esp+24] ;; cache localBar.getValue()
    mov     esi, 10 ;; loop counter
$a: push    ecx
    mov     ecx, edi ;; foo
    fstp    [esp] ;; use cached value
    call    Foo::munge
    fld     [esp]
    dec     esi
    jne     $a ;; loop
    pop     edi
    fstp    ST(0)
    pop     esi
    add     esp, 8
    ret     0

注意Adjust10A 中的内部循环必须重新计算值,因为它必须防止foo.munge 更改bar 的可能性。

也就是说,这种优化风格不是灌篮高手。 (例如,我们可以通过手动将 bar.getValue() 缓存到 localValue 中来获得相同的效果。)它往往对矢量化操作最有帮助,因为它们可以并行化。

【讨论】:

你的例子我得到了类似的结果。看来,要使这种优化技巧起作用,编译器必须能够清楚地访问所有正在优化的代码。总之,我的问题的答案是 a),GCC 没有使用优化,并且可能不会使用。 如果缓存到局部变量中的对象适合寄存器,您更有可能看到优化产生的效果。【参考方案2】:

首先,我假设munge() 不能被内联——也就是说,它的定义不在同一个翻译单元中;你没有提供完整的来源,所以我不能完全确定,但它会解释这些结果。

由于foo1 作为引用传递给munge,所以在实现级别,编译器只传递一个指针。如果我们只是转发我们的论点,这很好而且很快——任何别名问题都是munge() 的问题——而且必须如此,因为munge() 不能假设任何关于它的论点,我们也不能假设任何关于munge() 可能对它们做什么(因为 munge() 的定义不可用)。

但是,如果我们复制到一个局部变量,我们必须复制到一个局部变量并传递一个指向该局部变量的指针。这是因为munge() 可以观察到行为上的差异——如果它使用指向其第一个参数的指针,它可以看到它不等于&amp;foo1。由于munge() 的实现不在作用域内,编译器不能假设它不会这样做。

因此,这里的这种局部变量复制技巧最终是悲观的,而不是优化 - 它试图帮助的优化是不可能的,因为 munge() 不能被内联;出于同样的原因,局部变量会严重影响性能。

再试一次会很有启发性,确保munge() 是非虚拟的并且可用作内联函数。

【讨论】:

我还要说,一个可以内联的函数和一个必须将 this 指针传递到的函数(通常是通过堆栈)之间存在一个主要区别。 @inflagranti,即使它不能被内联(例如,由于太大),如果定义可用,编译器可能能够根据其行为进行优化 我的 Foo 实现相当简单,因此优化器在内联时可以使用它。但是,很明显,对于我拥有的代码,优化的代码在 munge() 内联时将结果变量的加载和存储保存在循环中。因此,如果编译器必须调用一个函数而不能内联它,那么给出的优化就没有任何用处。

记忆有啥好处,真的那么有用吗?

】记忆有啥好处,真的那么有用吗?【英文标题】:Whatismemoizationgoodforandisitreallyallthathelpful?记忆有什么好处,真的那么有用吗?【发布时间】:2011-03-1515:07:33【问题描述】:互联网上有一些用于各种不同语言的自动记忆库;但... 查看详情

Pex(测试生成)真的有用吗?

】Pex(测试生成)真的有用吗?【英文标题】:IsPex(Testgeneration)reallyusefultool?【发布时间】:2011-02-1121:03:40【问题描述】:是的,可以为“Sum”或“Divide”等函数生成边界值测试。Pex是一个很好的工具。但更多时候,我们会针对... 查看详情

禁用 GCC 中的所有优化选项

】禁用GCC中的所有优化选项【英文标题】:DisablealloptimizationoptionsinGCC【发布时间】:2016-01-2114:21:09【问题描述】:使用GCC编译C程序的默认优化级别是-O0。根据GCC文档关闭所有优化。例如:gcc-O0test.c但是,要检查-O0是否真的关闭... 查看详情

真的有用吗?(github)

为什么要新建一个GitHub账号 一个程序员不知道GitHub,那我就笑笑,呵呵哒。什么是GitHub呢?就我知道的git,谈一下。Git是一个版本控制软件,这个软件最初是Linux之父林纳斯.托瓦兹弄的。他就弄了两个重要的东西,一个是Linux... 查看详情

为循环优化 JavaScript 真的有必要吗?

】为循环优化JavaScript真的有必要吗?【英文标题】:IsoptimizingJavaScriptforloopsreallynecessary?【发布时间】:2011-10-2121:11:52【问题描述】:我读到notreadingthelengthattributeofanarrayeveryiterationintheloopheader建议优化JavaScript中的循环。所以,我... 查看详情

Flash10 + p2p 真的有用吗?

】Flash10+p2p真的有用吗?【英文标题】:DoesFlash10+p2preallywork?【发布时间】:2010-11-0919:11:36【问题描述】:我一直在谷歌上搜索,但我仍然无法得到它。有人说:给你,就用它。其他人声称它有某些限制,不允许您在Flash中以您想... 查看详情

当我一步编译所有内容时,GCC 可以更好地优化事情吗?

】当我一步编译所有内容时,GCC可以更好地优化事情吗?【英文标题】:CanGCCoptimizethingsbetterwhenIcompileeverythinginonestep?【发布时间】:2012-04-2114:14:23【问题描述】:当我将-O2标志传递给gcc时,gcc会优化代码,但我想知道如果我将... 查看详情

GCC 可以使用编译时常量变量优化类的方法吗?

】GCC可以使用编译时常量变量优化类的方法吗?【英文标题】:CanGCCoptimizemethodsofaclasswithcompile-timeconstantvariables?【发布时间】:2014-03-0216:46:48【问题描述】:序言我正在使用avr-g++对AVR微控制器进行编程,因此我总是需要获得非... 查看详情

gcc 会自动使用 -j4 吗?我可以做些啥来优化我的编译吗?

】gcc会自动使用-j4吗?我可以做些啥来优化我的编译吗?【英文标题】:Doesgccautomaticallyuse-j4?IsthereanythingIcandotooptimizemycompilation?gcc会自动使用-j4吗?我可以做些什么来优化我的编译吗?【发布时间】:2015-11-1921:01:05【问题描述】... 查看详情

今日话题:坚持真的有用吗?

大家好,我是『K同学啊』!我试着将自己每天的工作日常分享出来,希望可以激励到正处于困惑、迷茫状态中的你,也希望你可以加入群聊,分享自己fighting的日常🎯今天完成了更新了《Python入门100题》10... 查看详情

04-字典集合,你真的了解吗(代码片段)

...)。字典和集合在Python被广泛使用,并且性能进行了高度优化,其重要性不言而喻。字典和集合基础那究竟什么是字典,什么是集合呢?字典是一系列由键(key)和值(value)配对组成的元 查看详情

iPhone 5 优化要求 - 启动图像真的有必要吗?

】iPhone5优化要求-启动图像真的有必要吗?【英文标题】:iPhone5OptimizationRequirement-Launchimagereallynecessary?【发布时间】:2013-05-1420:33:25【问题描述】:尝试将二进制文件上传到AppStore时,我在电子邮件中收到以下回复:“iPhone5优化... 查看详情

Adam 优化器真的是 RMSprop 加动量吗?如果是,为啥它没有动量参数?

】Adam优化器真的是RMSprop加动量吗?如果是,为啥它没有动量参数?【英文标题】:IsAdamoptimizerreallyRMSpropplusmomentum?Ifyes,whyitdoesn\'thaveamomentumparameter?Adam优化器真的是RMSprop加动量吗?如果是,为什么它没有动量参数?【发布时间】... 查看详情

15个vue技巧,开发有段时间了,才知道还能这么用(你所知道真的包括这些吗?)(代码片段)

...关于那些需要花时间和精力才能掌握的大概念。掌握一些技巧和窍门,可以让我们的编程生活变得更容易--没有大量重复的工作。在使用Vue开发的这几年里,掌握一些有用的技巧,使用一些更高级的技术点,总会... 查看详情

mysql15个有用的mysql/mariadb性能调整和优化技巧(代码片段)

...。这篇文章将告诉你一些基本的,但非常有用的关于如何优化MySQL/MariaDB性能的技巧。注意,本文假定您已经安装了MySQL或MariaDB。如果你仍然不知道如何在系统上安装它们,你可以按照以下说明去安装:在RHEL/CentOS7上安装LAMP在Fedo... 查看详情

会优化,你真的会优化吗?其实你可能真的缺少一份理解数据库篇

  其实,在写这篇博客之前,我也是感觉自己会点优化,至少知道不要使用“*”号啊,给经常查询的列创建索引啊什么的,其实都不是大家想的那样简单的,其实它们背后存在很多的东西,值得我们去理解和学习。  和大... 查看详情

GCC 优化标志

】GCC优化标志【英文标题】:GCCOptimizationFlags【发布时间】:2013-12-0823:05:49【问题描述】:我正在尝试将一堆具有不同优化的可执行文件放在一起。我唯一的问题是,带有-c的gcc命令也应该能够使用-O标志吗?我问这个,因为我意... 查看详情

程序员亲试,人体工学椅真的有用吗?

目录一、前言介绍:二、人体工学简介2.1为什么要买人体工学椅?2.2怎么办?2.3公司配的椅子不行?三、静态体验3.1开箱体验3.2 外观体验四、动态体验4.1标准坐姿4.2椅座高度3.3座垫深度4.3椅背仰角4.44D扶手4.5腰枕... 查看详情