Node.js/coffeescript在数学密集型算法上的表现

mat*_*t b 14 performance node.js coffeescript

我与node.js的尝试建立一些服务器端逻辑,并实现了一个版本中描述的菱形方算法的这里在CoffeeScript中和Java.鉴于我对node.js和V8性能的所有赞誉,我希望node.js不会落后于java版本.

但是在4096x4096地图上,Java在1s以下完成,但node.js/coffeescript在我的机器上需要20多秒...

这些是我的全部结果.x轴是网格大小.对数和线性图表:

线性

日志

这是因为我的coffeescript实现有问题,还是这只是node.js的本质?

CoffeeScript的

genHeightField = (sz) ->
    timeStart = new Date()

    DATA_SIZE = sz
    SEED = 1000.0
    data = new Array()
    iters = 0

    # warm up the arrays to tell the js engine these are dense arrays
    # seems to have neligible effect when running on node.js though
    for rows in [0...DATA_SIZE]
        data[rows] = new Array();
        for cols in [0...DATA_SIZE]
            data[rows][cols] = 0

    data[0][0] = data[0][DATA_SIZE-1] = data[DATA_SIZE-1][0] = 
      data[DATA_SIZE-1][DATA_SIZE-1] = SEED;

    h = 500.0
    sideLength = DATA_SIZE-1
    while sideLength >= 2
      halfSide = sideLength / 2

      for x in [0...DATA_SIZE-1] by sideLength
          for y in [0...DATA_SIZE-1] by sideLength
              avg = data[x][y] +
                  data[x + sideLength][y] +
                  data[x][y + sideLength] +
                  data[x + sideLength][y + sideLength]
              avg /= 4.0;

              data[x + halfSide][y + halfSide] = 
                  avg + Math.random() * (2 * h) - h;
              iters++
              #console.log "A:" + x + "," + y

      for x in [0...DATA_SIZE-1] by halfSide
        y = (x + halfSide) % sideLength
        while y < DATA_SIZE-1
          avg = 
            data[(x-halfSide+DATA_SIZE-1)%(DATA_SIZE-1)][y]
            data[(x+halfSide)%(DATA_SIZE-1)][y]
            data[x][(y+halfSide)%(DATA_SIZE-1)]
            data[x][(y-halfSide+DATA_SIZE-1)%(DATA_SIZE-1)]
          avg /= 4.0;

          avg = avg + Math.random() * (2 * h) - h;
          data[x][y] = avg;

          if x is 0
            data[DATA_SIZE-1][y] = avg;
          if y is 0  
            data[x][DATA_SIZE-1] = avg;
          #console.log "B: " + x + "," + y
          y += sideLength
          iters++
      sideLength /= 2
      h /= 2.0

    #console.log iters
    console.log (new Date() - timeStart)

genHeightField(256+1)
genHeightField(512+1)
genHeightField(1024+1)
genHeightField(2048+1)
genHeightField(4096+1)
Run Code Online (Sandbox Code Playgroud)

Java的

import java.util.Random;

class Gen {

    public static void main(String args[]) {
        genHeight(256+1);
        genHeight(512+1);
        genHeight(1024+1);
        genHeight(2048+1);
        genHeight(4096+1);
    }

    public static void genHeight(int sz) {
        long timeStart = System.currentTimeMillis();
        int iters = 0;

        final int DATA_SIZE = sz;
        final double SEED = 1000.0;
        double[][] data = new double[DATA_SIZE][DATA_SIZE];
        data[0][0] = data[0][DATA_SIZE-1] = data[DATA_SIZE-1][0] = 
                data[DATA_SIZE-1][DATA_SIZE-1] = SEED;

        double h = 500.0;
        Random r = new Random();
        for(int sideLength = DATA_SIZE-1;
            sideLength >= 2;
            sideLength /=2, h/= 2.0){
          int halfSide = sideLength/2;

          for(int x=0;x<DATA_SIZE-1;x+=sideLength){
            for(int y=0;y<DATA_SIZE-1;y+=sideLength){
              double avg = data[x][y] + 
                      data[x+sideLength][y] +
                      data[x][y+sideLength] + 
                      data[x+sideLength][y+sideLength];
              avg /= 4.0;

              data[x+halfSide][y+halfSide] = 
                  avg + (r.nextDouble()*2*h) - h;

              iters++;
              //System.out.println("A:" + x + "," + y);
            }
          }

          for(int x=0;x<DATA_SIZE-1;x+=halfSide){
            for(int y=(x+halfSide)%sideLength;y<DATA_SIZE-1;y+=sideLength){
              double avg = 
                    data[(x-halfSide+DATA_SIZE-1)%(DATA_SIZE-1)][y] + 
                    data[(x+halfSide)%(DATA_SIZE-1)][y] + 
                    data[x][(y+halfSide)%(DATA_SIZE-1)] + 
                    data[x][(y-halfSide+DATA_SIZE-1)%(DATA_SIZE-1)]; 
              avg /= 4.0;

              avg = avg + (r.nextDouble()*2*h) - h;
              data[x][y] = avg;

              if(x == 0)  data[DATA_SIZE-1][y] = avg;
              if(y == 0)  data[x][DATA_SIZE-1] = avg;

              iters++;
              //System.out.println("B:" + x + "," + y);
            }
          }
        }
        //System.out.print(iters +" ");
        System.out.println(System.currentTimeMillis() - timeStart);
    }

}
Run Code Online (Sandbox Code Playgroud)

Tre*_*ham 11

正如其他回答者所指出的那样,JavaScript的数组是您正在进行的操作类型的主要性能瓶颈.因为它们是动态的,所以访问元素比使用Java的静态数组要慢得多.

好消息是,JavaScript中存在一种新兴的静态类型数组标准,某些浏览器已经支持这种标准.虽然Node本身尚不支持,但您可以使用库轻松添加它们:https://github.com/tlrobinson/v8-typed-array

typed-array通过npm 安装后,这是我修改后的代码版本:

{Float32Array} = require 'typed-array'

genHeightField = (sz) ->
    timeStart = new Date()
    DATA_SIZE = sz
    SEED = 1000.0
    iters = 0

    # Initialize 2D array of floats
    data = new Array(DATA_SIZE)
    for rows in [0...DATA_SIZE]
      data[rows] = new Float32Array(DATA_SIZE)
      for cols in [0...DATA_SIZE]
          data[rows][cols] = 0

    # The rest is the same...
Run Code Online (Sandbox Code Playgroud)

其中的关键是宣言data[rows].

使用该行data[rows] = new Array(DATA_SIZE)(基本上等同于原始行),我得到基准数字:

17
75
417
1376
5461
Run Code Online (Sandbox Code Playgroud)

随着线路data[rows] = new Float32Array(DATA_SIZE),我得到了

19
47
215
855
3452
Run Code Online (Sandbox Code Playgroud)

因此,一个小的改变可以将运行时间减少大约1/3,即速度提高50%!

它仍然不是Java,但它是一个相当大的改进.期待未来版本的Node/V8进一步缩小性能差距.

警告:必须提到的是,普通的JS数字是双精度的,即64位浮点数.Float32Array因此,使用会降低精度,使其成为苹果和橙子的比较 - 我不知道使用32位数学有多少性能提升,以及更快的数组访问有多少.A Float64Array V8规范的一部分,但尚未在v8类型的数组库中实现.)

  • **更新!**从0.5.5开始,Node.js现在支持类型化数组(包括`Float64Array`)!请注意,0.5.x分支是不稳定的,但这意味着几乎可以肯定Node 0.6+将支持它们. (3认同)