在C ++中,应该麻烦缓存变量还是让编译器进行优化?(别名)


114

考虑以下代码(p类型为,unsigned char*并且bitmap->width为某种整数类型,确切地是未知的,并且取决于我们使用的某些外部库的版本):

for (unsigned x = 0;  x < static_cast<unsigned>(bitmap->width);  ++x)
{
    *p++ = 0xAA;
    *p++ = 0xBB;
    *p++ = 0xCC;
}

是否值得对其进行优化[..]

在某些情况下,可以通过编写以下内容产生更有效的结果:

unsigned width(static_cast<unsigned>(bitmap->width));
for (unsigned x = 0;  x < width;  ++x)
{
    *p++ = 0xAA;
    *p++ = 0xBB;
    *p++ = 0xCC;
}

...还是对编译器进行优化很简单?

您认为什么是“更好”的代码?

编辑者(Ike)的注释:对于那些对删除线文本感到疑惑的人,最初的问题措词很危险,接近主题外的领域,尽管获得了积极的反馈,却非常接近完成。这些已经被淘汰了。但是,请不要惩罚回答了问题的这些问题的回答者。


19
如果*p与类型相同,width则优化并非易事,因为p可以width在循环内对其进行指向和修改。
emlai

31
询问编译器是否优化了特定操作通常是错误的问题。您(通常)最终感兴趣的是哪个版本运行速度更快,您应该简单地对其进行衡量。
SirGuy 2015年

4
@GuyGreer我确实同意,尽管我会说这个问题很好,或者至少很有趣,但不幸的是答案是“您必须根据用例进行度量”。原因是功能是可移植的,但性能却不是。因此,它实际上取决于构建过程的每个部分,从编译器开始并在目标站点(操作系统/硬件组合)完成。当然,最好的猜测是,在这一点上,编译器比人类更聪明。
luk32

19
如果我是编译器,我会看到您的两个示例不相同。可能p指向与相同的内存bitmap->width。因此,我不能合法地将第一个示例优化为第二个示例。
Mysticial

4
“ p”存储在哪里?我建议您通过执行诸如“ char * strict p2 = p;”之类的事情可能会获得巨大的性能胜利。然后在循环中使用“ p2”而不是“ p”。然后,如果要将对“ p2”的更改应用回p,请使用“ p + =(p2-p);”。请注意,不能使用从p2复制的指针读取在p2的生存期内未复制未从p2形式复制的指针,反之亦然,并且在p2的生存期之后不得将p2的副本用于任何目的,但是编译器可以使用这些指针使优化无法通过任何其他方式完成的事实。
2015年

Answers:


81

乍一看,我认为编译器可以在激活了优化标志的情况下为两个版本生成等效的程序集。当我检查它时,我很惊讶地看到结果:

资源 unoptimized.cpp

注意:此代码无意执行。

struct bitmap_t
{
    long long width;
} bitmap;

int main(int argc, char** argv)
{
    for (unsigned x = 0 ; x < static_cast<unsigned>(bitmap.width) ; ++x)
    {
        argv[x][0] = '\0';
    }
    return 0;
}

资源 optimized.cpp

注意:此代码无意执行。

struct bitmap_t
{
    long long width;
} bitmap;

int main(int argc, char** argv)
{
    const unsigned width = static_cast<unsigned>(bitmap.width);
    for (unsigned x = 0 ; x < width ; ++x)
    {
        argv[x][0] = '\0';
    }
    return 0;
}

汇编

  • $ g++ -s -O3 unoptimized.cpp
  • $ g++ -s -O3 optimized.cpp

组装(未优化)

    .file   "unoptimized.cpp"
    .text
    .p2align 4,,15
.globl main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    .cfi_personality 0x3,__gxx_personality_v0
    movl    bitmap(%rip), %eax
    testl   %eax, %eax
    je  .L2
    xorl    %eax, %eax
    .p2align 4,,10
    .p2align 3
.L3:
    mov %eax, %edx
    addl    $1, %eax
    movq    (%rsi,%rdx,8), %rdx
    movb    $0, (%rdx)
    cmpl    bitmap(%rip), %eax
    jb  .L3
.L2:
    xorl    %eax, %eax
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
.globl bitmap
    .bss
    .align 8
    .type   bitmap, @object
    .size   bitmap, 8
bitmap:
    .zero   8
    .ident  "GCC: (GNU) 4.4.7 20120313 (Red Hat 4.4.7-16)"
    .section    .note.GNU-stack,"",@progbits

组装(优化。s)

    .file   "optimized.cpp"
    .text
    .p2align 4,,15
.globl main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    .cfi_personality 0x3,__gxx_personality_v0
    movl    bitmap(%rip), %eax
    testl   %eax, %eax
    je  .L2
    subl    $1, %eax
    leaq    8(,%rax,8), %rcx
    xorl    %eax, %eax
    .p2align 4,,10
    .p2align 3
.L3:
    movq    (%rsi,%rax), %rdx
    addq    $8, %rax
    cmpq    %rcx, %rax
    movb    $0, (%rdx)
    jne .L3
.L2:
    xorl    %eax, %eax
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
.globl bitmap
    .bss
    .align 8
    .type   bitmap, @object
    .size   bitmap, 8
bitmap:
    .zero   8
    .ident  "GCC: (GNU) 4.4.7 20120313 (Red Hat 4.4.7-16)"
    .section    .note.GNU-stack,"",@progbits

差异

$ diff -uN unoptimized.s optimized.s
--- unoptimized.s   2015-11-24 16:11:55.837922223 +0000
+++ optimized.s 2015-11-24 16:12:02.628922941 +0000
@@ -1,4 +1,4 @@
-   .file   "unoptimized.cpp"
+   .file   "optimized.cpp"
    .text
    .p2align 4,,15
 .globl main
@@ -10,16 +10,17 @@
    movl    bitmap(%rip), %eax
    testl   %eax, %eax
    je  .L2
+   subl    $1, %eax
+   leaq    8(,%rax,8), %rcx
    xorl    %eax, %eax
    .p2align 4,,10
    .p2align 3
 .L3:
-   mov %eax, %edx
-   addl    $1, %eax
-   movq    (%rsi,%rdx,8), %rdx
+   movq    (%rsi,%rax), %rdx
+   addq    $8, %rax
+   cmpq    %rcx, %rax
    movb    $0, (%rdx)
-   cmpl    bitmap(%rip), %eax
-   jb  .L3
+   jne .L3
 .L2:
    xorl    %eax, %eax
    ret

