Crystal:包含大字符串

时间:2017-12-23 12:22:41

标签: json node.js performance go crystal-lang

我想知道为什么包含大字符串的结构的json序列化在Crystal中很慢。

以下代码执行得相当差:

struct Page
  include AutoJson
  field :uri, String
  field :html, String
end

page = Page.new(url, html) # html is a string containing ±128KB of html

page.to_json

而Javascript(Node.js)或Go中的以下代码几乎是瞬间完成的(比x10~x20倍快):

Node.js的

page = { url: url, html: html }
JSON.stringify(page)

开始

type Page struct {
  Uri string `json="uri"`
  Html string `json="html"`
}

page = Page{ uri, html }

json, _ = json.Marshal(page)  

考虑到Crystal通常非常快(与Go相提并且比V8 Javascript快得多)它让我想知道这里发生了什么。

我一直在尝试使用Crystal代码,看起来好像这里的冒号位是双引号字符串转义大字符串(这在序列化json对象时显然是必需的)。但为什么需要这么长时间,我不知道(多次分配,副本?)。

对于记录,在这些示例中,html是一个大约128KB的html文件,使用任何可用的同步方法从磁盘加载。在对这些片段进行基准测试时,显然没有考虑文件读取操作。

3 个答案:

答案 0 :(得分:1)

与许多其他API一样,Crystal的JSON实现并未真正针对速度进行优化。它只是让它运作。对于大多数用例而言,这实际上已经非常快,但肯定有很大的改进等待。

我不确定这里到底是什么原因。它可能与字符串转义有关,尽管这也需要在其他语言中完成。

关于与JavaScript的比较,将对象转换为JSON实际上是非常高效的,因为这是JavaScript的本机数据类型并且非常有效地实现。这不是动态代码评估,而是在Javascript VM中编译。

答案 1 :(得分:1)

我在macOS x86_64上使用Crystal 0.25.1(LLVM 6.0.1),1.10.3,node.js v8.11.2进行了测试。

所有示例都将一个161 KB的html文件读入字符串,打开一个临时文件,并进行了10.000次迭代,以序列化页面对象并将其写入文件。

这会生成约1.5 GB的JSON,系统具有非常快的PCIe SSD,因此IO吞吐量不是瓶颈。

我选择实际将数据写入文件,以确保编译器无法优化函数调用。

水晶

require "json"
require "tempfile"
url = "http://www.example.org"
html = File.read("index.html")

record(Page, uri : String, html : String) do
  include JSON::Serializable
end

Tempfile.open("foo") do |io|
  10_000.times do
    page = Page.new(url, html)
    page.to_json(io)
  end
end

开始

package main

import (
  "encoding/json"
  "io/ioutil"
  "log"
  "os"
)

type Page struct {
  Uri  string `json="uri"`
  Html string `json="html"`
}

func main() {
  buf, err := ioutil.ReadFile("index.html")
  if err != nil {
    log.Fatal(err)
  }
  uri := "http://www.example.org"
  html := string(buf)
  file, err := ioutil.TempFile(os.TempDir(), "foo")
  if err != nil {
    log.Fatal(err)
  }
  defer os.Remove(file.Name())
  for i := 0; i < 10000; i++ {
    page := Page{uri, html}
    json, err := json.Marshal(page)
    if err != nil {
      log.Fatal(err)
    }
    _, err = file.Write(json)
    if err != nil {
      log.Fatal(err)
    }
  }
}

Node.js

const fs = require('fs')
const tmp = require('tmp')
const uri = 'http://www.example.org'
const html = fs.readFileSync('index.html')

tmp.file((err, path, fd) => {
  if (err) throw err;

  for(let i = 0; i < 10000; i++) {
    const page = { uri, html }
    const json = JSON.stringify(page)
    fs.writeSync(fd, json)
  }
})

结果

  • 开始:10.88秒,8.5 MB RAM
  • 晶体:12.62秒,2 MB RAM,慢1.16倍
  • node.js:101.82秒,75 MB RAM,慢了9.36倍

请注意,我使用--release编译了Crystal示例,并将代码更新为0.25.1。

Node.js示例使用v8而不是v10,因为v10与我用于临时文件的node-tmp npm模块不兼容。

基准测试是在2015年初的13英寸Retina MacBook Pro上完成的,该MacBook Pro具有i7-5557U CPU,16 GB RAM和1 TB PCIe SSD。

答案 2 :(得分:0)

尝试:

crystal build test.cr --release --no-debug

如果这不能解决问题,那么在https://github.com/crystal-lang/crystal/issues

创建一张票是值得的

Re: - no-debug

--no-debug标志可能没有必要,但在撰写本文时,存在一个未解决的问题,表明在某些情况下它是:

https://github.com/crystal-lang/crystal/issues/4880