比较 c#、c++ 和 java 的性能(c# 的奇怪行为)

Posted

技术标签:

【中文标题】比较 c#、c++ 和 java 的性能(c# 的奇怪行为)【英文标题】:Comparing c# , c++ and java performance ( Strange behavior of c# ) 【发布时间】:2014-11-22 16:26:39 【问题描述】:

我正在使用 c++、c# 和 java 实现 Floyd–Warshall 算法。在每种语言中,我在测试结果后都使用顺序和并行实现:(经过的时间仅用于主要功能和读取文件,变量的 Inti 和 ... 未测量。)在此处下载源代码SourceCodes

c++

IDE:Netbeans 编译器:MinGW-4.8.1 连续时间:9.333000 并行时间:2.539000 将OpenMp 用于Prallel

如果NumOfThreads=1 那么是顺序的,否则是并行的

变量

#define n 1000 /* Then number of nodes */
double dist[n][n];

    void floyd_warshall(int NumOfThreads) 
    int i, j, k;
         omp_set_num_threads(NumOfThreads);
    for (k = 0; k < n; ++k)
       #pragma omp parallel for private(i,j)
        for (i = 0; i < n; ++i)
            for (j = 0; j < n; ++j)
                    if ((dist[i][k] * dist[k][j] != 0) && (i != j))
                     if ((dist[i][k] + dist[k][j] < dist[i][j]) || (dist[i][j] == 0))
                        dist[i][j] = dist[i][k] + dist[k][j];    

java

IDE:Netbeans 编译器:Netbeans 默认 连续时间:11.632 并行时间:3.089 -Xms512m -Xmx1024m 导入 java.util.concurrent.Callable; 导入 java.util.concurrent.ExecutionException; 导入 java.util.concurrent.ExecutorService; 导入 java.util.concurrent.Future;

java变量

 public final int numNodes =1000;

    public final double[][] costs= new double[numNodes][numNodes] ;

我没有把 java 代码放在这里,因为它工作正常(我认为)

c#

    IDE:Visual Studio 2012 编译器:Visual Studio 2012 默认 连续时间:31.312 并行时间:8.920 使用 System.Threading.Tasks;

变量

  const int n = 1000;
    static double[,] dist = new double[n, n];

并行代码

   static  void floyd_warshall(ParallelOptions pOp)
    
        int k;     
        for (k = 0; k < n; ++k)
            Parallel.For<int>(0, n, pOp, () => 0, (i, loop, j) =>
                
              for (j = 0; j < n; ++j)
               if ((dist[i, k] * dist[k, j] != 0) && (i != j))
                  if ((dist[i, k] + dist[k, j] < dist[i, j]) || (dist[i, j] == 0))
                            dist[i, j] = dist[i, k] + dist[k, j];
                    return 0;
                , (j) =>  );

单码

 static void floyd_warshallSingle()
  
      int i, j, k;
      for (k = 0; k < n; ++k)
          for (i = 0; i < n; ++i)
              for (j = 0; j < n; ++j)

                  if ((dist[i,k] * dist[k,j] != 0) && (i != j))

                      if ((dist[i,k] + dist[k,j] < dist[i,j]) || (dist[i,j] == 0))
                          dist[i,j] = dist[i,k] + dist[k,j];
  

我的 c# 实现有什么问题?都使用同一个文件 现在我的问题是为什么用 c# 解决这个算法需要更多时间? java 和 c++ 的运行时间几乎相同,但我认为我使用 c# 的实现是错误的,因为 c# 和 c++ 之间的这种差异很奇怪! 请帮助我改进我的 C# 实现或说出一些原因谢谢!

编辑 1


我将数组更改为锯齿状数组,结果更改为:

连续时间:19.22 并行时间:4.903

在 c# 和 c++ 或 java 之间仍然存在巨大差异!知道为什么吗? 新变量

const int n = 1000;
    static double[][] dist = new double[n][];

新代码:

static void floyd_warshallSingle()
  
      int i, j, k;
      for (k = 0; k < n; ++k)
          for (i = 0; i < n; ++i)
              for (j = 0; j < n; ++j)

                  if ((dist[i][k] * dist[k][j] != 0) && (i != j))

                      if ((dist[i][k] + dist[k][j] < dist[i][j]) || (dist[i][j] == 0))
                          dist[i][j] = dist[i][k] + dist[k][j];
  



   static  void floyd_warshall(ParallelOptions pOp)
    
        int k;     
        for (k = 0; k < n; ++k)
            Parallel.For<int>(0, n, pOp, () => 0, (i, loop, j) =>
                
                    for (j = 0; j < n; ++j)
                        if ((dist[i][k] * dist[k][j] != 0) && (i != j))
                            if ((dist[i][ k] + dist[k][j] < dist[i][ j]) || (dist[i][j] == 0))
                                dist[i][ j] = dist[i][k] + dist[k][j];

                    return 0;
                , (j) =>  );
    

【问题讨论】:

您在 C++ 和 Java 中使用锯齿状数组,但在 C# 中使用二维数组。后者在某些情况下速度较慢。尝试将您的 C# 实现也转换为锯齿状数组。 为了消除混淆:在 C# 中有int[][]int[] 的数组)和int[,](平面内存分配)——前者通常被称为锯齿状。 int[][] 在 C# 中显然比 int[,] 更快的事实只是意味着编译器/CLR 在某个地方做得很糟糕。在线性访问的情况下,两者的速度应该差不多,但是如果你对锯齿状数组进行随机访问,你会得到第二次缓存未命中,这是可以避免的。但是,锯齿状数组没有理由比平面数组更快。 另一个问题可能是操作顺序。 C# 有严格的从左到右的规则。所以对于if ((dist[i,k] * dist[k,j] != 0) &amp;&amp; (i != j)),乘法总是完成。 C++ 编译器(我不了解 Java)可能会生成代码来首先执行 i != j 测试,这会更快。以下if 语句也是如此:将(dist[i,j] == 0) 测试放在首位。 你为什么用乘法测试 !=0?不会检查双方 !=0 是否相等并保存乘法> 您遇到的一个问题(正如我在博客文章中指出的那样)是数组边界检查。在 C# 中,每个索引都会根据数组的边界进行检查。这将对运行时间产生影响。不过,我不希望它是双倍的。 【参考方案1】:

确定它是否是数组边界检查或至少确定数组边界检查是否是问题的部分的一种方法是删除一些索引计算。例如:

    static void floyd_warshallSingle()
    
        int i, j, k;
        for (k = 0; k < n; ++k)
        
            var distk = dist[k];
            for (i = 0; i < n; ++i)
            
                var disti = dist[i];
                for (j = 0; j < n; ++j)
                    if ((i != j) && (disti[k] * distk[j] != 0))
                        if ((disti[j] == 0) || disti[k] + distk[j] < disti[j])
                            disti[j] = disti[k] + distk[j];
            
        
    

我在这里所做的只是使用distk 作为对dist[k] 的引用。我怀疑编译器已经在进行这种优化,这很可能是您实现从矩形阵列到锯齿状阵列时获得的性能提升的方式。但值得一试。

另外,您说您在没有附加调试器的情况下运行。我假设您也在运行发布版本?并且所有三个程序(C++、Java 和 C#)都运行相同的位数吗?也就是说,它们都是 64 位可执行文件吗?所有 32 位可执行文件?小心 C#,因为可以在项目选项中打开“首选 32 位”标志。这可能会导致您在使用“任何 CPU”进行编译时以 32 位模式运行,即使在 64 位系统上也是如此。

【讨论】:

感谢您的好回答。 我假设您也在运行发布版本?在运行发布版本时间更改为 10.108 和 2.976 后,这是我的问题

以上是关于比较 c#、c++ 和 java 的性能(c# 的奇怪行为)的主要内容,如果未能解决你的问题,请参考以下文章

性能提升:使用c#调用c++(核心代码优化)

python、c++、c#、java中字符串比较的区别

C++买啥书比较好

C++ java 和 C# 的区别

C++ 与 Java/C# 比较

用 C++ 编写性能关键的 C# 代码