与未优化版本不同,未优化版本会在每次迭代()时计算偏移量,因此为优化版​​本生成的程序集实际上会加载(leawidth常量。widthmovq

当我有时间的时候,我最终发布了一些基准测试。好问题。


3
看看是否将代码转换为不同的生成方式(const unsigned而不只是unsigned在未优化的情况下)会很有趣。
马克·兰瑟姆

2
@MarkRansom我想应该不会有什么不同:const的“承诺”仅在单个比较中,而不是在整个循环中
Hagen von Eitzen

13
切勿使用该功能main来测试优化。Gcc故意将其标记为冷,因此禁用了一些优化。我不知道是不是这种情况,但这是一个重要的习惯。
Marc Glisse 2015年

3
@MarcGlisse您是100%正确的。我已经着急写了,我会改进的。
YSC 2015年

3
这是godbolt上一个编译单元中这两个函数的链接,假定bitmap是全局的。非CSEd版本使用的内存操作数cmp,在这种情况下perf没问题。如果它是本地的,则编译器可以假定其他指针无法“知道”它并指向它。只要将涉及全局变量的表达式存储在临时变量中,只要它能提高(或不损害)可读性,或者性能至关重要,就不是一个坏主意。除非有很多事情发生,否则此类本地人通常只能住在寄存器中,永远不会被浪费掉。
彼得·科德斯

38

实际上,您的代码片段中没有足够的信息可以分辨,而我能想到的一件事就是别名。从我们的角度来看,很明显,您不需要pbitmap指向内存中的相同位置,但是编译器不知道并且(由于p类型char*)编译器必须使此代码正常工作,即使pbitmap重叠。

这意味着在这种情况下,如果循环bitmap->width通过指针p进行更改,则在bitmap->width稍后重新读取时必须看到该循环,这又意味着将其存储在局部变量中是非法的。

话虽这么说,我相信有些编译器实际上有时会生成同一代码的两个版本(我已经看到了这种情况的间接证据,但从未在这种情况下直接寻找有关编译器正在做什么的信息),并迅速检查了指针是否正确别名并运行更快的代码(如果确定可以的话)。

话虽这么说,但我只是简单地衡量两个版本的性能,我对此表示赞同,我的钱是没有看到两个版本的代码之间存在任何一致的性能差异。

以我的观点,如果您的目的是学习编译器优化理论和技术,则可以使用此类问题,但如果您的最终目标是使程序运行得更快,则会浪费时间(无用的微优化)。


1
@GuyGreer:这是一个主要的优化阻止程序;我认为很不幸的是,语言规则集中在有关有效类型的规则上,而不是确定不同项目的读写顺序是否正确的情况。与目前的规则相比,用这种术语编写的规则可以更好地满足编译器和程序员的需求。
2015年

3
@GuyGreer- restrict在这种情况下,限定词不是解决别名问题的答案吗?
LThode

4
以我的经验,restrict很大程度上是失败的。MSVC是我见过的唯一看起来正确执行的编译器。即使内联了,ICC也会通过函数调用丢失别名信息。除非您将每个输入参数都声明为restrict(包括this成员函数),否则GCC通常无法获得任何好处。
Mysticial

1
@Mystical:要记住的一件事是,char所有类型都使用别名,因此,如果您有char *,则必须restrict在所有内容上使用。或者,如果您强行关闭了GCC的严格别名规则,-fno-strict-aliasing则所有内容均被视为可能的别名。
Zan Lynx

1
@Ray restrictC ++中最新的关于类语义的建议是N4150
TC

24

好的,伙计们,所以我进行了测量GCC -O3(在Linux x64上使用GCC 4.9)。

事实证明,第二个版本的运行速度提高了54%!

所以,我想是混叠了,我没想过。

[编辑]

我再次尝试了用定义所有指针的第一个版本,__restrict__结果是相同的。奇怪。不是别名问题,或者由于某种原因,即使使用,编译器也无法很好地对其进行优化__restrict__

[编辑2]

好的,我想我几乎可以证明别名是问题所在。我重复了我的原始测试,这次使用数组而不是指针:

const std::size_t n = 0x80000000ull;
bitmap->width = n;
static unsigned char d[n*3];
std::size_t i=0;
for (unsigned x = 0;  x < static_cast<unsigned>(bitmap->width);  ++x)
{
    d[i++] = 0xAA;
    d[i++] = 0xBB;
    d[i++] = 0xCC;
}

并进行测量(必须使用“ -mcmodel = large”进行链接)。然后我尝试了:

const std::size_t n = 0x80000000ull;
bitmap->width = n;
static unsigned char d[n*3];
std::size_t i=0;
unsigned width(static_cast<unsigned>(bitmap->width));
for (unsigned x = 0;  x < width;  ++x)
{
    d[i++] = 0xAA;
    d[i++] = 0xBB;
    d[i++] = 0xCC;
}

测量结果是相同的-似乎编译器能够自行对其进行优化。

然后,我尝试了原始代码(使用指针p),这次p是类型std::uint16_t*。再次,由于严格的别名,结果是相同的。然后,我尝试使用“ -fno-strict-aliasing”进行构建,然后再次看到时间上的差异。


4
尽管从技术上讲确实可以回答问题,但这似乎应该是一条评论。还要注意,不幸的是,您还没有证明别名就是问题。似乎有可能,当然是有道理的,但这与得出结论是不同的。
SirGuy 2015年

@GuyGreer:参见我的[edit 2]-现在我认为它已经被证明了很多。
Yaron Cohen-Tal 2015年

2
我只是想知道为什么在循环中有“ x”的情况下才开始使用变量“ i”?
杰斯珀·马德森

1
是我自己难于理解54%的短语吗?您是说它是未优化速度的1.54倍吗?
罗迪

3
@ YaronCohen-Tal那么快两倍吗?令人印象深刻,但不是我所理解的“快54%”的意思!
罗迪

24

其他答案指出,由于别名规则允许将char别名为任何内容,因此将指针操作提升到循环之外可能会更改定义的行为,因此即使在大多数情况下显然对人类而言是正确的,也不是编译器所允许的优化程序员。

他们还指出,从性能的角度出发,将操作提升到循环之外通常不是但总是改善,而从可读性的角度来看通常是不利的。

我想指出,通常存在“第三种方式”。您可以倒计数为零,而不必计算所需的迭代次数。这意味着迭代次数仅在循环开始时需要一次,而不必在此之后存储。更好的是,在汇编程序级别上,它通常不需要显式比较,因为减量操作通常会设置标志,以指示在减量之前(进位标志)和之后(零标志)计数器是否为零。

for (unsigned x = static_cast<unsigned>(bitmap->width);x > 0;  x--)
{
    *p++ = 0xAA;
    *p++ = 0xBB;
    *p++ = 0xCC;
}

请注意,此版本的循环给出的x值在1..width范围内,而不是在0 ..(width-1)范围内。在您的情况下这并不重要,因为您实际上并没有将x用于任何东西,但这是需要注意的。如果您想使用x值在0 ..(width-1)范围内的递减计数循环,则可以执行。

for (unsigned x = static_cast<unsigned>(bitmap->width); x-- > 0;)
{
    *p++ = 0xAA;
    *p++ = 0xBB;
    *p++ = 0xCC;
}

如果需要,您还可以摆脱上面示例中的强制类型转换,而不必担心它对比较规则的影响,因为您对bitmap-> width所做的全部工作都是直接将其分配给变量。


2
我已经看到第二种情况的格式为x --> 0,导致出现“ downto”运算符。挺滑稽的。PS:我不认为将结束条件的变量作为可读性的负数,实际上可能相反。
Mark Ransom 2015年

这确实取决于,有时一条语句变得如此可怕,以至于将其分成多个语句可以提高可读性,但是我不相信这种情况。
–plugwash

1
+1不错的观察,尽管我会争辩说,在循环中悬挂static_cast<unsigned>(bitmap->width)width代替实际上是对可读性的一种改进,因为现在读者可以减少每行解析的事情。但是其他人的观点可能有所不同。
SirGuy 2015年

1
在许多其他情况下,向下计数比较好(例如,从列表中删除项目时)。我不知道为什么不经常这样做。
伊恩·戈德比(Nan Goldby)2015年

3
如果您想编写看起来更像是最佳asm的循环,请使用do { } while(),因为在ASM中,您将在循环的末尾添加一个条件分支。如果编译器无法证明它总是至少运行一次,那么通常的for(){}while(){}循环需要额外的指令在循环之前测试一次循环条件。无论如何,请使用for()while()在检查循环是否应该运行一次或更具可读性时是否有用。
彼得·科德斯

11

这里唯一可以阻止优化的是严格的别名规则简而言之

“严格的别名是由C(或C ++)编译器做出的假设,即取消引用指向不同类型对象的指针将永远不会引用相同的内存位置(即彼此别名)。”

[…]

规则的例外是char*,它可以指向任何类型。

该异常也适用于unsignedsigned char指针。

这是你的代码的情况下:您正在修改*p通过p这一个unsigned char*,所以编译器必须假定它可能指向bitmap->width。因此,的缓存bitmap->width是无效的优化。YSC的答案显示了这种防止优化的行为。

当且仅当p指向chardecltype(bitmap->width)类型和非类型时,缓存才是可能的优化。


10

最初提出的问题是:

值得优化吗?

我对此的回答(获得了上下投票的好组合。)

让编译器为此担心。

编译器几乎肯定会比您做得更好。并且不能保证您的“优化”要比“显而易见的”代码更好-您是否进行了测量?

更重要的是,您是否有任何证据证明您正在优化的代码会对程序性能产生影响?

尽管投票不足(并且现在看到了别名问题),但我仍然对此感到满意,因为它是有效的答案。如果您不知道优化某件事是否值得,那可能就不是。

当然,一个完全不同的问题是:

我怎么知道优化一段代码是否值得?

首先,您的应用程序或库是否需要比当前运行速度更快?用户是否等待太久?您的软件会预测昨天的天气,而不是明天的天气吗?

根据您的软件用途和用户期望,只有您能真正说出这一点。

假设您的软件确实需要优化,接下来要做的就是开始测量。探查器会告诉您代码花费的时间。如果您的片段没有显示为瓶颈,则最好不要管它。Profiler和其他测量工具还将告诉您您的更改是否有所作为。可能花费数小时的时间尝试优化代码,却发现您并没有明显的不同。

无论如何,“优化”是什么意思?

如果您不是在编写“优化”代码,则您的代码应尽可能清晰,简洁。“过早的优化是邪恶的”论据不是草率或低效代码的借口。

优化的代码通常会牺牲上面的某些属性以提高性能。它可能涉及引入其他局部变量,使对象的范围超出预期范围,甚至颠倒正常的循环顺序。所有这些可能都不那么清楚或简洁,因此请记录一下有关为什么执行此操作的代码(简短地!)。

但是通常,使用“慢速”代码,这些微优化是最后的手段。首先要看的是算法和数据结构。有没有一种方法可以避免进行这项工作?线性搜索可以替换为二进制搜索吗?链表在这里会比矢量更快吗?还是哈希表?我可以缓存结果吗?在这里做出好的“有效”决策通常会影响性能一个数量级甚至更多!


12
当您遍历位图图像的宽度时,循环逻辑可能是循环所花费时间的重要部分。与其担心过早的优化,不如在这种情况下最好从一开始就开发出有效的最佳实践。
Mark Ransom 2015年

4
@MarkRansom在某种程度上表示同意:但是“最佳做法”可能是:a:使用现有的库或API调用来填充图像,或者b:让GPU为您完成。这绝对不是OP建议的未经测量的微观优化。您怎么知道此代码曾经执行过多次,或者位图大于16像素宽...?
罗迪

@Veedrac。欣赏-1的理由。自从我回答以来,这个问题的重点已经发生了微妙的变化。如果您认为(扩展的)答案仍然无济于事,那么我该删除它的时间了……“是否值得……”始终主要基于观点。
罗迪

@Roddy我很感谢这些编辑,它们确实对我们有所帮助(无论如何,我的评论听起来似乎太苛刻了)。不过,我仍然处于待命状态,因为这实际上是对不适合堆栈溢出的问题的解答。就像这里的投票结果一样,这似乎是针对片段的正确答案。
Veedrac

6

在这种情况下,我使用以下模式。它几乎与您的第一种情况一样短,并且比第二种情况要好,因为它将临时变量保留在循环本地。

for (unsigned int x = 0, n = static_cast<unsigned>(bitmap->width); x < n; ++x)
{
  *p++ = 0xAA;
  *p++ = 0xBB;
  *p++ = 0xCC;
}

相比智能编译器,调试版本或某些编译标志,这将更快。

Edit1:将恒定操作放在循环外部是一种很好的编程模式。它显示了对机器操作基础的理解,尤其是在C / C ++中。我认为证明自己的努力应该放在不遵循这种做法的人身上。如果编译器为好的模式付出代价,那是编译器中的错误。

Edit2::我在vs2013上针对原始代码对我的建议进行了评估,改善了%1。我们可以做得更好吗?一个简单的手动优化比x64机器上的原始循环提高了3倍,而无需求助于特殊指令。下面的代码假定使用小端系统和正确对齐的位图。TEST 0是原始的(9秒),TEST 1是更快的(3秒)。我敢打赌,有人可以使它变得更快,而测试的结果将取决于位图的大小。肯定在不久的将来,编译器将能够生成始终如一的最快代码。恐怕这将是未来,当编译器也将成为程序员AI时,我们将无法工作。但是现在,只需编写代码来表明您知道循环中不需要额外的操作。

#include <memory>
#include <time.h>

struct Bitmap_line
{
  int blah;
  unsigned int width;
  Bitmap_line(unsigned int w)
  {
    blah = 0;
    width = w;
  }
};

#define TEST 0 //define 1 for faster test

int main(int argc, char* argv[])
{
  unsigned int size = (4 * 1024 * 1024) / 3 * 3; //makes it divisible by 3
  unsigned char* pointer = (unsigned char*)malloc(size);
  memset(pointer, 0, size);
  std::unique_ptr<Bitmap_line> bitmap(new Bitmap_line(size / 3));
  clock_t told = clock();
#if TEST == 0
  for (int iter = 0; iter < 10000; iter++)
  {
    unsigned char* p = pointer;
    for (unsigned x = 0; x < static_cast<unsigned>(bitmap->width); ++x)
    //for (unsigned x = 0, n = static_cast<unsigned>(bitmap->width); x < n; ++x)
    {
      *p++ = 0xAA;
      *p++ = 0xBB;
      *p++ = 0xCC;
    }
  }
#else
  for (int iter = 0; iter < 10000; iter++)
  {
    unsigned char* p = pointer;
    unsigned x = 0;
    for (const unsigned n = static_cast<unsigned>(bitmap->width) - 4; x < n; x += 4)
    {
      *(int64_t*)p = 0xBBAACCBBAACCBBAALL;
      p += 8;
      *(int32_t*)p = 0xCCBBAACC;
      p += 4;
    }

    for (const unsigned n = static_cast<unsigned>(bitmap->width); x < n; ++x)
    {
      *p++ = 0xAA;
      *p++ = 0xBB;
      *p++ = 0xCC;
    }
  }
#endif
  double ms = 1000.0 * double(clock() - told) / CLOCKS_PER_SEC;
  printf("time %0.3f\n", ms);

  {
    //verify
    unsigned char* p = pointer;
    for (unsigned x = 0, n = static_cast<unsigned>(bitmap->width); x < n; ++x)
    {
      if ((*p++ != 0xAA) || (*p++ != 0xBB) || (*p++ != 0xCC))
      {
        printf("EEEEEEEEEEEEERRRRORRRR!!!\n");
        abort();
      }
    }
  }

  return 0;
}

如果您使用三个int64_t而不是int64_t和int32_t,则可以在64bit上再节省25%。
安东宁Lejsek

5

有两件事要考虑。

A)优化将多久运行一次?

