【问题标题】:C++ standard library vs mortal made code + where can I find the sources?C++ 标准库与凡人制作的代码 + 我在哪里可以找到源代码?
【发布时间】:2010-09-09 21:17:06
【问题描述】:

两个,也许是微不足道的问题:

1. 为什么我的 STD 功能打不通?

真的。在过去的三天里,我实现了比 std::sort 更快的东西,只是为了做到这一点。它应该是一个 introsort,我怀疑它在内部使用了单轴版本的快速排序。史诗般的失败。我的速度至少慢了一倍。

我什至非常痛苦地复制粘贴了其他一流的程序员代码。徒劳无功。 我也对我的其他算法进行了基准测试......我的二分搜索、upper_bound、lower_bound 版本被如此精简,以至于不能用更少的指令来制作。不过,它们的速度大约是原来的两倍。

我问,为什么,为什么,为什么?这引出了我的下一个问题……

2。在哪里可以找到 STL 库函数的源代码?

当然,我想看看他们的来源!是否有可能编写比这些更高效的代码,或者我是否在我的“简单”main.cpp 的抽象级别上无法达到 STL 库使用的优化?

我的意思是例如...让我们以地图为例...它们是简单的关联容器。文档说它是用红黑树实现的。现在......是否值得尝试实现我自己的红黑树,或者他们把这种快乐 :-) 从我身边带走了,我应该把我得到的每一个数据都扔到地图容器中?

我希望这确实有意义。 如果没有,请见谅。


【问题讨论】:

  • 大多数(如果不是全部)在大多数实现中的 STL 只是头文件。因此,您已经有了源代码。
  • 只是一点观点:您将三天的努力与一个用了 15 年的时间才到达现在的图书馆进行了对比。此外,“精简”代码不一定更快。如果是正确的 10 行代码,10 行代码可能会比 1 行更快。
  • 提问就是开始学习。 :)
  • “真正的大玩家”?在过去的 15 年里,那些大玩家是否花了他们的红黑树代码来完善他们的红黑树代码?因为编写标准库的人正是这样做的。
  • @Mike 你认为“那些人”有多少人?你认为“他们”在这方面工作了多长时间?并不是有一天有人有了 STL 的想法并坐下来编写它,从而准确地为我们提供了我们今天仍在使用的东西。作为 STL,它已经发展了 15 年左右。

标签: c++


【解决方案1】:

简短的回答是“如果可以编写更快的代码来做同样的事情,那么标准库早就做到了”。

标准库是由聪明人设计的,它之所以成为 C++ 的一部分,是因为其他聪明人认为它很聪明。从那时起,15 年过去了,其他聪明的人试图采用这些规范并编写绝对最有效的代码来实现它们。

这是你试图与之竞争的很多聪明才智。 ;)

因此,STL 中没有魔法,他们不会作弊,也不会使用您无法使用的技巧。它经过精心设计,旨在最大限度地提高性能。

C++ 的问题在于它本身并不是一种快速的语言。如果你不小心,很容易引入各种低效率:虚函数调用、缓存未命中、过多的内存分配、不必要的对象复制,如果你不小心,所有这些都会削弱 C++ 代码的性能。

小心,您可以编写与 STL 一样高效的代码。 并不特别。但一般来说,获得更快代码的唯一方法是更改​​需求。标准库必须是通用的,才能在所有用例中尽可能好地工作。如果您的要求更具体,有时可以编写有利于这些特定情况的专用代码。但代价是,在其他情况下,代码要么不起作用,要么效率低下。

最后一点是,STL 如此聪明以及将其纳入标准的一个关键部分是它几乎是零开销。许多语言的标准库“足够快”,但不如手动代码快。他们有一个排序算法,但它并不像你自己就地编写那样快。它可能会在一个公共的“对象”基类之间使用一些强制转换,或者可能对值类型使用装箱。 STL 的设计使得几乎所有内容都可以由编译器内联,产生的代码与您自己手动滚动的代码相同。它使用模板来专门针对您正在使用的类型,因此没有转换为容器或算法可以理解的类型的开销。

