【问题标题】:A simple duplicate block finding algorithm performs worse when using BloomFilter for lookups一个简单的重复块查找算法在使用 BloomFilter 进行查找时性能更差
【发布时间】:2017-01-26 08:58:58
【问题描述】:

我已将两个 ISO 文件合并为一个文件。两个单独的 ISO 文件都是同一供应商的 Linux 发行版,但版本不同。在我编写的程序中(如下所示),以 512 字节的块读取的连接文件和块的 MD5sum 被计算。 MD5sum 存储在Hashet<String> 中。如果使用HashSet 查找找到具有相同签名的块,则会记录下来。

在实际查找HashSet 之前,也使用BloomFilter 完成了完全相同的算法。由于BloomFilter 仅提供“非包含”保证并且可以提供包含错误肯定,如果BloomFilter 报告可能已经存在密钥,我也会查找 HashSet。

级联文件大小 > 1GB,因此 512 字节块签名的数量超过 177 万。使用BloomFilter 的方法的性能始终是第一种方法的约六倍。

为什么会出现这种情况?我在这里做错了吗?

import com.google.common.base.Charsets;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnel;
import com.google.common.hash.PrimitiveSink;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.time.StopWatch;

public class SimpleDedupTrial {

    public static void main(String[] args) throws IOException {

        int blockSize = 512;

        HashSet<String> signatureSet = new HashSet<>();

        File f = new File(
            "D:\\keshav\\per\\projects\\work-area\\dedup-temp\\merged-iso"
        );

        FileInputStream fis = new FileInputStream(f);

        long length = f.length();
        long sizeSaved = 0l;

        StopWatch sw = new StopWatch();

        int len;
        byte[] buffer = new byte[blockSize];
        while ((len = fis.read(buffer)) != -1) {

            String md5Hex = DigestUtils.md5Hex(buffer);

            if (sw.isStopped()) {
                sw.start();
            }
            if (sw.isSuspended()) {
                sw.resume();
            }

            if (signatureSet.contains(md5Hex)) {
                sizeSaved += len;
            } else {
                signatureSet.add(md5Hex);
            }

            sw.suspend();
        }

        sw.stop();

        fis.close();

        System.out.println("Time: "+sw.getTime(TimeUnit.MILLISECONDS));
        System.out.println("File size in MB: "+convertToMB(length));
        System.out.println("Size saved in MB: "+convertToMB(sizeSaved));
        System.out.println("Signature set size: "+signatureSet.size());
        System.out.println("Duplicate ratio: "+ ((double)sizeSaved * 100 / length));

        System.out.println("With Blooom:");
        useBloomFilter();
    }

    private static long convertToMB(long sizeInBytes) {
        return sizeInBytes / (1024 * 1024);
    }

    private static void useBloomFilter() throws IOException {
        int blockSize = 512;

        Funnel<String> strFunnel = (String t, PrimitiveSink ps) -> {
            ps.putString(t, Charsets.US_ASCII);
        };

        HashSet<String> signatureSet = new HashSet<>();

        File f = new File(
            "D:\\keshav\\per\\projects\\work-area\\dedup-temp\\merged-iso"
        );

        FileInputStream fis = new FileInputStream(f);

        long length = f.length();
        long sizeSaved = 0l;

        BloomFilter<String> signatureBloomFilter = BloomFilter.create(
            strFunnel, (length / blockSize)
        );

        StopWatch sw = new StopWatch();

        int len;
        byte[] buffer = new byte[blockSize];
        while ((len = fis.read(buffer)) != -1) {

            String md5Hex = DigestUtils.md5Hex(buffer);

            if (sw.isStopped()) {
                sw.start();
            }
            if (sw.isSuspended()) {
                sw.resume();
            }

            if (signatureBloomFilter.mightContain(md5Hex)) {
                if (!signatureSet.contains(md5Hex)) {
                    signatureBloomFilter.put(md5Hex);
                    signatureSet.add(md5Hex);
                } else {
                    sizeSaved += len;
                }
            } else {
                signatureBloomFilter.put(md5Hex);
                signatureSet.add(md5Hex);
            }
            sw.suspend();
        }

        sw.stop();

        fis.close();

        System.out.println("Time: "+sw.getTime(TimeUnit.MILLISECONDS));
        System.out.println("File size in MB: "+convertToMB(length));
        System.out.println("Size saved in MB: "+convertToMB(sizeSaved));
        System.out.println("Signature set size: "+signatureSet.size());
        System.out.println("Duplicate ratio: "+ ((double)sizeSaved * 100 / length));
    }
}

样本输出:

Time: 819
File size in MB: 1071
Size saved in MB: 205
Signature set size: 1774107
Duplicate ratio: 19.183032558071734
With Blooom:
Time: 4539
File size in MB: 1071
Size saved in MB: 205
Signature set size: 1774107
Duplicate ratio: 19.183032558071734

【问题讨论】:

  • 对我来说,您误解了BloomFilter 的用途,BloomFilter 是一种轻量级数据结构,我们在被称为像磁盘访问一样慢的东西之前使用它......并且被称为太大加载并简单地保存在缓存中的内存中。在这里,您已经以某种方式使用了缓存(您的 HashSet),如果您负担得起,则根本不需要BloomFilter
  • 我明白了。在我查找文件块签名的用例中,BloomFilters 并没有真正的帮助。是对的吗?假设数百万个文件块签名已经存储在磁盘上的单独文件中(预先计算),并且我只在内存中加载了相应的 BloomFilter。每当 BloomFilter 报告可能存在密钥时,我必须将所有数百万个签名加载到内存中以确定它。我在吗?
  • 如果您需要将数百万个签名加载到内存中,则说明您的架构存在问题。您需要能够直接检查给定签名是否存在。假设您已将所有签名定义到数据库中,并且您使用签名作为主键。假设所有签名都无法放入您的内存中,那么您将使用 BloomFilter,您最初将在其中加载所有签名,然后不是启动查询来检查给定签名是否存在,而是首先检查您的 BF,如果它存在这将允许您减少查询的总数。
  • 只有在 BF 表明它可能存在的情况下才进行检查,正确配置 BF 以限制误报的可能性可以大大减少对慢速后端的访问总量

标签: java algorithm hash md5sum bloom-filter


【解决方案1】:

您似乎有点忽略了Bloom filter 的意思。当我们无法承受记忆并同意失去一些准确性时,我们会使用它们。例如,决定向1/100(或不向他们发送)用户发送 2 个推送通知,以节省存储已收到通知的用户的集合。

HashSet 中,您预计访问时间为O(1),因此Bloom filter 不会加快进程,正如您所见,它会减慢进程。另一方面,它使用的内存非常少,不足以显示在您的统计信息中。

这是因为指示not in 需要大约相同的时间,而in 需要更多时间。

您可以阅读更多here

【讨论】:

  • 对!我猜 BloomFilter 在我的用例中没有用。我不仅需要非收容,还需要立即确定收容,如果不将整个集合加载到内存中,这是无法做到的。
猜你喜欢
  • 1970-01-01
  • 2015-02-08
  • 2019-02-02
  • 1970-01-01
  • 2017-09-30
  • 1970-01-01
  • 2011-07-31
  • 2011-01-26
  • 1970-01-01
相关资源
最近更新 更多