我想知道为什么包含大字符串的结构的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文件,使用任何可用的同步方法从磁盘加载。在对这些片段进行基准测试时,显然没有考虑文件读取操作。
答案 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)
}
})
结果
请注意,我使用--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
创建一张票是值得的 --no-debug
标志可能没有必要,但在撰写本文时,存在一个未解决的问题,表明在某些情况下它是: