【问题标题】:C# slows down 30x when computing with infinities计算无穷大时,C# 速度减慢 30 倍
【发布时间】:2022-02-24 17:26:39
【问题描述】:

以下 C# 程序计算平方根的 1000 万次巴比伦迭代。

using System;
using System.Diagnostics;

namespace Performance {

    public class Program {
        public static void MeasureTime(long n, Action f) {
            Stopwatch watch = new Stopwatch();
            watch.Start();
            for (long i = 0; i < n; ++i) f();
            watch.Stop();
            Console.WriteLine($"{(n / watch.ElapsedMilliseconds) / 1000} Mop/s, {watch.ElapsedMilliseconds} ms");
        }

        public static void TestSpeed(double a) {
            Console.WriteLine($"Parameter {a}");
            double x = a;
            long n = 10_000_000;
            MeasureTime(n, () => x = (a / x + x) / 2);
            Console.WriteLine($"{x}\n");
        }

        static void Main(string[] args) {
            TestSpeed(2);
            TestSpeed(Double.PositiveInfinity);
        }
    }

}

当我在我的计算机上以发布模式运行它时,我得到:

Parameter 2
99 Mop/s, 101 ms
1,41421356237309

Parameter ∞
3 Mop/s, 3214 ms
NaN

这里的Mop/s 代表每秒百万次操作。当参数为无穷大时,由于某种原因,代码速度减慢了 30 倍以上。

这是为什么?

为了比较,这里是用 C++20 编写的相同程序:

#include <iostream>
#include <chrono>
#include <format>

namespace Performance {

    template <typename F>
    void MeasureTime(long long n, F f) {
        auto begin = std::chrono::steady_clock::now();
        for (long long i = 0; i < n; ++i) f();
        auto end = std::chrono::steady_clock::now();
        auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count();
        std::cout << std::format("{0} Mop/s, {1} ms", (n / ms) / 1000, ms) << std::endl;
    }

    void TestSpeed(double a) {
        std::cout << std::format("Parameter {0}", a) << std::endl;
        double x = a;
        long long n = 10'000'000;
        MeasureTime(n, [&]() { x = (a / x + x) / 2; });
        std::cout << std::format("{0}\n\n", x);
    }

}

using namespace Performance;

int main() {
    auto inf = std::numeric_limits<double>::infinity();
    TestSpeed(2);
    TestSpeed(inf);
    return 0;
}

当我在发布模式下运行这个程序时,我得到:

Parameter 2
181 Mop/s, 55 ms
1.414213562373095

Parameter inf
192 Mop/s, 52 ms
-nan(ind)

这是预期的;即性能没有差异。

这两个程序均在 Visual Studio 2022 版本 17.1.0 中构建。 C# 项目是一个 Net Framework 4.7.2 控制台应用程序。

【问题讨论】:

  • 这仅通过调试配置重现。
  • 对我来说,默认发布配置会发生这种情况。似乎有一些特定于我的环境或机器的东西,因为当我在各种在线 C# 编译器上运行它时,它运行良好。
  • 在选项中有一个选项“首选 32 位”。取消选中该选项时,问题就解决了!我想知道发生了什么事。
  • fdivfadd 用于 32 位+调试环境。他们似乎计算 NaN 数很慢。 This answer 包含一些细节。
  • 另一方面,在 32 位模式下构建 C++ 程序完全不会影响其性能。

标签: c# performance floating-point


【解决方案1】:

通过在 C# 项目选项中取消选中 Prefer 32-bits 解决了该问题。

通过将 Visual Studio 中的 Enable Enhanced Instruction Set 选项更改为 No Enhanced Instructions (/arch:IA32)Streaming SIMD Extensions (/arch:SSE),我还能够重现 C++ 端的性能问题。这些选项仅在构建 32 位程序时可用。正如 @shingo 在 cmets 中所暗示的那样,在较旧的 32 位指令集中使用 NaN 进行计算时似乎存在性能问题。实际上,当参数 a 设置为无穷大时,给定代码仅使用 NaN 进行计算。

【讨论】:

    猜你喜欢
    • 2017-09-18
    • 1970-01-01
    • 1970-01-01
    • 2022-11-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-17
    • 1970-01-01
    相关资源
    最近更新 更多