如果答案不是很常见(例如仅当用户单击按钮时),则不要打扰它使您的代码不可读。如果答案是每秒1000次,那么您可能需要进行优化。如果还有些复杂,请确保添加评论以说明将如何帮助下一个出现的人。

B)这会使代码更难以维护/故障排除吗?

如果您没有看到性能上的巨大提高,那么仅使代码变得神秘以节省一些时钟滴答就不是一个好主意。很多人会告诉你,任何优秀的程序员都应该能够查看代码并弄清楚发生了什么。这是真的。问题是在商业世界中,额外的时间花费了很多金钱。因此,如果您可以使其更漂亮,则可以阅读。您的朋友会感谢您的。

也就是说,我个人将使用B示例。



4

通常,让编译器为您进行优化,直到您确定应该接管为止。这样做的逻辑与性能无关,而与人类的可读性有关。在茫茫人海中大多数情况下,你的程序的可读性比性能更重要。您应该以编写易于阅读的代码为目标,然后仅在确信性能比代码的可维护性更重要时才担心优化。

一旦发现性能很重要,就应该在代码上运行探查器,以确定哪些循环效率低下,并分别进行优化。确实确实存在某些情况下您想进行优化(特别是如果您迁移到涉及STL容器的C ++),但是在可读性方面的成本很高。

