【问题标题】:I need high performance. Will there be a difference if I use C or C++?我需要高性能。如果我使用 C 或 C++,会有区别吗?
【发布时间】:2010-12-17 13:56:27
【问题描述】:

我需要编写一个程序(大学项目)来解决(大约)一个 NP 难题。 它是线性排序问题的一种变体。 一般来说,我会有非常大的输入(如图表),并会尝试找到最佳解决方案 (基于将“评价”每个解决方案的函数)

如果我用 C 风格的代码(一个 main 和函数)编写这个会有区别吗 或者构建一个 Solver 类,创建一个实例并从 main 调用一个“运行”方法(类似于 Java)

此外,每次迭代都会进行大量浮点数学运算。

谢谢!

【问题讨论】:

标签: c++ c performance np-hard


【解决方案1】:

没有。

最大的性能提升/缺陷将取决于您实现的算法,以及您执行了多少不需要的工作(不需要的工作可能是从重新计算可能已缓存的先前值到使用过多的 malloc/free 与使用内存池, 通过值而不是引用传递大型不可变数据)

【讨论】:

  • 如果您查看 Language Shootout,您会发现 C 和 C++ 之间的差别非常小。
  • +1:OP 描述的是解决一个 NP 难题,因此真正的瓶颈是计算复杂度,而不是语言速度。即使是很小的问题集,Python 中的 O(log n) 算法每次都会击败 C 中的 O(n^2) 算法。计算复杂度将使使用语言 X 而不是语言 Y 获得的微小的性能常数因子相形见绌。
  • @Juliet:实际上,没有。对于小问题集,O(n^2) 算法可能比 O(log n) 甚至 O(1) 算法更快。例如,10 或 15 个元素的关联,做线性,O(n),在链表上搜索比使用哈希表更快,这是 O(1)(如果你的问题包括做很多很多操作在小型关联上,链表可能比使用哈希表快得多)。 big-O 复杂度只讨论 large 数据集的性能特征。
  • @BrianCampbell big-O 谈论问题如何扩展,而不是数据集的大小。
  • Big O 表示法讨论了当数据集的大小变为无穷大时问题的表现。大 O 表示法在小问题集中没有多大意义。 Robert Sedgewick 抱怨具有更好大 O 值的学术算法会比实际使用的算法更快的情况,当且仅当我们处理更大的问题时,在可预见的未来任何计算机都可以处理。
【解决方案2】:

优化代码的最大障碍不再是语言(对于正确编译的语言),而是程序员。

