如何提高 Rust 序列化性能?- 2

上一篇文章中,我們手動實現 Serde 庫中的 Serialize trait, 提高了序列化的性能。但是不能使用默認的#[derive(Serialize)] 功能,在這一篇文章中,我們來解決這個問題,使這兩種情景可以兼容。

格式化器

我們不要在數據類型上直接手動實現 Serialize trait。相反,應該在類似格式化器的封裝類型上實現它。

這裏有一個例子:

struct DisplayFormatter<T: Display>(T);

impl<T: Display> Serialize for DisplayFormatter<T> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.collect_str(&self.0)
    }
}

此泛型格式器封裝了實現 Display 的類型 T,並使用該表示對 T 進行序列化。你不僅可以在 Name 類型上使用它,還可以在任何實現 Display 的類型上使用它。

但是,有時你的類型並不一定要實現 Display,或者你想要不同的 Display 和 Serialize 實現。在這種情況下,可以使用一個封裝具體類型的格式化器:

struct FullNameFormatter<'a>(&'a Name);

impl<'a> Serialize for FullNameFormatter<'a> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.collect_str(&format_args!("{} {}", self.0.first_name, self.0.last_name))
    }
}

這裏,我們直接使用 format_args! 宏,像 println! 宏和 format! 宏在底層使用的就是 format_args! 宏。它返回 Arguments,重要的是 Arguments 實現了 collect_str() 方法所需的 Display。

現在,當我們想要序列化 Name 時,我們可以使用該格式化器:

fn formatter(names: &[Name]) -> serde_json::Result<String> {
    let full_names = names.iter().map(FullNameFormatter).collect::<Vec<_>>();

    serde_json::to_string(&full_names)
}

我們將每個 & Name 映射到 FullNameFormatter(&Name),並將映射收集到一個向量中,然後將該向量傳遞給 serde_json 進行序列化。

查看基準測試結果,可以看到該方法與之前的方法幾乎具有相同的性能:

serialization            fastest       │ slowest       │ median        │ mean          │ samples │ iters
├─ ser_formatter                       │               │               │               │         │
│  ├─ 0                  282.2 ms      │ 502 ms        │ 294 ms        │ 312.4 ms      │ 100     │ 100
..........    
│  ╰─ 20                 99.15 ms      │ 222.2 ms      │ 110.4 ms      │ 116.2 ms      │ 100     │ 100
╰─ ser_manual_serialize                │               │               │               │         │
   ├─ 0                  172 ms        │ 299.8 ms      │ 175.6 ms      │ 184 ms        │ 100     │ 100
..........
   ╰─ 20                 95.45 ms      │ 127.9 ms      │ 99.34 ms      │ 100.7 ms      │ 100     │ 100

幾乎沒性能差異,因爲封裝器類型在 Rust 中是零成本抽象。

但實際上,應該有一個與封裝器類型本身無關的額外成本。在上面的測試結果中應該會看到,對於低 N 值,此方法的性能略低於 manual_serialize

這是因爲有一次收集 map 數據並進行 Vec 的分配!這種分配幾乎沒有出現在基準測試結果中,特別是對於較高的 N 值。這是因爲它只執行一次,與序列化本身相比,它的開銷可以忽略不計。

接下來,我們將取消 Vec 分配,雖然這似乎是一個不必要的優化,但至少可以看到另一個格式化程序示例。

序列格式化器

我們不能跳過收集 map 數據並將 Iterator 傳遞給 serde_json 序列化器的過程,因爲 Serde 庫不直接支持 Iterator 的序列化。Serialize trait 沒有被 Iterator 實現,但是 Serde 的 Serializer 提供了 collect_seq 方法來收集 Iterator。

我們需要一個封裝器類型,它接受 slice 並通過將 map 後的數據傳遞給 collect_seq 來進行序列化:

struct FullNameSequenceFormatter<'a>(&'[Name]);

impl<'a> Serialize for FullNameSequenceFormatter<'a> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.collect_seq(self.0.iter().map(FullNameFormatter))
    }
}

現在我們可以將序列格式化器傳遞給 serde_json:

fn sequence_formatter(names: &[Name]) -> serde_json::Result<String> {
    serde_json::to_string(&FullNameSequenceFormatter(names))
}

我們再來看看基準測試結果:

serialization              fastest       │ slowest       │ median        │ mean          │ samples │ iters
├─ ser_manual_serialize                  │               │               │               │         │
│  ├─ 0                    170.6 ms      │ 358.9 ms      │ 185.8 ms      │ 193.5 ms      │ 100     │ 100
............
│  ╰─ 20                   97.34 ms      │ 143.5 ms      │ 101.2 ms      │ 106.2 ms      │ 100     │ 100
╰─ ser_sequence_formatter                │               │               │               │         │
   ├─ 0                    172.8 ms      │ 225.5 ms      │ 183.3 ms      │ 186.7 ms      │ 100     │ 100
............
   ╰─ 20                   97.89 ms      │ 152.6 ms      │ 99.4 ms       │ 102 ms        │ 100     │ 100

這纔是真正的沒有性能差異!

總結

“避免分配” 是這兩篇文章的真正結論,更具體地說,應該是避免在序列化之前分配數據的中間狀態。

我們已經看到,Serialize Trait 的簡單手工實現可以帶來很大的性能改進。但這並不意味着應該總是手動實現 Serialize Trait,只有需要自定義序列化時才應該手動實現。否則,只需在類型上使用#[derive(Serialize)] 即可。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/v0wy72Li5UT60OtJw3ozZg