另外,我可以想到实际上可能会使代码变慢的病理情况。例如,考虑编译器无法bitmap->width在整个过程中证明其不变的情况。通过添加width变量,您可以强制编译器在该范围内维护局部变量。如果出于某种特定于平台的原因,该额外变量阻止了某些堆栈空间优化,则可能必须重新组织其发出字节码的方式,并产生效率较低的内容。

例如,在Windows x64上,__chkstk如果函数将使用一页以上的局部变量,则必须在函数的序言中调用特殊的API调用。此功能使Windows有机会管理在需要时用于扩展堆栈的保护页。如果您的额外变量将堆栈使用量从1页以下提高到1页以上,则函数__chkstk每次输入时都必须调用。如果要在慢速路径上优化此循环,实际上可能会使快速路径的速度降低得比在慢速路径上保存的速度大!

当然,这有点病态,但是该示例的要点是您实际上可以减慢编译器的速度。它只是表明您必须分析工作以确定优化的方向。同时,请不要以任何方式牺牲可读性,以免影响优化。


4
我希望C和C ++提供更多方式来明确标识程序员不关心的事物。它们不仅为编译器提供了更多优化的机会,而且还可以节省其他阅读代码的程序员,而不必猜测是否每次都在重新检查bitmap-> width以确保对其所做的更改影响循环,或者是否可能在缓存bitmap-> width以确保对其所做的更改不会影响循环。有了说“是否缓存这个-我不在乎”的方法,就可以清楚地说明程序员选择的原因。
2015年

@supercat我全心全意地同意,因为可以看到有人看着我为解决这个问题而试图写的那些成堆的失败的语言。我发现,如果没有太多不敬虔的语法来定义某人不关心的“什么”,那将是非常不值得的,这是非常困难的。我继续徒劳地寻找。
Cort Ammon 2015年

不可能在所有情况下都定义它,但是我认为在很多情况下类型系统都可以提供帮助。C决定将字符类型设置为“通用访问器”,而不是使用比可以应用于任何类型的“ volatile”宽松一些的类型限定符,并且此类语义将按顺序处理访问非限定等效类型,也访问具有相同限定符的所有类型的变量。这将有助于弄清楚是否有人在使用字符类型,因为有人需要...
supercat

...混淆行为,或者是否正在使用它们,因为它们的大小适合于人们的需求。具有显式的别名障碍也将是有帮助的,这种别名在许多情况下可以放置在循环之外,这与与字符类型访问相关的隐式障碍不同。
2015年

1
这是一个明智的选择,但是,通常,如果您确实已经为任务选择了C,则性能可能非常重要,因此应使用不同的规则。否则,最好使用Ruby,Java,Python或类似的东西。
Audrius Meskauskas

4

比较是错误的,因为这两个代码段

for (unsigned x = 0;  x < static_cast<unsigned>(bitmap->width);  ++x)