【讨论】:

    【解决方案3】:

    不,除非你使用虚函数。

    编辑:如果您需要运行时动态,那么是的,虚函数与手动构造的 if-else 语句一样快或更快。但是,如果您在方法前添加 virtual 关键字,但实际上并不需要多态性,那么您将付出不必要的开销。编译器不会在编译时优化它。我只是指出这一点,因为它是 C++ 的特性之一,它打破了“零开销原则”(引用 Stroustrup)。

    作为旁注,因为您提到大量使用 fp 数学:

  • 以下 gcc 标志可以帮助您加快速度(我确信对于 Visual C++ 也有等效标志,但我不使用它):-mfpmath=sse-ffast-math-mrecip(最后两个“有点危险”,这意味着它们可能会在边缘情况下给你奇怪的结果以换取速度。第一个会降低精度——你有 64 位双精度而不是 80 位双精度——但是这个额外的通常不需要精度。)这些标志对于 C 和 C++ 编译器同样适用。

  • 根据您的处理器,您可能还会发现使用较大但不是无限的值模拟真正的INFINITY 可以提高速度。这是因为真正的INFINITY 必须由处理器作为特殊情况处理。
  • 【讨论】:

    • 我敢打赌,如果您将虚函数的明智使用与可以在 C 中完成的替代方法(函数指针、switch 语句等)进行比较,您会发现虚函数的性能也一样好,或者比那些替代品更好。
    • 嘿,这真是个好帖子。您说了一些增加问题本身的内容!
    • 这是关于虚拟功能的愚蠢评论。如果你需要它们,你就需要它们。在 C 中实现等价的虚函数将与编译器在 C++ 中生成的一样昂贵。不同之处在于,编译器生成的 C++ 更可能是正确的,并且背后有 10 年的编译器优化技巧。
    • 使用虚函数并不一定意味着性能更差。必须考虑在虚函数体中花费的时间。例如,执行 vtable 查找所产生的间接性可能由虚函数体中实际发生的事情支配。
    • 另外,仅仅将方法声明为虚拟并不意味着它是使用 vtable 调用的。如果编译器可以确定变量的确切类型,因为它在堆栈上,则不需要使用虚拟调度。
    【解决方案4】:

    经验法则 - 在您知道要优化什么之前不要优化。所以从 C++ 开始,并有一些工作原型。然后对其进行轮廓分析并重写装配中的瓶颈。但正如其他人所指出的,选择的算法将比语言产生更大的影响。

    【讨论】:

      【解决方案5】:

      说到性能,你可以在 C 中做的任何事情都可以在 C++ 中完成。 例如,众所周知,虚方法“慢”,但如果确实有问题,您仍然可以求助于 C 习惯用法。

      C++ 还带来了模板,与使用void* 进行泛型编程相比,它带来了更好的性能。

      【讨论】:

      • ++ 如果虚函数是您真正需要的东西,那么它们在任何一种语言中的运行速度都一样快。它们只是在 C++ 中更漂亮一点。无论如何,在您注意到虚拟函数和非虚拟函数之间的区别之前,代码必须非常非常紧凑。
      • 有点 OT,但几年前我看到了 Scott Meyers 的一次演讲,他展示了使用模板生成与手动优化的 C 完全相同的代码(这是在嵌入式系统的上下文中,但同样的原则仍然适用)。
      【解决方案6】:

      Solver 类将被构造一次,我接受它,run 方法执行一次......在那种环境下,你不会看到区别。相反,需要注意以下几点:

      • 内存管理非常昂贵。如果你需要做很多小事malloc()s,操作系统会吃掉你的午餐。如果您知道自己很快会再次做同样的事情,请下定决心重用您创建的任何数据结构!

      • 实例化类通常意味着...分配内存!同样,实例化少量对象并重新使用它们实际上没有任何成本。但请注意创建对象只是为了将它们拆除并在不久之后重建它们!

      • 在问题允许的范围内,为您的架构选择合适的浮点类型。 double 可能最终会比 float 快,尽管它需要更多内存。您应该尝试对此进行微调。理想情况下,您将使用#definetypedef 来指定类型,以便您可以在一处轻松更改它。

      • 整数计算可能比浮点计算快。根据数据的数值范围,您还可以考虑将整数视为定点小数。如果您需要 3 个小数位,您可以使用 ints 并将它们视为“milli-somethings”。你必须记住在除法和乘法之后移动小数......但没什么大不了的。当然,如果您使用基本算术之外的任何数学函数,那当然会消除这种可能性。

      【讨论】:

        【解决方案7】:

        由于两者都已编译,而且现在的编译器非常擅长处理 C++,我认为唯一的问题在于代码的优化程度。我认为用 C++ 编写速度较慢的代码会更容易,但这取决于您的模型最适合哪种风格。

        归根结底,我怀疑会有什么真正的区别,假设两者都写得很好,你使用的任何库,写得有多好,如果你在同一台计算机上测量。

        【讨论】:

        • 用 C++ 编写更慢的代码更容易!如果您使用的是 GCC,那么两种语言都会被编译成相同的中间语言。正是这种语言被优化并转换为机器语言。这就像说用 C++ 编写绿色代码更容易。
        【解决方案8】:

        与文件输入和算法本身相比,函数调用与成员函数调用开销不太可能成为限制因素。 C++ iostream 不一定是超高速的。如果你真的在优化,C 有“限制”,在 C++ 中,内联函数调用更容易。总的来说,C++ 为清晰地组织代码提供了更多选择,但如果它不是一个大程序,或者你只是打算用类似的方式编写它,无论是 C 还是 C++,那么 C 库的可移植性就变得更加重要。

        【讨论】:

          【解决方案9】:

          只要您不使用任何虚函数等,您就不会注意到任何显着的性能差异。早期的 C++ 被编译为 C,所以只要您知道这会产生任何相当大的开销(例如使用虚函数)的精确点,您就可以清楚地计算出差异。

          此外,我想指出,如果您使用 STL 和 Boost 库,则使用 C++ 可以给您带来很多好处。特别是 STL 为最重要的数据结构和算法提供了非常有效且经过验证的实现,因此您可以节省大量开发时间。

          实际上,它还取决于您将使用的编译器以及它将如何优化代码。

          【讨论】:

          • 我什至会争辩说,如果他使用虚函数,即使也不会有任何相当大的性能差异。 :)
          • 我发现 Johannes 对虚拟函数的看法过时了。在 1989 年,虚函数比直接调用稍慢,足以让您在紧密循环中注意到。但在 2009 年,情况并非如此。
          • 不同意虚函数的评论。如果你在 C++ 中使用它们,你将不得不在 C 中重新发明它们。你认为你在 C 中重新发明的版本会比编译器在 C++ 中生成的版本更快吗?
          • 两种语言在大多数编译器中仍然编译为相同的中间语言。
          • @marin york:嗯,从过程语言到面向对象可能会改变我们想要实现某些东西的方式,使我们能够编写更简单但更慢的代码?这是一个权衡。请注意,我对虚函数并不太难,而是对创建额外运行时代码(构造函数/析构函数、动态转换等)的所有其他内容都太难了。
          【解决方案10】:

          首先,用 C++ 编写并不意味着使用 OOP,请查看 STL 算法。 其次,C++ 在运行时甚至可以稍微快一些(编译时间与 C 相比可能很糟糕,但这是因为现代 C++ 往往严重依赖于对编译器征税的抽象)。

          编辑:好的,请参阅 Bjarne Stroustrup 的 discussion of qsort and std::sort,以及常见问题解答中提到的文章 (Learning Standard C++ as a New Language),其中他表明 C++ 样式的代码不仅可以更短且更具可读性 (因为更高的抽象),但也有点快。

          【讨论】:

          • 虽然正确,但您的答案需要得到大量示例和解释的支持,然后才能获得任何真正的价值。对于初学者,我建议解释(并链接到)Stroustrup 在 C 的 qsort 和 C++ 的 sort 例程之间的比较。 (但 +1 表示良好的开端)
          【解决方案11】:

          另一方面:

          C++ 模板可以是生成特定类型 / 优化代码变体。

          例如,C qsort 需要对比较器进行函数调用,而 std::sort 可以内联传递的函子。当比较和交换自己很便宜时,这可能会产生重大影响。

          请注意,您可以使用大量定义或代码生成器或手动生成针对各种类型优化的“自定义 qsorts” - 您也可以在 C 中进行这些优化,但成本要高得多。

          (它不是通用武器,模板仅在特定场景中有所帮助 - 通常将单个算法应用于不同的数据类型或注入不同的小段代码。)

          【讨论】:

            【解决方案12】:

            很好的答案。我会这样说:

            1. 就其形式结构而言,使算法尽可能高效。

            2. C++ 将与 C 一样快,除了它会诱使你做一些愚蠢的事情,比如构造你不需要的对象,所以不要上当. STL 容器类和迭代器之类的东西可能看起来是最新最好的东西,但它们会在热点中杀死你。

            3. 即便如此,还是在反汇编级别单步执行。您应该看到它非常直接地解决了您的问题。如果它花费大量周期进入和退出例程,请尝试一些内联(或宏)。如果它在大部分时间里徘徊在内存分配和释放中,请停止它。如果它有循环开销占很大比例的内部循环,请尝试展开循环。

            这就是您可以尽可能快地完成它的方法。

            【讨论】:

            • 我们使用我们拥有的容器类。如果您用 C 编写程序,则很容易编写使用排序数组的集合的代码,其性能比使用集合容器要差得多。或者,您可以花费大量时间来实现好的集合代码,这些代码可能会或可能不会像专业团队多年来编写的代码一样快。
            • 而 OP 正在谈论 NP 难题。很容易找到将代码速度提高 100,000 倍的方法,尤其是对学生而言。 (这意味着您可以解决比以前多 16 个元素的问题。)您可以在反汇编级别上花费很长时间,甚至无法将程序的速度提高一倍。
            • @prosfilaes:一般的库,特别是容器类,在一般性方面犯了错误,因为它们的首要目的是让人们不必自己写东西,其次是为了提高效率。他们进行了通常不适用于特定情况的权衡,例如当实际情况可能高频访问小型集合时,对大型集合有效。另外,不要假设有“专业团队”。通常是一个相当有能力的人,或者可能是一对夫妇,他们正在做出妥协。
            • 90% 的程序员无法正确编写二分搜索(Programming Pearls,Jon Bentley)。即使是那些有能力的人,这真的是最有效地利用他们的时间吗?
            • 我们在这里讨论的是 STL; 20 多年来,GNU 的实现已经引起了很多人的注意,我怀疑微软是否在任何可以将 Windows 上一半程序的速度提高 5% 的东西上吝啬。大多数容器库都有很多选项,因此您可以找到一个与您需要的权衡相匹配的选项。明智地使用容器库很少会比临时实现慢,如果是这样,您可以重新实现代码。
            【解决方案13】:

            我肯定会选择 C++。如果您小心设计并避免在热点内创建繁重的对象,您应该不会看到任何性能差异,但代码将更易于理解、维护和扩展。

            明智地使用模板和类。通过引用传递对象来避免不必要的对象创建。避免过多的内存分配,如果需要,在热点之前分配内存。在内存指针上使用restrict关键字来告诉编译器指针是否重叠。

            就优化而言,请注意内存对齐。假设您正在使用英特尔处理器,您可以使用向量指令,前提是您通过编译指示告诉编译器您的内存对齐和别名指针。你也可以通过内在函数直接使用向量指令。

            如果您有不同大小的短循环之类的东西,您还可以使用模板自动创建热点代码并让编译器对其进行优化。要了解性能并深入了解您的瓶颈,英特尔 vtune 或 oprofile 非常有用。

            希望对你有帮助

            【讨论】:

              【解决方案14】:

              我会进行一些 DSP 编码,但有时使用汇编语言仍然是值得的。我会说使用 C 或 C++ 中的任何一种,并准备好在需要时使用汇编语言,尤其是利用 SIMD 指令。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2016-01-10
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多