代码之家  ›  专栏  ›  技术社区  ›  theflash

将已知大小的切片在铁锈中组合成一个阵列

  •  1
  • theflash  · 技术社区  · 2 年前

    试图找到将切片组合到数据库密钥中的最佳方式。

    我需要一个包含三个内容(串联)的切片:

    1. 前缀字节(u8)
    2. 以编译时已知大小(20字节)传入的切片
    3. 以编译时已知大小(32字节)传入的切片

    我尝试过对以下3个功能进行基准测试。我本以为“slot_key_array”是最快的,因为它没有分配向量,但“slot_key_size”始终是最快。为什么?

    
    #[inline]
    pub fn slot_key_vec(address: &[u8], slot: &[u8]) -> Vec<u8> {
      let mut key: Vec<u8> = vec![DB_PREFIX_SLOT];
      key.extend_from_slice(address);
      key.extend_from_slice(slot);
      key
    }
    
    #[inline]
    pub fn slot_key_sized(address: &[u8], slot: &[u8]) -> Vec<u8> {
      let mut key: Vec<u8> = Vec::with_capacity(1 + 20 + 32);
      key.push(DB_PREFIX_SLOT);
      key.extend_from_slice(address);
      key.extend_from_slice(slot);
      key
    }
    
    #[inline]
    pub fn slot_key_array(address: &[u8], slot: &[u8]) -> [u8; 53] {
      let key: [u8; 53] = [
        DB_PREFIX_SLOT,
        address[0], address[1], address[2], address[3], address[4], address[5], address[6], address[7],
        address[8], address[9], address[10], address[11], address[12], address[13], address[14], address[15],
        address[16], address[17], address[18], address[19],
        slot[0], slot[1], slot[2], slot[3], slot[4], slot[5], slot[6], slot[7],
        slot[8], slot[9], slot[10], slot[11], slot[12], slot[13], slot[14], slot[15],
        slot[16], slot[17], slot[18], slot[19], slot[20], slot[21], slot[22], slot[23],
        slot[24], slot[25], slot[26], slot[27], slot[28], slot[29], slot[30], slot[31],
      ];
      key
    }
    
    2 回复  |  直到 2 年前
        1
  •  2
  •   Chayim Friedman    2 年前

    您可能在Linux上,那里的默认分配器很好(我在Windows上进行了基准测试,将分配器替换为 mimalloc 我得到了同样的结果)。

    这是因为数组版本需要对每个元素进行边界检查,这会阻止向量化并增加开销。但他们不必这样做:如果我们制作参数数组而不是切片,那么边界检查就会消失。

    pub fn slot_key_array_arrays(address: &[u8; 20], slot: &[u8; 32]) -> [u8; 53] {
        // ...
    }
    

    如果只有切片,则可以使用将它们转换为数组 try_into() :

    #[inline]
    pub fn slot_key_copy_array_try_into(address: &[u8], slot: &[u8]) -> [u8; 53] {
        slot_key_array_arrays(address.try_into().unwrap(), slot.try_into().unwrap())
    }
    

    另一种选择是使用 copy_from_slice() :

    #[inline]
    pub fn slot_key_copy_from_slice(address: &[u8], slot: &[u8]) -> [u8; 53] {
        let mut key: [u8; 53] = [0; 53];
        key[0] = DB_PREFIX_SLOT;
        key[1..][..20].copy_from_slice(address);
        key[21..].copy_from_slice(slot);
        key
    }
    

    基准

    copy_from_slice         time:   [6.8321 ns 6.8841 ns 6.9611 ns]
    Found 13 outliers among 100 measurements (13.00%)
      4 (4.00%) high mild
      9 (9.00%) high severe
    
    array_try_into          time:   [4.6906 ns 4.7001 ns 4.7119 ns]
    Found 11 outliers among 100 measurements (11.00%)
      3 (3.00%) high mild
      8 (8.00%) high severe
    
    array_arrays            time:   [4.8212 ns 4.8325 ns 4.8473 ns]
    Found 15 outliers among 100 measurements (15.00%)
      4 (4.00%) high mild
      11 (11.00%) high severe
    
    array                   time:   [12.963 ns 12.992 ns 13.029 ns]
    Found 17 outliers among 100 measurements (17.00%)
      5 (5.00%) high mild
      12 (12.00%) high severe
    
    sized                   time:   [10.116 ns 10.218 ns 10.327 ns]
    Found 4 outliers among 100 measurements (4.00%)
      2 (2.00%) high mild
      2 (2.00%) high severe
    
    vec                     time:   [25.602 ns 27.162 ns 29.211 ns]
    Found 7 outliers among 100 measurements (7.00%)
      6 (6.00%) high mild
      1 (1.00%) high severe
    

    因此,使用数组而不是切片的版本与 try_into() (性能相同),那么 复制from_slice() ,然后是其他人。

    另一种选择是帮助LLVM消除绑定检查。它在这里有问题,因为你按递增顺序索引:我们检查索引0,但这对索引1没有任何意义,所以我们也检查它,然后是2,然后是3……但如果我们先检查最后一个索引,LLVM知道以前的所有索引也存在:

    #[inline]
    pub fn slot_key_array(address: &[u8], slot: &[u8]) -> [u8; 53] {
        address[19];
        slot[31];
    
        // ...
    }
    

    这使此代码与数组版本不相上下:

    array                   time:   [4.6107 ns 4.6164 ns 4.6238 ns]
                            change: [-65.325% -64.755% -64.411%] (p = 0.00 < 0.05)
                            Performance has improved.
    
        2
  •  0
  •   Stargateur    2 年前

    slot_key_sized 最好的原因很简单,它只分配一次 slot_key_vec 他们可能至少分配了两次。

    slot_key_array 是缓慢的,因为你正在对切片进行多次索引。如果编译器不够聪明,它会多次检查切片的长度。

    一个可能更好的版本是:

    #[inline]
    pub fn slot_key_array(address: &[u8], slot: &[u8]) -> [u8; 53] {
      let mut key: [u8; 53] = [0; 53];
      key[0] = 42;
      key[1..address.len() + 1].copy_from_slice(address);
      key[address.len() + 1..].copy_from_slice(slot);
      key