unsigned width(static_cast<unsigned>(bitmap->width));
for (unsigned x = 0;  x<width ;  ++x)

不等同

在第一种情况下,它width是依赖的而不是const,因此不能假定它在后续迭代之间可能不会改变。因此,无法对其进行优化,而必须在每个循环中对其进行检查

在优化的情况下,将在程序执行过程中的某个时候为局部变量分配值bitmap->width编译器可以验证这实际上没有改变。

您是否考虑过多线程,或者该值可能是外部依赖的,因此其值是易变的。如果您不告诉,编译器将如何解决所有这些问题?

编译器只能完成您的代码允许的工作。


2

除非您知道编译器如何精确地优化代码,否则最好通过保持代码的可读性和设计来进行自己的优化。实际上,很难检查我们为新编译器版本编写的每个函数的汇编代码。


1

编译器无法优化,bitmap->width因为width可以在迭代之间更改的值。有几个最常见的原因:

  1. 多线程。编译器无法预测其他线程是否要更改值。
  2. 在循环内进行修改,有时很难判断变量是否会在循环内更改。
  3. 这是函数调用,例如iterator::end()container::size()因此很难预测它是否总是返回相同的结果。

总结(我个人的观点),需要高度优化的地方,您需要自己做,在其他地方,只要代码可读性没有太大差异,编译器就可以对其进行优化,而不是对其进行优化。

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.