首頁  >  文章  >  web前端  >  與 JSON.stringify 競爭 - 透過建立自訂的 JSON.stringify

與 JSON.stringify 競爭 - 透過建立自訂的 JSON.stringify

王林
王林原創
2024-08-14 14:35:02352瀏覽

Competing with JSON.stringify - by building a custom one

這是在與我的朋友討論遞歸時提出的。為什麼不建造
Javascript JSON.stringify 方法作為遞歸程式設計練習?看起來很棒
主意。

我很快就起草了第一個版本。而且表現很糟糕!
所需時間約為標準的 4 倍 JSON.stringify。

第一稿

function json_stringify(obj) {
  if (typeof obj == "number" || typeof obj == "boolean") {
    return String(obj);
  }

  if (typeof obj == "string") {
    return `"${obj}"`;
  }

  if (Array.isArray(obj)) {
    return "[" + obj.map(json_stringify).join(",") + "]";
  }

  if (typeof obj === "object") {
    const properties_str = Object.entries(obj)
      .map(([key, val]) => {
        return `"${key}":${json_stringify(val)}`;
      })
      .join(",");
    return "{" + properties_str + "}";
  }
}

透過執行以下指令,我們可以看到我們的 json_stringify 的工作方式為
預計。

const { assert } = require("console");
const test_obj = {
  name: "John Doe",
  age: 23,
  hobbies: ["football", "comet study"]
};

assert(json_stringify(test_obj) === JSON.stringify(test_obj))

測試更多場景,並進行多次運行,以大致了解我們的
腳本運行,我們製作了一個簡單的測試腳本!

一個簡單的測試腳本

function validity_test(fn1, fn2, test_values) {
  for (const test_value of test_values) {
    assert(fn1(test_value) == fn2(test_value));
  }
}

function time(fn, num_runs = 1, ...args) {
  const start_time = Date.now()

  for (let i = 0; i < num_runs; i++) {
    fn(...args);
  }

  const end_time = Date.now()
  return end_time - start_time
}


function performance_test(counts) {
  console.log("Starting performance test with", test_obj);

  for (const count of counts) {
    console.log("Testing", count, "times");

    const duration_std_json = time(JSON.stringify.bind(JSON), count, test_obj);
    console.log("\tStd lib JSON.stringify() took", duration_std_json, "ms");

    const duration_custom_json = time(json_stringify, count, test_obj);
    console.log("\tCustom json_stringify() took", duration_custom_json, "ms");
  }
}

const test_obj = {} // a deeply nested JS object, ommitted here for brevity 
const test_values = [
  12,
  "string test",
  [12, 34, 1],
  [12, true, 1, false],
  test_obj
];

validity_test(JSON.stringify, json_stringify, test_values);
performance_test([1000, 10_000, 100_000, 1000_000]);

運行這個我們得到如下的計時。

Testing 1000 times
    Std lib JSON.stringify() took 5 ms
    Custom json_stringify() took 20 ms
Testing 10000 times
    Std lib JSON.stringify() took 40 ms
    Custom json_stringify() took 129 ms
Testing 100000 times
    Std lib JSON.stringify() took 388 ms
    Custom json_stringify() took 1241 ms
Testing 1000000 times
    Std lib JSON.stringify() took 3823 ms
    Custom json_stringify() took 12275 ms

它可能在不同的系統上運作不同,但所花費的時間比例
透過 std JSON.strngify 到我們自訂的 json_stringify 應該是關於
1:3 - 1:4

在一個有趣的案例中,情況也可能有所不同。繼續閱讀以了解更多關於
那個!

提升績效

首先可以解決的是地圖功能的使用。它創造了
舊數組中的新數組。在我們的物件範例中,它會建立一個
陣列 包含物件條目的陣列中的 JSON 字串化物件屬性。

陣列元素的字串化也會發生類似的情況。

我們必須循環遍歷數組中的元素或物件的條目!但是
我們可以跳過建立另一個陣列來連接 JSON 字串化部分。

這是更新版本(為簡潔起見,僅顯示更改的部分)