这就是它难以与之竞争的原因。这是一个非常高效的库,而且必须如此。以普通 C 或 C++ 程序员的心态,尤其是 10 到 15 年前,如果std::vector 比原始数组慢 5%,没有人会使用它。如果迭代器和标准算法不如自己编写循环那么快,那么没有人会使用它们。

因此,STL 开创了许多巧妙的 C++ 技巧,以便变得与手动 C 代码一样高效。

【讨论】:

  • +1 表示“如果可以编写更快的代码来做同样的事情,那么标准库已经完成了”
  • “如果你不小心,很容易引入各种低效率:...,缓存未命中...”您能详细说明一下吗?我今晚要去谷歌搜索,但如果你有任何好的资源可以学习,我会全神贯注(或眼睛,在这种情况下)。
  • 说实话,我不确定是否包括那个,因为它不是 C++ 独有的,它是硬件的属性。问题基本上是 CPU 缓存使用局部性来确定要保留在缓存中的内存位:空间局部性(如果您只使用地址 X 处的数据,您可能很快就会需要 X+1 处的数据)和时间局部性(如果您只需要一大块数据,您可能很快就会再次需要它)。因此,如果您的代码涉及在随机数据块之间跳转,缓存将无法保存正确的数据,因此它必须不断地从主内存中加载该数据
  • 这可能成为问题的两种常见方式是,如果您有一个 2D 数组,按列而不是按行遍历它意味着每个新元素都在新行上,所以它很远从您访问的上一个条目中删除,并将导致缓存未命中。如果您一次遍历一行,您只能访问连续的地址,因此缓存能够预测需要哪些数据,并为您准备好。
  • 另一个例子是链表与数组。该列表为每个节点分配新内存,并且该内存可能在任何地方。向量是一块连续的内存,因此保证了空间局部性。向量的每一部分都靠近向量的其余部分,缓存喜欢这样
【解决方案2】:

它们可能在很大程度上进行了优化。这样的实现会考虑内存页面错误、缓存未命中等。

获取这些实现的源代码取决于它们附带的编译器。我认为大多数编译器(甚至微软)都会让你看到它们。

我认为最重要的是要了解您正在编译的体系结构以及您的程序将在其上运行的操作系统(如果有的话)。了解这些内容将使您能够精确定位硬件。

还有无数的优化技术。 This 是一个很好的总结。此外,全局优化是一门科学,所以肯定有很多东西要学。

There are some clever things 也在这个网站上。索西克特!

【讨论】:

  • 嗨,sztomi!我也来自匈牙利:-)。我使用 GCC,我只能找到带有声明的头文件,但没有任何来源:-(。我怎样才能达到相同的性能?我应该学习什么技能?我无法想象一个人怎么能用简单的方式完成如此复杂的事情我提到的几行代码(考虑二进制搜索,没有介绍)。
  • @Mike:你会感到惊讶。去年 11 月,我刚刚提高了 GCC 的 std::rotate 的性能。几十年来,代码从未更改过。 (但现在它很快,当然 :v)。)不过,我认为排序功能更迷人,并且往往会随着任何新的研究而更新。
  • 哇!听起来不错。您是否提交了更改或其他内容?无论如何,我刚刚找到了一些真正的 GCC 代码……dohh……充满了 __variable _name_this___and_that :-)。你怎么能忍受呢?
  • @Mike:GCC 标准库限制自己使用为实现保留的名称,这意味着所有内容(接口除外)都以双下划线或下划线+大写字母开头。否则,用户可以合法地#define 某些东西并意外修改变量名。 (具有讽刺意味的是,许多人查看库代码并决定使用具有此类名称的宏是 l33t,这违背了整个目的。)我认为最好使用普通变量名编写代码并在提交之前添加下划线。
  • @ Potatoswatter:“具有讽刺意味的是,许多人查看库代码并认为使用具有此类名称的宏是 l33t,这违背了整个目的。”在那部分大笑;-)
【解决方案3】:

查看您的代码的反汇编版本与他们的代码并进行比较,您可能会了解为什么他们的代码比您的更快。

为了制作更快的版本而从头开始重新实现标准库功能似乎是一件愚蠢的事情。尝试修改他们的版本以实现您的目标会为您提供更好的服务,尽管即便如此,您确实需要了解底层平台才能判断您所做更改的价值。

我猜如果你发布你的排序例程,它会在几分钟内被撕裂,你就会明白为什么你的版本比标准库版本慢得多。

【讨论】:

  • 你尝试修改微软的标准库。代码看不懂。
  • 谢谢你的组装提示,我会去的!另外……这不仅仅是为了编写更快的代码,它可能不会有所作为,但知道它是可行的……
  • 我在 MSVC 附带的标准容器代码中花费了大量时间。这是一个与我不同的编码标准,但是一旦你掌握了约定,它就非常一致并且很容易理解。 (如果是你不理解的算法,优化它可能不适合你。)
【解决方案4】:

大多数 IDE 都有使用编译器的搜索路径打开命名头文件的命令。我经常使用它并且倾向于保持algorithm 的代码打开。

对我来说,您要查找的代码在

/usr/include/c++/4.2.1/bits/stl_algo.h
/usr/include/c++/4.2.1/bits/stl_tree.h

请注意,很多人已经完成了关于排序和树平衡的论文(我认为这些领域已经被挑选到骨子里,我不会尝试在那里进行研究),而且他们中的许多人可能比你更有决心去做GCC 的标准库更快。

也就是说,总是有可能利用特定于您的代码的模式(一些子范围已经排序,经常使用的特定小序列大小等)。

【讨论】:

  • 感谢 IDE 提示。可恶的是,我的见识与那些人相比微不足道,但还是……难以下咽。我成功地在 1 亿个整数上比 std::sort 快了 4-6 倍,但仅限于分类排序,空间复杂度为 O(N)。我将尝试将其中一个重写为就地,从而减少所需的额外内存。看看我是否想出一些有价值的东西。如果有人可以向我指出上述源代码,(s)他非常受欢迎。
  • @Mike Shinola - 你为什么一直要求指向源代码的指针? Potatoswatter 刚刚告诉您它在 Linux 上的 GCC 位置。您使用的是什么编译器和环境?您现在可以下载许多 STL 实现,但很可能您的系统上已经有了源代码。
  • @Brian Neal - 我指的是分类源的就地实现的源代码,而不是我最初询问的 STL 库源。对不起,如果我没有正确表达我的意图,我很累,这里是午夜。
  • @Mike:“分类源的就地实现”?你能举个例子你在找什么吗?与树和排序远程相关的所有内容都在您机器上的头文件中。
  • 编辑:*“分类排序”,抱歉。我正在寻找一种基数排序的实现,它的空间复杂度比通常的 O(N) 小得多。在过去的几天里,我已经阅读了一些关于它们的报告,但是我目前的工作并没有给我足够的时间来尝试一下——我的技能也不足以在这么短的时间内将学术上的伪代码翻译成原始代码时间我手上的东西。不过,我一定会在一段时间内破解它。不幸的是我的假期结束了,我不能让自己花几个小时在算法上。
【解决方案5】:

两个答案,可能普遍适用:

  • 您可能无法实现许多其他聪明人花费更多时间优化的更高效的算法版本。仅凭时间和测试,STD 算法就相当不错了。

  • 对于相同的算法,优化对于所有当前的硬件和配置变化来说是“非常困难的”。举个例子,特定平台上算法性能的主要因素可能是其最常用的例程可以存储在哪些级别的缓存中,这通常不是您可以手动优化的东西。因此,与您可以编写的任何特定代码相比,编译器通常对实际算法性能的影响更大。

但是是的...如果您真的对优化很认真,请进入程序集并进行比较。不过,我的建议是专注于其他事情,除非您的工作是优化您的实施或其他事情。只是我的 2c。

【讨论】:

  • “不过,我的建议是专注于其他事情”......这正是我的结论,我会的。不,不幸的是,没有人会因为我对一个该死的算法感到紧张而付钱给我。 :-D
猜你喜欢
  • 2012-03-29
  • 1970-01-01
  • 2012-07-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-12
  • 2011-08-31
  • 1970-01-01
相关资源
最近更新 更多