如何快速统计大流中的差异个数?

如何快速统计大流中的差异个数?

我想计算两个大流(设备/文件)中的差异(不同字节)的数量。例如两个硬盘,或一个硬盘和/dev/zero.

所涉及的程序必须很快,与输入的流一样快(例如 1GB/s,尽管 0.2GB/s 可能也可以),并且它们最多可以使用几 GB 的 RAM 和 tmp 文件。特别是,没有足够大的可用文件系统来存储要计算的差异。这些流的大小有几 TB。

计数不需要(事实上也不能)将空格或换行符与其他字符区别对待。这些流只是二进制的,不能解释为文本。

我说的是流,尽管这些设备实际上是可查找的。但出于速度原因,如果可能的话,所有操作最好通过一次流传输完成,无需查找。

到目前为止我尝试过的:

cmp -l /dev/sda /dev/sdb | wc

但这太慢了;wc单独使用一个CPU核心> 50%,输出只有大约2MB / s,这太慢了100倍。

答案1

使用可查找文件,您可以并行化此操作。例如,您可以执行以下操作(未经测试):

# Number of jobs to run at once
jobs=4
# Block size
bs=512
# Total number of block to work with
size=1000000
# Or, to calculate the size automatically for one of the disks,
#size=$(( $(df -B 1 /dev/sda | tail -n1 | awk '{ print $2 }') / $bs ))

# Number of blocks per job
count=$(( $size / $jobs )) 
for i in $(seq 1 $jobs) ; do
    seek=$(( ($i - 1) * $count ))
    cmp -l <(dd bs=$bs skip=$seek count=$count if=/dev/sdb1) <(dd bs=$bs skip=$seek count=$count if=/dev/sdb2) | wc &
done

ddif 第一次启动时只会查找一次。

答案2

除非您的硬件支持,否则您不会获得 1GB/s。最快的 7200 rpm 硬盘在其部分表面上可以达到约 200MB/s,在整个表面上更接近 100MB 到 150MB。因此,您的“可能没问题”数字实际上高于理想数字,除非您的驱动器速度异常快( 10krpm,RAID0)。

cmp -l | wc可能受到CPU的限制,因为你要求wc计算字数,这有点复杂。cmp -l | wc -l会减少CPU的负载。

除非您有非常快的磁盘,否则cmp -l | wc -l可能已经受到 IO 限制。如果您使用整个磁盘带宽,并行化将无济于事。

如果cmp -l | wc -l仍然受 CPU 限制,或者如果您想释放 CPU 来做其他事情,那么执行计数的临时程序会表现得更好。警告,未经测试。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define BUFFER_SIZE 1048576
unsigned char buf1[BUFFER_SIZE];
unsigned char buf2[BUFFER_SIZE];
int main (int argc, char *argv[]) {
    FILE *f1, *f2;
    unsigned long long total = 0, diffs = 0;
    unsigned n1, n2, n, i;
    f1 = fopen(argv[1], "r");
    if (f1 == NULL) {perror(argv[1]); return 2;}
    f2 = fopen(argv[2], "r");
    if (f2 == NULL) {perror(argv[2]); return 2;}
    do {
        n1 = fread(buf1, 1, BUFFER_SIZE, f1);
        n2 = fread(buf2, 1, BUFFER_SIZE, f2);
        if (n1 > n2) n = n2; else n = n1;
        for (i = 0; i < n; i++) if (buf1[i] != buf2[i]) ++diffs;
        total += n;
    } while (n1 == BUFFER_SIZE && n2 == BUFFER_SIZE);
    printf("%llu\n", diffs);
    if (ferror(f1)) {perror(argv[1]);}
    if (ferror(f2)) {perror(argv[2]);}
    if (n1 != n2) {
        fprintf(stderr, "Stopped after %llu bytes.\n", total);
        return 1;
    }
    return 0;
}

相关内容