静态数据繁重的Rust库看起来很臃肿

时间:2016-07-23 18:00:31

标签: size rust

我最近开发了一个Rust库,试图提供对大型数据库的快速访问(Unicode字符数据库,它作为一个扁平的XML文件是160MB)。我还希望它占用空间小,所以我使用了各种方法来减小尺寸。最终结果是我有一系列静态切片看起来像:

#[derive(Clone,Copy,Eq,PartialEq,Debug)]
pub enum UnicodeCategory {
    UppercaseLetter,
    LowercaseLetter,
    TitlecaseLetter,
    ModifierLetter,
    OtherLetter,
    NonspacingMark,
    SpacingMark,
    EnclosingMark,
    DecimalNumber,
    // ...
}

pub static UCD_CAT: &'static [((u8, u8, u8), (u8, u8, u8), UnicodeCategory)] =
    &[((0, 0, 0), (0, 0, 31), UnicodeCategory::Control),
      ((0, 0, 32), (0, 0, 32), UnicodeCategory::SpaceSeparator),
      ((0, 0, 33), (0, 0, 35), UnicodeCategory::OtherPunctuation),
      /* ... */];

// ...

pub static UCD_DECOMP_MAP: &'static [((u8, u8, u8), &'static [(u8, u8, u8)])] =
    &[((0, 0, 160), &[(0, 0, 32)]),
      ((0, 0, 168), &[(0, 0, 32), (0, 3, 8)]),
      ((0, 0, 170), &[(0, 0, 97)]),
      ((0, 0, 175), &[(0, 0, 32), (0, 3, 4)]),
      ((0, 0, 178), &[(0, 0, 50)]),
      /* ... */];

总的来说,所有数据应该只占用大约600kB(假设有额外的对齐空间等),但在发布模式下产生的库是3.3MB。源代码本身(几乎所有数据)都是2.6MB,所以我不明白为什么结果会更多。我不认为额外的大小是固有的,因为在项目开始时(当我只有大约2kB的数据时)大小<50kB。如果它有所不同,我也使用#![no_std]功能。

是否有任何理由需要额外的二进制膨胀,是否有办法减小尺寸?从理论上讲,我不明白为什么我不能将库减少到兆字节或更少。

根据Matthieu的建议,我尝试用nm分析二进制文件。

因为我的所有表都被表示为借用的切片,所以这对于计算表大小非常有用,因为它们都是匿名的_ref。我可以确定的是最大地址0x1208f8,它与~1MB而不是3.3MB的文件大小一致。我还查看了十六进制转储以查看是否有任何可以解释它的空块,但是没有。

要查看是否是借用的切片问题,我将它们转换为非借用的切片([T; N]形式)。文件大小没有太大变化,但现在我可以很容易地解释nm数据。奇怪的是,这些表格确切地说明了我对它们的期望(更奇怪的是,它们在不考虑对齐时与我的下限匹配,并且表格之间没有空格)。

我还查看了嵌套借用切片的表格,例如上面UCD_DECOMP_MAP。当我删除所有这些(大约2/3的数据)时,文件大小是〜1MB,它应该只有~250kB(通过我的计算和最高nm地址,0x3d1d0),所以它没有&# 39;看起来这些表也是问题。

我尝试从.rlib文件(这是一个简单的ar格式存档)中提取单个文件。事实证明,40%的库只是元数据文件,而实际的目标文件是1.9MB。此外,当我在没有借用引用的情况下对库执行此操作时,目标文件为261kB!然后我回到原始库并查看了各个_ref的大小,发现对于像UCD_DECOMP_MAP: &'static [((u8,u8,u8),&'static [(u8,u8,u8)])]这样的表,类型((u8,u8,u8),&'static [(u8,u8,u8)])的每个值占用24个字节(3个u8三元组的字节数,5个字节的填充和16个字节的指针),结果这些表占用了比我想象的更多的空间。我想我现在可以完全考虑所有文件大小。

当然,3MB仍然很小,我只想保持文件尽可能小!

1 个答案:

答案 0 :(得分:0)

感谢Matthieu M.和Chris Emerson指出我的解决方案。这是对问题更新的总结,对不起重复!

似乎有两个原因可以说是膨胀:

  1. 输出的.rlib文件不是纯对象文件,而是ar存档文件。通常这样的文件完全由一个或多个目标文件组成,但rust也包括元数据。部分原因似乎是为了避免需要单独的头文件。这占最终文件大小的40%左右。

  2. 我的计算结果对某些表来说并不准确,这些表也恰好是最大的表。使用nm我能够发现,对于普通表,例如UCD_CAT: &'static [((u8,u8,u8), (u8,u8,u8), UnicodeCategory)],每个项目的大小为7个字节(实际上 比我原先预期的那样,假设8用于对齐的字节)。所有这些表的总数大约是230kB,包含这些表的目标文件以260kB(提取后)出现,所以这一切都是一致的。

    但是,更仔细地检查其他表(例如nm)的UCD_DECOMP_MAP: &'static [((u8,u8,u8),&'static [(u8,u8,u8)])]输出更加困难,因为它们显示为匿名借用对象。然而,事实证明每个((u8,u8,u8),&'static [(u8,u8,u8)])实际占用24个字节:第一个元组为3个字节,填充为5个字节,指针为16个字节。我相信这是因为指针还包括引用数组的大小。这增加了大约一兆字节的膨胀,但似乎占了整个文件大小。