C#中的二维阵列性能。NET与Java
我编写了一些代码来测试C#(.NETCLR)和Java(Java8,Windows)中数组的性能。对于普通平面阵列。NET显示出比Java快一点
当我编写一些测试2d数组(使用锯齿数组)的代码时,我注意到Java和C#之间存在明显的差距。Java版本运行速度比C快2倍以上
这是我的C代码:
class ArrayTest
{
public int [][] jagged;
public ArrayTest(int width, int height)
{
Height = height;
Width = width;
Random rng = new Random();
jagged = new int[height][];
for (int i = 0; i < height; i++)
{
jagged[i] = new int[width];
for (int j = 0; j < width; j++)
{
jagged[i][j] = rng.Next(1024);
}
}
}
public int Height { get; private set; }
public int Width { get; private set; }
public void DoMath(ArrayTest a)
{
for (int i = 0; i < Height; i++)
{
for (int j = 0; j < Width; j++)
{
jagged[i][j] *= a.jagged[i][j];
}
}
}
}
class Program
{
static void Main(string[] args)
{
const int loop = 500;
int width = 800, height = 800;
ArrayTest a1 = new ArrayTest(width, height),
a2 = new ArrayTest(width, height);
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < loop; i++)
{
a1.DoMath(a2);
}
sw.Stop();
Console.WriteLine("Time taken: " + sw.ElapsedMilliseconds);
Console.ReadKey();
}
}
在我的电脑中,被测部分大约需要2200ms才能运行
以下是Java版本:
public class ArrayTest {
private int width, height;
private int[][] array;
public ArrayTest(int width, int height) {
this.width = width;
this.height = height;
array = new int[height][width];
Random rng = new Random();
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
array[i][j] = rng.nextInt(1024);
}
}
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int[][] getArray() {
return array;
}
public void doMath(ArrayTest a) {
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
array[i][j] *= a.array[i][j];
}
}
}
}
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
final int loop = 500;
int width = 800,
height = 800;
ArrayTest a1 = new ArrayTest(width, height),
a2 = new ArrayTest(width, height);
long start, end;
start = java.lang.System.currentTimeMillis();
for (int i = 0; i < loop; i++) {
a1.doMath(a2);
}
end = java.lang.System.currentTimeMillis();
System.out.println("Elapsed time: " + (end - start));
}
}
运行大约需要930毫秒
到目前为止,它是C#2200ms与java930ms的对比
但是,当我将C#方法更改为如下所示时:
public void DoMath(ArrayTest a)
{
int[][] _jagged = this.jagged,
_a = a.jagged;
int[] __jagged, __a;
for (int i = 0; i < _jagged.Length; i++)
{
__jagged = _jagged[i];
__a = _a[i];
for (int j = 0; j < __jagged.Length; j++)
{
__jagged[j] *= __a[j];
}
}
}
然后我的C#代码变得和Java一样快!(930毫秒) 我花了很长时间才找到这段代码,我仍然不知道为什么它比第一个版本更快(而且更难看)
因此,以下是我的问题:
- 为什么最后一个代码比第一个代码更有效李>
- 我可以重写它,使它变得更有效(或不那么难看,但像这个一样有效)李>
[编辑]
我正在使用。NET4.5和Java8都是控制台应用程序。 这个NET版本我正在发布模式下运行,并且没有附加VS调试器
[编辑2]
需要明确的是:它不是一个微观基准或类似的东西。我只想让2d数组的操作比Java更快(或和Java一样快),而且我更喜欢更干净的代码
# 1 楼答案
我做了这些新的测试,我的新C#代码运行速度与Java差不多
代码如下:
我不会发布Java的代码,因为它与本文非常相似
以下是每项实施的结果:
C#,使用*=运算符
C#,no*=运算符:
爪哇:
正如您所看到的,C#的最快方法(doMathFasts)总是非常接近Java的方法(DoMath)。 我的猜测是,这是因为C#的平面数组访问(已经测试过)比Java的更快,并且该方法的执行速度更快。甚至认为在DoMathsFastest中的操作
引入一些开销,最终通过更快的访问来补偿。然而,在Java中,它没有任何明显的区别
这只是我的猜测。我不确定这是否真的会发生
谢谢你的回答
[编辑]
通过测试发现了一些有趣的新信息。 你发现
操作总是比以前慢
操作,但这仅对由完整索引(a[i][j])访问的锯齿数组的成员有效
还发现,在某些情况下,使用索引器(this[i,j])与通过(this.jagged[i][j])直接访问数组成员的速度一样快,在某些情况下甚至更快!太神奇了
用新代码和测试结果编辑了原始帖子以进行比较
# 2 楼答案
我对C#代码进行了一些调整,以最大限度地(据我所知)使用该技术。这里是“doMath”调整方法:
即使进行这种优化并在项目设置上启用优化代码,C#相比之下仍然较慢。 有趣的是,在本例中使用统一2D数组(
int[,]
)的速度要慢得离谱。 我还将Java改为使用nanoTime
而不是currentTimeMillis
,因为据我所知,currentTimeMillis
可以使用一些优化来“撒谎”我的结果是:
爪哇:
C#:
这是在i7处理器上,频率为2.2ghz,turbo boost可能因Hyper-V Windows 8错误而禁用
这里发生了什么强> 你注意到我们做了500次同样的手术了吗?Java的优化允许它存储一个内部快捷方式,以更快地到达响应。 我怎么知道,为什么强> 因为如果我们在nanoTime checker中使用DoMath函数,我们会看到第一次DoMath运行大约需要13毫秒才能完成,第二次运行大约需要7毫秒,然后是4秒,然后是2秒,它会进一步减少,直到优化开始并将其快捷方式应用到整个过程中。与此同时,我们的C#对手将从第一次到最后一次DoMath操作,需要2毫秒才能完成
正如我们所知,Java的编译器也通过在编译时简化公式来进行优化,这两个优化功能的关联可能会导致这种结果发生,另一方面,C#确实会一步一步地做所有的数学运算,而不使用任何快捷方式
**编辑** 如果我们以这种方式进一步优化C#:
我们可以取得以下成果: C#:
现在,作为一名C#用户,我发现知道这一点令人惊讶。我在这里学到了一件重要的事情:myArray[I][j]比创建指向myArray[I]的新的_myArray[]要慢得多。这种优化使C#的运行速度与java优化一样快,但需要使用并行处理。 C#仍然像好的旧C一样一遍又一遍地做着所有的数学运算,但是每一行的高度都由不同的线程处理,这些线程受到限制和控制。NET运行时,它们在多处理器内核上并行运行,但在单核处理器上可能没有这么多
# 3 楼答案
我将DoMath方法更改如下:
我只将a.jagged缓存到一个局部变量(arr),它需要1400毫秒而不是1650毫秒。 我用java进行了测试,耗时1120毫秒。 我使用了10次迭代的10000x1000数组
关于这个性能问题,我有一些看法,我认为它是有用的:
1-测试时,请使用大型阵列以避免在CPU中缓存内存,这可能会让我们感到困惑。例如,200 MB或更大。因此,使用10次迭代的10000x1000数组更好
我在{a1}中读到了。NET Core 2.0多维数组的性能几乎是阻塞数组的两倍
3-当性能有问题时,我们通常会生成难看的代码。例如,在图像处理中使用指针(C#中的不安全代码)是非常常见的。您可以使用指针来解决这个问题,它可以大大提高性能。但是在Java中不能使用指针
关于为什么使用
__jagged = _jagged[i];
时性能会提高的另一个问题的回答是:因为缓存了我使用的内存位置。它将内存引用从两次减少到一次# 4 楼答案
尝试进行以下非常小的更改:
这几乎使我的机器的性能提高了三倍。您是否将C#项目设置为在发布模式或调试模式下运行?这有很大的区别
以下是我的结果:
那么,为什么这个小小的变化会产生如此大的影响呢?在调试模式下运行会禁用C#中的优化,因此编译器不会优化对
Height
和Width
属性的调用。每次循环时,它都会转到属性,然后属性必须转到backing字段以获取值,然后返回因为您在Java中使用的是字段而不是属性,所以不必每次循环都进行此往返。尝试将循环中的宽度和高度替换为getWidth和getHeight,然后查看它的作用
在发布模式下运行并进行优化应该确保您正在对苹果进行比较。即使没有上面的修改,只要切换到发布模式,C代码的速度也会提高3倍