function json_stringify(val) {
  if (typeof val === "number" || typeof val === "boolean") {
    return String(val);
  }

  if (typeof val === "string") {
    return `"${val}"`;
  }

  if (Array.isArray(val)) {
    let elements_str = "["

    let sep = ""
    for (const element of val) {
      elements_str += sep + json_stringify(element)
      sep = ","
    }
    elements_str += "]"

    return elements_str
  }

  if (typeof val === "object") {
    let properties_str = "{"

    let sep = ""
    for (const key in val) {
      properties_str += sep + `"${key}":${json_stringify(val[key])}`
      sep = ","
    }
    properties_str += "}"

    return properties_str;
  }
}

這是現在測試腳本的輸出

Testing 1000 times
        Std lib JSON.stringify() took 5 ms
        Custom json_stringify() took 6 ms
Testing 10000 times
        Std lib JSON.stringify() took 40 ms
        Custom json_stringify() took 43 ms
Testing 100000 times
        Std lib JSON.stringify() took 393 ms
        Custom json_stringify() took 405 ms
Testing 1000000 times
        Std lib JSON.stringify() took 3888 ms
        Custom json_stringify() took 3966 ms

現在看起來好多了。我們的自訂 json_stringify 只花費 3 毫秒
比 JSON.stringify 能夠對深層巢狀物件進行字串化 10,000 次。
雖然這並不完美,但這是可以接受的延遲。

還擠多了? ?

當前的延遲可能是由於所有字串創建和連接
這正在發生。每次我們執行 elements_str += sep + json_stringify(element)
我們正在連接 3 個字串。

連接字串的成本很高,因為它需要

  1. 建立一個新的字串緩衝區來容納整個組合字串
  2. 將各個字串複製到新建立的緩衝區

透過我們自己使用緩衝區並直接將資料寫入那裡可能會給我們
性能改進。因為我們可以建立一個大緩衝區(例如 80 個字元)
然後創建新的緩衝區以容納 80 個字符,當它用完時。

我們不會完全避免資料的重新分配/複製,但我們會
減少這些操作。

另一個可能的延遲是遞歸過程本身!具體來說
函數呼叫會佔用時間。考慮我們的函數呼叫 json_stringify(val)
它只有一個參數。

了解函數調用

步驟是

  1. 將回傳位址壓入堆疊
  2. 將參數參考壓入堆疊
  3. 在被呼叫的函數中
    1. 從堆疊中彈出參數參考
    2. 從堆疊中彈出回位址
    3. 將回傳值(字串化部分)壓入堆疊
  4. 在呼叫函數中
    1. 從堆疊中彈出函數傳回的值

所有這些操作都是為了確保函數呼叫發生,這會增加 CPU
費用。

如果我們建立一個非遞歸的 json_stringify 演算法來完成所有這些操作
上面列出的函數呼叫(乘以此類呼叫的次數)將為
減少到沒有。

這可以是未來的嘗試。

NodeJs版本差異

這裡需要注意的最後一件事。考慮測試腳本的以下輸出

Testing 1000 times
        Std lib JSON.stringify() took 8 ms
        Custom json_stringify() took 8 ms
Testing 10000 times
        Std lib JSON.stringify() took 64 ms
        Custom json_stringify() took 51 ms
Testing 100000 times
        Std lib JSON.stringify() took 636 ms
        Custom json_stringify() took 467 ms
Testing 1000000 times
        Std lib JSON.stringify() took 6282 ms
        Custom json_stringify() took 4526 ms

我們的自訂 json_stringify 是否比 NodeJs 標準表現更好
JSON.stringify???

嗯,是的!但這是舊版的 NodeJs (v18.20.3)。事實證明,對於
這個版本(也可能更低)我們客製化的 json_stringify 可以工作
比標準庫更快!

本文的所有測試(除了最後一個)均已使用
完成 節點 v22.6.0

JSON.stringify 的效能從 v18 提升到了 v22。這太棒了

還要注意的是,我們的腳本在 NodeJs v22 中表現得更好。
所以,這意味著 NodeJs 也提高了運行時的整體效能。
底層V8引擎本身可能發生了更新。

嗯,這對我來說是一次愉快的經驗。我希望這是為了
你也是。在所有這些享受中,我們學到了一兩件事!

繼續構建,繼續測試!

以上是與 JSON.stringify 競爭 - 透過建立自訂的 JSON.stringify的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn