之前我对一个字符串格式的文本文档进行了整理 将其转换为了二进制文件 其格式如下
field (per row) |
type |
size |
field1 |
int64 |
8byte |
field2 |
int64 |
8byte |
这不仅使得体积从约 16GB 减小到约 10GB 还大幅提升了读取和检索的速度
从在 WSL1 中使用 cat + grep 最高 300MB / s 的速度
提升到用 C 写一个简单的程序进行读取的最高约 1GB / s 的速度
最近 为了进一步压缩体积 我决定对其进行压缩
当然 一切的前提是便于检索数据 即可以通过流式方法读取数据
于是我决定使用 GZip 一方面 GZip 是比较广泛的压缩格式
另一方面 C# 在System.IO.Compression
中提供了GZipStream
便于读取
同时也可以用这个进行压缩
压缩测试
GZipStream
提供了CompressionLevel
的选项 包括Optimal
Fastest
SmallestSize
和NoComression
为了选择使用哪个等级压缩 我写了一个简单的基准测试来比较 代码如下
static void Compress(CompressionLevel compressionLevel) { Console.WriteLine($"input file: {rawFile}"); Console.WriteLine($"output file: {gzFile}"); using var inputStream = File.OpenRead(rawFile); using var outputStream = File.OpenWrite(gzFile); using var gzipStream = new GZipStream(outputStream, compressionLevel); var sw = Stopwatch.StartNew(); inputStream.CopyTo(gzipStream); var seconds = sw.Elapsed.TotalSeconds; Console.WriteLine($"Time used: {seconds}s"); long rawSize = new FileInfo(rawFile).Length; long compressedSize = new FileInfo(gzFile).Length; Console.WriteLine($"Compressed {rawSize} bytes to {compressedSize} bytes, compression ratio: {Math.Round((double)compressedSize / rawSize, 5) * 100}%"); Console.WriteLine($"Average speed: " + $"Read: {Math.Round((double)rawSize / 1024 / 1024 / seconds, 3)}MB/s, " + $"Write: {Math.Round((double)compressedSize / 1024 / 1024 / seconds, 3)}MB/s"); } static void Main(string[] args) { foreach (var compressionLevel in Enum.GetValues<CompressionLevel>()) { Console.WriteLine($"====================[{compressionLevel}]===================="); Compress(compressionLevel); Console.WriteLine(); } }
|
使用 NativeAot 编译后运行得到结果
测试平台:
CPU: Intel® Core™ i7-10750H CPU @ 2.60GHz
RAM Size: 16GB
OS Version: Win11 22H2 22621.1778
测试结果:
====================[Optimal]==================== input file: D:\Files\裤子\qq8e.bin output file: D:\Files\裤子\qq8e.gz Time used: 263.0331656s Compressed 11519053264 bytes to 5577536686 bytes, compression ratio: 48.42% Average speed: Read: 41.764MB/s, Write: 20.222MB/s
====================[Fastest]==================== input file: D:\Files\裤子\qq8e.bin output file: D:\Files\裤子\qq8e.gz Time used: 133.5304461s Compressed 11519053264 bytes to 5577536686 bytes, compression ratio: 48.42% Average speed: Read: 82.269MB/s, Write: 39.835MB/s
====================[NoCompression]==================== input file: D:\Files\裤子\qq8e.bin output file: D:\Files\裤子\qq8e.gz Time used: 30.9754716s Compressed 11519053264 bytes to 11520778240 bytes, compression ratio: 100.01500000000001% Average speed: Read: 354.649MB/s, Write: 354.702MB/s
====================[SmallestSize]==================== input file: D:\Files\裤子\qq8e.bin output file: D:\Files\裤子\qq8e.gz
|
SmallestSize 实在等不及了 根据任务管理器的数据估算 Read 在 2MB/s Write 在 200KB/s
照这个速度要 91.55 小时才能压缩完
根据数据 选择 Fastest 即可满足需求
读取并检索
先写一个读取原始二进制数据的代码
var BatchSize = Prompt.Input<int>("Please enter batch size", 65536); using var inputStream = File.OpenRead(rawFile); byte[] bytes = new byte[BatchSize * 16]; long target = 114514L; var sw = Stopwatch.StartNew(); while (inputStream.Read(bytes) > 0) { for (int i = 0; i < BatchSize; i++) { long field1 = BitConverter.ToInt64(bytes, i * 16); long field2 = BitConverter.ToInt64(bytes, i * 16 + 8); if (uid == target) { double seconds = sw.Elapsed.TotalSeconds; Console.WriteLine($"field1: {field1}, field2: {field2}"); Console.WriteLine($"time used: {seconds}s"); Console.WriteLine($"average speed: {Math.Round((double)inputStream.Position / 1024 / 1024 / sw.Elapsed.TotalSeconds, 3)}MB/s"); goto found; } } } Console.WriteLine("Not found!");
|
BatchSize 为 65536 时 速度达到了约 1500MB / s
再看看压缩后的数据
代码差异不大 只不过把FileStream
改成GZipStream
可惜的是 这时 Read 的速度只有约 25MB / s 几乎不能使用
然而我又发现 无论压缩时选用的 ComressionLevel 是什么 (即使是 NoCompression)
读取时速度都是这么慢 可能是我代码写得有点问题 但由于时间原因无法深入研究
总结
寄