晶体:包含大字符串的结构的慢速JSON序列化



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

以下代码的性能差:

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)

GO

type Page struct {
  Uri string `json="uri"`
  Html string `json="html"`
}
page = Page{ uri, html }
json, _ = json.Marshal(page)  

考虑水晶通常很快(与GO相当,比V8 JavaScript快得多),这让我想知道这里发生了什么。

我一直在尝试一些晶体代码,似乎这里的罪名是大字符串的双引号字符串逃脱(显然是在序列化JSON对象时需要的)。但是,为什么我不知道要花这么长时间(多个分配,副本?)。

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

i用Crystal 0.25.1(LLVM 6.0.1),GO 1.10.3,Node.js v8.11.2在MacOS X86_64上进行了测试。

示例全部读取一个161 kb的HTML文件,打开一个tempfile并执行序列化页面对象并将其写入文件的10.000迭代。

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

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

Crystal

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

GO

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)
  }
})

结果

  • GO:10.88秒,8.5 MB RAM
  • 水晶:12.62秒,2 MB RAM,1.16X作为慢
  • node.js:101.82秒,75 MB RAM,9.36X作为慢

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

node.js示例使用了V8,而不是V10,因为V10与我用于tempfiles的node-tmp NPM模块不相容。

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

与许多其他API一样,Crystal的JSON实现并未真正优化速度。它只是为了使它起作用。实际上,对于大多数用例中,这实际上已经很快了,但是当然有很大的改进正在等待。

我不确定这里的原因是什么。它可能与字符串逃脱有关,但这也需要用其他语言来完成。

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

尝试:

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

如果这无法解决问题,那么值得在https://github.com/crystal-lang/crystal/issues/issues

上创建票

re:---no-debug

--no-debug标志可能不是必需的,但是在撰写本文时,有一个空旷的问题,表明在某些情况下是:

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

相关内容

  • 没有找到相关文章

最新更新